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.show_completions(&ShowCompletions { trigger: None }, 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 window,
5101 cx,
5102 );
5103 }
5104 Some(CompletionsMenuSource::Normal)
5105 | Some(CompletionsMenuSource::SnippetChoices)
5106 | None
5107 if self.is_completion_trigger(
5108 text,
5109 trigger_in_words,
5110 completions_source.is_some(),
5111 cx,
5112 ) =>
5113 {
5114 self.show_completions(
5115 &ShowCompletions {
5116 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
5117 },
5118 window,
5119 cx,
5120 )
5121 }
5122 _ => {
5123 self.hide_context_menu(window, cx);
5124 }
5125 }
5126 }
5127
5128 fn is_completion_trigger(
5129 &self,
5130 text: &str,
5131 trigger_in_words: bool,
5132 menu_is_open: bool,
5133 cx: &mut Context<Self>,
5134 ) -> bool {
5135 let position = self.selections.newest_anchor().head();
5136 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
5137 return false;
5138 };
5139
5140 if let Some(completion_provider) = &self.completion_provider {
5141 completion_provider.is_completion_trigger(
5142 &buffer,
5143 position.text_anchor,
5144 text,
5145 trigger_in_words,
5146 menu_is_open,
5147 cx,
5148 )
5149 } else {
5150 false
5151 }
5152 }
5153
5154 /// If any empty selections is touching the start of its innermost containing autoclose
5155 /// region, expand it to select the brackets.
5156 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5157 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5158 let buffer = self.buffer.read(cx).read(cx);
5159 let new_selections = self
5160 .selections_with_autoclose_regions(selections, &buffer)
5161 .map(|(mut selection, region)| {
5162 if !selection.is_empty() {
5163 return selection;
5164 }
5165
5166 if let Some(region) = region {
5167 let mut range = region.range.to_offset(&buffer);
5168 if selection.start == range.start && range.start >= region.pair.start.len() {
5169 range.start -= region.pair.start.len();
5170 if buffer.contains_str_at(range.start, ®ion.pair.start)
5171 && buffer.contains_str_at(range.end, ®ion.pair.end)
5172 {
5173 range.end += region.pair.end.len();
5174 selection.start = range.start;
5175 selection.end = range.end;
5176
5177 return selection;
5178 }
5179 }
5180 }
5181
5182 let always_treat_brackets_as_autoclosed = buffer
5183 .language_settings_at(selection.start, cx)
5184 .always_treat_brackets_as_autoclosed;
5185
5186 if !always_treat_brackets_as_autoclosed {
5187 return selection;
5188 }
5189
5190 if let Some(scope) = buffer.language_scope_at(selection.start) {
5191 for (pair, enabled) in scope.brackets() {
5192 if !enabled || !pair.close {
5193 continue;
5194 }
5195
5196 if buffer.contains_str_at(selection.start, &pair.end) {
5197 let pair_start_len = pair.start.len();
5198 if buffer.contains_str_at(
5199 selection.start.saturating_sub(pair_start_len),
5200 &pair.start,
5201 ) {
5202 selection.start -= pair_start_len;
5203 selection.end += pair.end.len();
5204
5205 return selection;
5206 }
5207 }
5208 }
5209 }
5210
5211 selection
5212 })
5213 .collect();
5214
5215 drop(buffer);
5216 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5217 selections.select(new_selections)
5218 });
5219 }
5220
5221 /// Iterate the given selections, and for each one, find the smallest surrounding
5222 /// autoclose region. This uses the ordering of the selections and the autoclose
5223 /// regions to avoid repeated comparisons.
5224 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5225 &'a self,
5226 selections: impl IntoIterator<Item = Selection<D>>,
5227 buffer: &'a MultiBufferSnapshot,
5228 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5229 let mut i = 0;
5230 let mut regions = self.autoclose_regions.as_slice();
5231 selections.into_iter().map(move |selection| {
5232 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5233
5234 let mut enclosing = None;
5235 while let Some(pair_state) = regions.get(i) {
5236 if pair_state.range.end.to_offset(buffer) < range.start {
5237 regions = ®ions[i + 1..];
5238 i = 0;
5239 } else if pair_state.range.start.to_offset(buffer) > range.end {
5240 break;
5241 } else {
5242 if pair_state.selection_id == selection.id {
5243 enclosing = Some(pair_state);
5244 }
5245 i += 1;
5246 }
5247 }
5248
5249 (selection, enclosing)
5250 })
5251 }
5252
5253 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5254 fn invalidate_autoclose_regions(
5255 &mut self,
5256 mut selections: &[Selection<Anchor>],
5257 buffer: &MultiBufferSnapshot,
5258 ) {
5259 self.autoclose_regions.retain(|state| {
5260 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5261 return false;
5262 }
5263
5264 let mut i = 0;
5265 while let Some(selection) = selections.get(i) {
5266 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5267 selections = &selections[1..];
5268 continue;
5269 }
5270 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5271 break;
5272 }
5273 if selection.id == state.selection_id {
5274 return true;
5275 } else {
5276 i += 1;
5277 }
5278 }
5279 false
5280 });
5281 }
5282
5283 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5284 let offset = position.to_offset(buffer);
5285 let (word_range, kind) =
5286 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5287 if offset > word_range.start && kind == Some(CharKind::Word) {
5288 Some(
5289 buffer
5290 .text_for_range(word_range.start..offset)
5291 .collect::<String>(),
5292 )
5293 } else {
5294 None
5295 }
5296 }
5297
5298 pub fn visible_excerpts(
5299 &self,
5300 cx: &mut Context<Editor>,
5301 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5302 let Some(project) = self.project() else {
5303 return HashMap::default();
5304 };
5305 let project = project.read(cx);
5306 let multi_buffer = self.buffer().read(cx);
5307 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5308 let multi_buffer_visible_start = self
5309 .scroll_manager
5310 .anchor()
5311 .anchor
5312 .to_point(&multi_buffer_snapshot);
5313 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5314 multi_buffer_visible_start
5315 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5316 Bias::Left,
5317 );
5318 multi_buffer_snapshot
5319 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5320 .into_iter()
5321 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5322 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5323 let buffer_file = project::File::from_dyn(buffer.file())?;
5324 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5325 let worktree_entry = buffer_worktree
5326 .read(cx)
5327 .entry_for_id(buffer_file.project_entry_id()?)?;
5328 if worktree_entry.is_ignored {
5329 None
5330 } else {
5331 Some((
5332 excerpt_id,
5333 (
5334 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5335 buffer.version().clone(),
5336 excerpt_visible_range,
5337 ),
5338 ))
5339 }
5340 })
5341 .collect()
5342 }
5343
5344 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5345 TextLayoutDetails {
5346 text_system: window.text_system().clone(),
5347 editor_style: self.style.clone().unwrap(),
5348 rem_size: window.rem_size(),
5349 scroll_anchor: self.scroll_manager.anchor(),
5350 visible_rows: self.visible_line_count(),
5351 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5352 }
5353 }
5354
5355 fn trigger_on_type_formatting(
5356 &self,
5357 input: String,
5358 window: &mut Window,
5359 cx: &mut Context<Self>,
5360 ) -> Option<Task<Result<()>>> {
5361 if input.len() != 1 {
5362 return None;
5363 }
5364
5365 let project = self.project()?;
5366 let position = self.selections.newest_anchor().head();
5367 let (buffer, buffer_position) = self
5368 .buffer
5369 .read(cx)
5370 .text_anchor_for_position(position, cx)?;
5371
5372 let settings = language_settings::language_settings(
5373 buffer
5374 .read(cx)
5375 .language_at(buffer_position)
5376 .map(|l| l.name()),
5377 buffer.read(cx).file(),
5378 cx,
5379 );
5380 if !settings.use_on_type_format {
5381 return None;
5382 }
5383
5384 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5385 // hence we do LSP request & edit on host side only — add formats to host's history.
5386 let push_to_lsp_host_history = true;
5387 // If this is not the host, append its history with new edits.
5388 let push_to_client_history = project.read(cx).is_via_collab();
5389
5390 let on_type_formatting = project.update(cx, |project, cx| {
5391 project.on_type_format(
5392 buffer.clone(),
5393 buffer_position,
5394 input,
5395 push_to_lsp_host_history,
5396 cx,
5397 )
5398 });
5399 Some(cx.spawn_in(window, async move |editor, cx| {
5400 if let Some(transaction) = on_type_formatting.await? {
5401 if push_to_client_history {
5402 buffer
5403 .update(cx, |buffer, _| {
5404 buffer.push_transaction(transaction, Instant::now());
5405 buffer.finalize_last_transaction();
5406 })
5407 .ok();
5408 }
5409 editor.update(cx, |editor, cx| {
5410 editor.refresh_document_highlights(cx);
5411 })?;
5412 }
5413 Ok(())
5414 }))
5415 }
5416
5417 pub fn show_word_completions(
5418 &mut self,
5419 _: &ShowWordCompletions,
5420 window: &mut Window,
5421 cx: &mut Context<Self>,
5422 ) {
5423 self.open_or_update_completions_menu(
5424 Some(CompletionsMenuSource::Words {
5425 ignore_threshold: true,
5426 }),
5427 None,
5428 window,
5429 cx,
5430 );
5431 }
5432
5433 pub fn show_completions(
5434 &mut self,
5435 options: &ShowCompletions,
5436 window: &mut Window,
5437 cx: &mut Context<Self>,
5438 ) {
5439 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5440 }
5441
5442 fn open_or_update_completions_menu(
5443 &mut self,
5444 requested_source: Option<CompletionsMenuSource>,
5445 trigger: Option<&str>,
5446 window: &mut Window,
5447 cx: &mut Context<Self>,
5448 ) {
5449 if self.pending_rename.is_some() {
5450 return;
5451 }
5452
5453 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5454
5455 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5456 // inserted and selected. To handle that case, the start of the selection is used so that
5457 // the menu starts with all choices.
5458 let position = self
5459 .selections
5460 .newest_anchor()
5461 .start
5462 .bias_right(&multibuffer_snapshot);
5463 if position.diff_base_anchor.is_some() {
5464 return;
5465 }
5466 let buffer_position = multibuffer_snapshot.anchor_before(position);
5467 let Some(buffer) = buffer_position
5468 .buffer_id
5469 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5470 else {
5471 return;
5472 };
5473 let buffer_snapshot = buffer.read(cx).snapshot();
5474
5475 let query: Option<Arc<String>> =
5476 Self::completion_query(&multibuffer_snapshot, buffer_position)
5477 .map(|query| query.into());
5478
5479 drop(multibuffer_snapshot);
5480
5481 // Hide the current completions menu when query is empty. Without this, cached
5482 // completions from before the trigger char may be reused (#32774).
5483 if query.is_none() {
5484 let menu_is_open = matches!(
5485 self.context_menu.borrow().as_ref(),
5486 Some(CodeContextMenu::Completions(_))
5487 );
5488 if menu_is_open {
5489 self.hide_context_menu(window, cx);
5490 }
5491 }
5492
5493 let mut ignore_word_threshold = false;
5494 let provider = match requested_source {
5495 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5496 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5497 ignore_word_threshold = ignore_threshold;
5498 None
5499 }
5500 Some(CompletionsMenuSource::SnippetChoices) => {
5501 log::error!("bug: SnippetChoices requested_source is not handled");
5502 None
5503 }
5504 };
5505
5506 let sort_completions = provider
5507 .as_ref()
5508 .is_some_and(|provider| provider.sort_completions());
5509
5510 let filter_completions = provider
5511 .as_ref()
5512 .is_none_or(|provider| provider.filter_completions());
5513
5514 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5515 if filter_completions {
5516 menu.filter(query.clone(), provider.clone(), window, cx);
5517 }
5518 // When `is_incomplete` is false, no need to re-query completions when the current query
5519 // is a suffix of the initial query.
5520 if !menu.is_incomplete {
5521 // If the new query is a suffix of the old query (typing more characters) and
5522 // the previous result was complete, the existing completions can be filtered.
5523 //
5524 // Note that this is always true for snippet completions.
5525 let query_matches = match (&menu.initial_query, &query) {
5526 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5527 (None, _) => true,
5528 _ => false,
5529 };
5530 if query_matches {
5531 let position_matches = if menu.initial_position == position {
5532 true
5533 } else {
5534 let snapshot = self.buffer.read(cx).read(cx);
5535 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5536 };
5537 if position_matches {
5538 return;
5539 }
5540 }
5541 }
5542 };
5543
5544 let trigger_kind = match trigger {
5545 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5546 CompletionTriggerKind::TRIGGER_CHARACTER
5547 }
5548 _ => CompletionTriggerKind::INVOKED,
5549 };
5550 let completion_context = CompletionContext {
5551 trigger_character: trigger.and_then(|trigger| {
5552 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5553 Some(String::from(trigger))
5554 } else {
5555 None
5556 }
5557 }),
5558 trigger_kind,
5559 };
5560
5561 let Anchor {
5562 excerpt_id: buffer_excerpt_id,
5563 text_anchor: buffer_position,
5564 ..
5565 } = buffer_position;
5566
5567 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5568 buffer_snapshot.surrounding_word(buffer_position, None)
5569 {
5570 let word_to_exclude = buffer_snapshot
5571 .text_for_range(word_range.clone())
5572 .collect::<String>();
5573 (
5574 buffer_snapshot.anchor_before(word_range.start)
5575 ..buffer_snapshot.anchor_after(buffer_position),
5576 Some(word_to_exclude),
5577 )
5578 } else {
5579 (buffer_position..buffer_position, None)
5580 };
5581
5582 let language = buffer_snapshot
5583 .language_at(buffer_position)
5584 .map(|language| language.name());
5585
5586 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5587 .completions
5588 .clone();
5589
5590 let show_completion_documentation = buffer_snapshot
5591 .settings_at(buffer_position, cx)
5592 .show_completion_documentation;
5593
5594 // The document can be large, so stay in reasonable bounds when searching for words,
5595 // otherwise completion pop-up might be slow to appear.
5596 const WORD_LOOKUP_ROWS: u32 = 5_000;
5597 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5598 let min_word_search = buffer_snapshot.clip_point(
5599 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5600 Bias::Left,
5601 );
5602 let max_word_search = buffer_snapshot.clip_point(
5603 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5604 Bias::Right,
5605 );
5606 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5607 ..buffer_snapshot.point_to_offset(max_word_search);
5608
5609 let skip_digits = query
5610 .as_ref()
5611 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5612
5613 let omit_word_completions = !self.word_completions_enabled
5614 || (!ignore_word_threshold
5615 && match &query {
5616 Some(query) => query.chars().count() < completion_settings.words_min_length,
5617 None => completion_settings.words_min_length != 0,
5618 });
5619
5620 let (mut words, provider_responses) = match &provider {
5621 Some(provider) => {
5622 let provider_responses = provider.completions(
5623 buffer_excerpt_id,
5624 &buffer,
5625 buffer_position,
5626 completion_context,
5627 window,
5628 cx,
5629 );
5630
5631 let words = match (omit_word_completions, completion_settings.words) {
5632 (true, _) | (_, WordsCompletionMode::Disabled) => {
5633 Task::ready(BTreeMap::default())
5634 }
5635 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5636 .background_spawn(async move {
5637 buffer_snapshot.words_in_range(WordsQuery {
5638 fuzzy_contents: None,
5639 range: word_search_range,
5640 skip_digits,
5641 })
5642 }),
5643 };
5644
5645 (words, provider_responses)
5646 }
5647 None => {
5648 let words = if omit_word_completions {
5649 Task::ready(BTreeMap::default())
5650 } else {
5651 cx.background_spawn(async move {
5652 buffer_snapshot.words_in_range(WordsQuery {
5653 fuzzy_contents: None,
5654 range: word_search_range,
5655 skip_digits,
5656 })
5657 })
5658 };
5659 (words, Task::ready(Ok(Vec::new())))
5660 }
5661 };
5662
5663 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5664
5665 let id = post_inc(&mut self.next_completion_id);
5666 let task = cx.spawn_in(window, async move |editor, cx| {
5667 let Ok(()) = editor.update(cx, |this, _| {
5668 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5669 }) else {
5670 return;
5671 };
5672
5673 // TODO: Ideally completions from different sources would be selectively re-queried, so
5674 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5675 let mut completions = Vec::new();
5676 let mut is_incomplete = false;
5677 let mut display_options: Option<CompletionDisplayOptions> = None;
5678 if let Some(provider_responses) = provider_responses.await.log_err()
5679 && !provider_responses.is_empty()
5680 {
5681 for response in provider_responses {
5682 completions.extend(response.completions);
5683 is_incomplete = is_incomplete || response.is_incomplete;
5684 match display_options.as_mut() {
5685 None => {
5686 display_options = Some(response.display_options);
5687 }
5688 Some(options) => options.merge(&response.display_options),
5689 }
5690 }
5691 if completion_settings.words == WordsCompletionMode::Fallback {
5692 words = Task::ready(BTreeMap::default());
5693 }
5694 }
5695 let display_options = display_options.unwrap_or_default();
5696
5697 let mut words = words.await;
5698 if let Some(word_to_exclude) = &word_to_exclude {
5699 words.remove(word_to_exclude);
5700 }
5701 for lsp_completion in &completions {
5702 words.remove(&lsp_completion.new_text);
5703 }
5704 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5705 replace_range: word_replace_range.clone(),
5706 new_text: word.clone(),
5707 label: CodeLabel::plain(word, None),
5708 icon_path: None,
5709 documentation: None,
5710 source: CompletionSource::BufferWord {
5711 word_range,
5712 resolved: false,
5713 },
5714 insert_text_mode: Some(InsertTextMode::AS_IS),
5715 confirm: None,
5716 }));
5717
5718 let menu = if completions.is_empty() {
5719 None
5720 } else {
5721 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5722 let languages = editor
5723 .workspace
5724 .as_ref()
5725 .and_then(|(workspace, _)| workspace.upgrade())
5726 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5727 let menu = CompletionsMenu::new(
5728 id,
5729 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5730 sort_completions,
5731 show_completion_documentation,
5732 position,
5733 query.clone(),
5734 is_incomplete,
5735 buffer.clone(),
5736 completions.into(),
5737 display_options,
5738 snippet_sort_order,
5739 languages,
5740 language,
5741 cx,
5742 );
5743
5744 let query = if filter_completions { query } else { None };
5745 let matches_task = if let Some(query) = query {
5746 menu.do_async_filtering(query, cx)
5747 } else {
5748 Task::ready(menu.unfiltered_matches())
5749 };
5750 (menu, matches_task)
5751 }) else {
5752 return;
5753 };
5754
5755 let matches = matches_task.await;
5756
5757 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5758 // Newer menu already set, so exit.
5759 if let Some(CodeContextMenu::Completions(prev_menu)) =
5760 editor.context_menu.borrow().as_ref()
5761 && prev_menu.id > id
5762 {
5763 return;
5764 };
5765
5766 // Only valid to take prev_menu because it the new menu is immediately set
5767 // below, or the menu is hidden.
5768 if let Some(CodeContextMenu::Completions(prev_menu)) =
5769 editor.context_menu.borrow_mut().take()
5770 {
5771 let position_matches =
5772 if prev_menu.initial_position == menu.initial_position {
5773 true
5774 } else {
5775 let snapshot = editor.buffer.read(cx).read(cx);
5776 prev_menu.initial_position.to_offset(&snapshot)
5777 == menu.initial_position.to_offset(&snapshot)
5778 };
5779 if position_matches {
5780 // Preserve markdown cache before `set_filter_results` because it will
5781 // try to populate the documentation cache.
5782 menu.preserve_markdown_cache(prev_menu);
5783 }
5784 };
5785
5786 menu.set_filter_results(matches, provider, window, cx);
5787 }) else {
5788 return;
5789 };
5790
5791 menu.visible().then_some(menu)
5792 };
5793
5794 editor
5795 .update_in(cx, |editor, window, cx| {
5796 if editor.focus_handle.is_focused(window)
5797 && let Some(menu) = menu
5798 {
5799 *editor.context_menu.borrow_mut() =
5800 Some(CodeContextMenu::Completions(menu));
5801
5802 crate::hover_popover::hide_hover(editor, cx);
5803 if editor.show_edit_predictions_in_menu() {
5804 editor.update_visible_edit_prediction(window, cx);
5805 } else {
5806 editor.discard_edit_prediction(false, cx);
5807 }
5808
5809 cx.notify();
5810 return;
5811 }
5812
5813 if editor.completion_tasks.len() <= 1 {
5814 // If there are no more completion tasks and the last menu was empty, we should hide it.
5815 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5816 // If it was already hidden and we don't show edit predictions in the menu,
5817 // we should also show the edit prediction when available.
5818 if was_hidden && editor.show_edit_predictions_in_menu() {
5819 editor.update_visible_edit_prediction(window, cx);
5820 }
5821 }
5822 })
5823 .ok();
5824 });
5825
5826 self.completion_tasks.push((id, task));
5827 }
5828
5829 #[cfg(feature = "test-support")]
5830 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5831 let menu = self.context_menu.borrow();
5832 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5833 let completions = menu.completions.borrow();
5834 Some(completions.to_vec())
5835 } else {
5836 None
5837 }
5838 }
5839
5840 pub fn with_completions_menu_matching_id<R>(
5841 &self,
5842 id: CompletionId,
5843 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5844 ) -> R {
5845 let mut context_menu = self.context_menu.borrow_mut();
5846 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5847 return f(None);
5848 };
5849 if completions_menu.id != id {
5850 return f(None);
5851 }
5852 f(Some(completions_menu))
5853 }
5854
5855 pub fn confirm_completion(
5856 &mut self,
5857 action: &ConfirmCompletion,
5858 window: &mut Window,
5859 cx: &mut Context<Self>,
5860 ) -> Option<Task<Result<()>>> {
5861 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5862 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5863 }
5864
5865 pub fn confirm_completion_insert(
5866 &mut self,
5867 _: &ConfirmCompletionInsert,
5868 window: &mut Window,
5869 cx: &mut Context<Self>,
5870 ) -> Option<Task<Result<()>>> {
5871 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5872 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5873 }
5874
5875 pub fn confirm_completion_replace(
5876 &mut self,
5877 _: &ConfirmCompletionReplace,
5878 window: &mut Window,
5879 cx: &mut Context<Self>,
5880 ) -> Option<Task<Result<()>>> {
5881 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5882 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5883 }
5884
5885 pub fn compose_completion(
5886 &mut self,
5887 action: &ComposeCompletion,
5888 window: &mut Window,
5889 cx: &mut Context<Self>,
5890 ) -> Option<Task<Result<()>>> {
5891 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5892 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5893 }
5894
5895 fn do_completion(
5896 &mut self,
5897 item_ix: Option<usize>,
5898 intent: CompletionIntent,
5899 window: &mut Window,
5900 cx: &mut Context<Editor>,
5901 ) -> Option<Task<Result<()>>> {
5902 use language::ToOffset as _;
5903
5904 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5905 else {
5906 return None;
5907 };
5908
5909 let candidate_id = {
5910 let entries = completions_menu.entries.borrow();
5911 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5912 if self.show_edit_predictions_in_menu() {
5913 self.discard_edit_prediction(true, cx);
5914 }
5915 mat.candidate_id
5916 };
5917
5918 let completion = completions_menu
5919 .completions
5920 .borrow()
5921 .get(candidate_id)?
5922 .clone();
5923 cx.stop_propagation();
5924
5925 let buffer_handle = completions_menu.buffer.clone();
5926
5927 let CompletionEdit {
5928 new_text,
5929 snippet,
5930 replace_range,
5931 } = process_completion_for_edit(
5932 &completion,
5933 intent,
5934 &buffer_handle,
5935 &completions_menu.initial_position.text_anchor,
5936 cx,
5937 );
5938
5939 let buffer = buffer_handle.read(cx);
5940 let snapshot = self.buffer.read(cx).snapshot(cx);
5941 let newest_anchor = self.selections.newest_anchor();
5942 let replace_range_multibuffer = {
5943 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5944 excerpt.map_range_from_buffer(replace_range.clone())
5945 };
5946 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5947 return None;
5948 }
5949
5950 let old_text = buffer
5951 .text_for_range(replace_range.clone())
5952 .collect::<String>();
5953 let lookbehind = newest_anchor
5954 .start
5955 .text_anchor
5956 .to_offset(buffer)
5957 .saturating_sub(replace_range.start);
5958 let lookahead = replace_range
5959 .end
5960 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5961 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5962 let suffix = &old_text[lookbehind.min(old_text.len())..];
5963
5964 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5965 let mut ranges = Vec::new();
5966 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5967
5968 for selection in &selections {
5969 let range = if selection.id == newest_anchor.id {
5970 replace_range_multibuffer.clone()
5971 } else {
5972 let mut range = selection.range();
5973
5974 // if prefix is present, don't duplicate it
5975 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5976 range.start = range.start.saturating_sub(lookbehind);
5977
5978 // if suffix is also present, mimic the newest cursor and replace it
5979 if selection.id != newest_anchor.id
5980 && snapshot.contains_str_at(range.end, suffix)
5981 {
5982 range.end += lookahead;
5983 }
5984 }
5985 range
5986 };
5987
5988 ranges.push(range.clone());
5989
5990 if !self.linked_edit_ranges.is_empty() {
5991 let start_anchor = snapshot.anchor_before(range.start);
5992 let end_anchor = snapshot.anchor_after(range.end);
5993 if let Some(ranges) = self
5994 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5995 {
5996 for (buffer, edits) in ranges {
5997 linked_edits
5998 .entry(buffer.clone())
5999 .or_default()
6000 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6001 }
6002 }
6003 }
6004 }
6005
6006 let common_prefix_len = old_text
6007 .chars()
6008 .zip(new_text.chars())
6009 .take_while(|(a, b)| a == b)
6010 .map(|(a, _)| a.len_utf8())
6011 .sum::<usize>();
6012
6013 cx.emit(EditorEvent::InputHandled {
6014 utf16_range_to_replace: None,
6015 text: new_text[common_prefix_len..].into(),
6016 });
6017
6018 self.transact(window, cx, |editor, window, cx| {
6019 if let Some(mut snippet) = snippet {
6020 snippet.text = new_text.to_string();
6021 editor
6022 .insert_snippet(&ranges, snippet, window, cx)
6023 .log_err();
6024 } else {
6025 editor.buffer.update(cx, |multi_buffer, cx| {
6026 let auto_indent = match completion.insert_text_mode {
6027 Some(InsertTextMode::AS_IS) => None,
6028 _ => editor.autoindent_mode.clone(),
6029 };
6030 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6031 multi_buffer.edit(edits, auto_indent, cx);
6032 });
6033 }
6034 for (buffer, edits) in linked_edits {
6035 buffer.update(cx, |buffer, cx| {
6036 let snapshot = buffer.snapshot();
6037 let edits = edits
6038 .into_iter()
6039 .map(|(range, text)| {
6040 use text::ToPoint as TP;
6041 let end_point = TP::to_point(&range.end, &snapshot);
6042 let start_point = TP::to_point(&range.start, &snapshot);
6043 (start_point..end_point, text)
6044 })
6045 .sorted_by_key(|(range, _)| range.start);
6046 buffer.edit(edits, None, cx);
6047 })
6048 }
6049
6050 editor.refresh_edit_prediction(true, false, window, cx);
6051 });
6052 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6053
6054 let show_new_completions_on_confirm = completion
6055 .confirm
6056 .as_ref()
6057 .is_some_and(|confirm| confirm(intent, window, cx));
6058 if show_new_completions_on_confirm {
6059 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6060 }
6061
6062 let provider = self.completion_provider.as_ref()?;
6063 drop(completion);
6064 let apply_edits = provider.apply_additional_edits_for_completion(
6065 buffer_handle,
6066 completions_menu.completions.clone(),
6067 candidate_id,
6068 true,
6069 cx,
6070 );
6071
6072 let editor_settings = EditorSettings::get_global(cx);
6073 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6074 // After the code completion is finished, users often want to know what signatures are needed.
6075 // so we should automatically call signature_help
6076 self.show_signature_help(&ShowSignatureHelp, window, cx);
6077 }
6078
6079 Some(cx.foreground_executor().spawn(async move {
6080 apply_edits.await?;
6081 Ok(())
6082 }))
6083 }
6084
6085 pub fn toggle_code_actions(
6086 &mut self,
6087 action: &ToggleCodeActions,
6088 window: &mut Window,
6089 cx: &mut Context<Self>,
6090 ) {
6091 let quick_launch = action.quick_launch;
6092 let mut context_menu = self.context_menu.borrow_mut();
6093 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6094 if code_actions.deployed_from == action.deployed_from {
6095 // Toggle if we're selecting the same one
6096 *context_menu = None;
6097 cx.notify();
6098 return;
6099 } else {
6100 // Otherwise, clear it and start a new one
6101 *context_menu = None;
6102 cx.notify();
6103 }
6104 }
6105 drop(context_menu);
6106 let snapshot = self.snapshot(window, cx);
6107 let deployed_from = action.deployed_from.clone();
6108 let action = action.clone();
6109 self.completion_tasks.clear();
6110 self.discard_edit_prediction(false, cx);
6111
6112 let multibuffer_point = match &action.deployed_from {
6113 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6114 DisplayPoint::new(*row, 0).to_point(&snapshot)
6115 }
6116 _ => self
6117 .selections
6118 .newest::<Point>(&snapshot.display_snapshot)
6119 .head(),
6120 };
6121 let Some((buffer, buffer_row)) = snapshot
6122 .buffer_snapshot()
6123 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6124 .and_then(|(buffer_snapshot, range)| {
6125 self.buffer()
6126 .read(cx)
6127 .buffer(buffer_snapshot.remote_id())
6128 .map(|buffer| (buffer, range.start.row))
6129 })
6130 else {
6131 return;
6132 };
6133 let buffer_id = buffer.read(cx).remote_id();
6134 let tasks = self
6135 .tasks
6136 .get(&(buffer_id, buffer_row))
6137 .map(|t| Arc::new(t.to_owned()));
6138
6139 if !self.focus_handle.is_focused(window) {
6140 return;
6141 }
6142 let project = self.project.clone();
6143
6144 let code_actions_task = match deployed_from {
6145 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6146 _ => self.code_actions(buffer_row, window, cx),
6147 };
6148
6149 let runnable_task = match deployed_from {
6150 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6151 _ => {
6152 let mut task_context_task = Task::ready(None);
6153 if let Some(tasks) = &tasks
6154 && let Some(project) = project
6155 {
6156 task_context_task =
6157 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6158 }
6159
6160 cx.spawn_in(window, {
6161 let buffer = buffer.clone();
6162 async move |editor, cx| {
6163 let task_context = task_context_task.await;
6164
6165 let resolved_tasks =
6166 tasks
6167 .zip(task_context.clone())
6168 .map(|(tasks, task_context)| ResolvedTasks {
6169 templates: tasks.resolve(&task_context).collect(),
6170 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6171 multibuffer_point.row,
6172 tasks.column,
6173 )),
6174 });
6175 let debug_scenarios = editor
6176 .update(cx, |editor, cx| {
6177 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6178 })?
6179 .await;
6180 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6181 }
6182 })
6183 }
6184 };
6185
6186 cx.spawn_in(window, async move |editor, cx| {
6187 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6188 let code_actions = code_actions_task.await;
6189 let spawn_straight_away = quick_launch
6190 && resolved_tasks
6191 .as_ref()
6192 .is_some_and(|tasks| tasks.templates.len() == 1)
6193 && code_actions
6194 .as_ref()
6195 .is_none_or(|actions| actions.is_empty())
6196 && debug_scenarios.is_empty();
6197
6198 editor.update_in(cx, |editor, window, cx| {
6199 crate::hover_popover::hide_hover(editor, cx);
6200 let actions = CodeActionContents::new(
6201 resolved_tasks,
6202 code_actions,
6203 debug_scenarios,
6204 task_context.unwrap_or_default(),
6205 );
6206
6207 // Don't show the menu if there are no actions available
6208 if actions.is_empty() {
6209 cx.notify();
6210 return Task::ready(Ok(()));
6211 }
6212
6213 *editor.context_menu.borrow_mut() =
6214 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6215 buffer,
6216 actions,
6217 selected_item: Default::default(),
6218 scroll_handle: UniformListScrollHandle::default(),
6219 deployed_from,
6220 }));
6221 cx.notify();
6222 if spawn_straight_away
6223 && let Some(task) = editor.confirm_code_action(
6224 &ConfirmCodeAction { item_ix: Some(0) },
6225 window,
6226 cx,
6227 )
6228 {
6229 return task;
6230 }
6231
6232 Task::ready(Ok(()))
6233 })
6234 })
6235 .detach_and_log_err(cx);
6236 }
6237
6238 fn debug_scenarios(
6239 &mut self,
6240 resolved_tasks: &Option<ResolvedTasks>,
6241 buffer: &Entity<Buffer>,
6242 cx: &mut App,
6243 ) -> Task<Vec<task::DebugScenario>> {
6244 maybe!({
6245 let project = self.project()?;
6246 let dap_store = project.read(cx).dap_store();
6247 let mut scenarios = vec![];
6248 let resolved_tasks = resolved_tasks.as_ref()?;
6249 let buffer = buffer.read(cx);
6250 let language = buffer.language()?;
6251 let file = buffer.file();
6252 let debug_adapter = language_settings(language.name().into(), file, cx)
6253 .debuggers
6254 .first()
6255 .map(SharedString::from)
6256 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6257
6258 dap_store.update(cx, |dap_store, cx| {
6259 for (_, task) in &resolved_tasks.templates {
6260 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6261 task.original_task().clone(),
6262 debug_adapter.clone().into(),
6263 task.display_label().to_owned().into(),
6264 cx,
6265 );
6266 scenarios.push(maybe_scenario);
6267 }
6268 });
6269 Some(cx.background_spawn(async move {
6270 futures::future::join_all(scenarios)
6271 .await
6272 .into_iter()
6273 .flatten()
6274 .collect::<Vec<_>>()
6275 }))
6276 })
6277 .unwrap_or_else(|| Task::ready(vec![]))
6278 }
6279
6280 fn code_actions(
6281 &mut self,
6282 buffer_row: u32,
6283 window: &mut Window,
6284 cx: &mut Context<Self>,
6285 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6286 let mut task = self.code_actions_task.take();
6287 cx.spawn_in(window, async move |editor, cx| {
6288 while let Some(prev_task) = task {
6289 prev_task.await.log_err();
6290 task = editor
6291 .update(cx, |this, _| this.code_actions_task.take())
6292 .ok()?;
6293 }
6294
6295 editor
6296 .update(cx, |editor, cx| {
6297 editor
6298 .available_code_actions
6299 .clone()
6300 .and_then(|(location, code_actions)| {
6301 let snapshot = location.buffer.read(cx).snapshot();
6302 let point_range = location.range.to_point(&snapshot);
6303 let point_range = point_range.start.row..=point_range.end.row;
6304 if point_range.contains(&buffer_row) {
6305 Some(code_actions)
6306 } else {
6307 None
6308 }
6309 })
6310 })
6311 .ok()
6312 .flatten()
6313 })
6314 }
6315
6316 pub fn confirm_code_action(
6317 &mut self,
6318 action: &ConfirmCodeAction,
6319 window: &mut Window,
6320 cx: &mut Context<Self>,
6321 ) -> Option<Task<Result<()>>> {
6322 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6323
6324 let actions_menu =
6325 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6326 menu
6327 } else {
6328 return None;
6329 };
6330
6331 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6332 let action = actions_menu.actions.get(action_ix)?;
6333 let title = action.label();
6334 let buffer = actions_menu.buffer;
6335 let workspace = self.workspace()?;
6336
6337 match action {
6338 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6339 workspace.update(cx, |workspace, cx| {
6340 workspace.schedule_resolved_task(
6341 task_source_kind,
6342 resolved_task,
6343 false,
6344 window,
6345 cx,
6346 );
6347
6348 Some(Task::ready(Ok(())))
6349 })
6350 }
6351 CodeActionsItem::CodeAction {
6352 excerpt_id,
6353 action,
6354 provider,
6355 } => {
6356 let apply_code_action =
6357 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6358 let workspace = workspace.downgrade();
6359 Some(cx.spawn_in(window, async move |editor, cx| {
6360 let project_transaction = apply_code_action.await?;
6361 Self::open_project_transaction(
6362 &editor,
6363 workspace,
6364 project_transaction,
6365 title,
6366 cx,
6367 )
6368 .await
6369 }))
6370 }
6371 CodeActionsItem::DebugScenario(scenario) => {
6372 let context = actions_menu.actions.context;
6373
6374 workspace.update(cx, |workspace, cx| {
6375 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6376 workspace.start_debug_session(
6377 scenario,
6378 context,
6379 Some(buffer),
6380 None,
6381 window,
6382 cx,
6383 );
6384 });
6385 Some(Task::ready(Ok(())))
6386 }
6387 }
6388 }
6389
6390 pub async fn open_project_transaction(
6391 editor: &WeakEntity<Editor>,
6392 workspace: WeakEntity<Workspace>,
6393 transaction: ProjectTransaction,
6394 title: String,
6395 cx: &mut AsyncWindowContext,
6396 ) -> Result<()> {
6397 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6398 cx.update(|_, cx| {
6399 entries.sort_unstable_by_key(|(buffer, _)| {
6400 buffer.read(cx).file().map(|f| f.path().clone())
6401 });
6402 })?;
6403 if entries.is_empty() {
6404 return Ok(());
6405 }
6406
6407 // If the project transaction's edits are all contained within this editor, then
6408 // avoid opening a new editor to display them.
6409
6410 if let [(buffer, transaction)] = &*entries {
6411 let excerpt = editor.update(cx, |editor, cx| {
6412 editor
6413 .buffer()
6414 .read(cx)
6415 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6416 })?;
6417 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6418 && excerpted_buffer == *buffer
6419 {
6420 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6421 let excerpt_range = excerpt_range.to_offset(buffer);
6422 buffer
6423 .edited_ranges_for_transaction::<usize>(transaction)
6424 .all(|range| {
6425 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6426 })
6427 })?;
6428
6429 if all_edits_within_excerpt {
6430 return Ok(());
6431 }
6432 }
6433 }
6434
6435 let mut ranges_to_highlight = Vec::new();
6436 let excerpt_buffer = cx.new(|cx| {
6437 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6438 for (buffer_handle, transaction) in &entries {
6439 let edited_ranges = buffer_handle
6440 .read(cx)
6441 .edited_ranges_for_transaction::<Point>(transaction)
6442 .collect::<Vec<_>>();
6443 let (ranges, _) = multibuffer.set_excerpts_for_path(
6444 PathKey::for_buffer(buffer_handle, cx),
6445 buffer_handle.clone(),
6446 edited_ranges,
6447 multibuffer_context_lines(cx),
6448 cx,
6449 );
6450
6451 ranges_to_highlight.extend(ranges);
6452 }
6453 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6454 multibuffer
6455 })?;
6456
6457 workspace.update_in(cx, |workspace, window, cx| {
6458 let project = workspace.project().clone();
6459 let editor =
6460 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6461 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6462 editor.update(cx, |editor, cx| {
6463 editor.highlight_background::<Self>(
6464 &ranges_to_highlight,
6465 |theme| theme.colors().editor_highlighted_line_background,
6466 cx,
6467 );
6468 });
6469 })?;
6470
6471 Ok(())
6472 }
6473
6474 pub fn clear_code_action_providers(&mut self) {
6475 self.code_action_providers.clear();
6476 self.available_code_actions.take();
6477 }
6478
6479 pub fn add_code_action_provider(
6480 &mut self,
6481 provider: Rc<dyn CodeActionProvider>,
6482 window: &mut Window,
6483 cx: &mut Context<Self>,
6484 ) {
6485 if self
6486 .code_action_providers
6487 .iter()
6488 .any(|existing_provider| existing_provider.id() == provider.id())
6489 {
6490 return;
6491 }
6492
6493 self.code_action_providers.push(provider);
6494 self.refresh_code_actions(window, cx);
6495 }
6496
6497 pub fn remove_code_action_provider(
6498 &mut self,
6499 id: Arc<str>,
6500 window: &mut Window,
6501 cx: &mut Context<Self>,
6502 ) {
6503 self.code_action_providers
6504 .retain(|provider| provider.id() != id);
6505 self.refresh_code_actions(window, cx);
6506 }
6507
6508 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6509 !self.code_action_providers.is_empty()
6510 && EditorSettings::get_global(cx).toolbar.code_actions
6511 }
6512
6513 pub fn has_available_code_actions(&self) -> bool {
6514 self.available_code_actions
6515 .as_ref()
6516 .is_some_and(|(_, actions)| !actions.is_empty())
6517 }
6518
6519 fn render_inline_code_actions(
6520 &self,
6521 icon_size: ui::IconSize,
6522 display_row: DisplayRow,
6523 is_active: bool,
6524 cx: &mut Context<Self>,
6525 ) -> AnyElement {
6526 let show_tooltip = !self.context_menu_visible();
6527 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6528 .icon_size(icon_size)
6529 .shape(ui::IconButtonShape::Square)
6530 .icon_color(ui::Color::Hidden)
6531 .toggle_state(is_active)
6532 .when(show_tooltip, |this| {
6533 this.tooltip({
6534 let focus_handle = self.focus_handle.clone();
6535 move |_window, cx| {
6536 Tooltip::for_action_in(
6537 "Toggle Code Actions",
6538 &ToggleCodeActions {
6539 deployed_from: None,
6540 quick_launch: false,
6541 },
6542 &focus_handle,
6543 cx,
6544 )
6545 }
6546 })
6547 })
6548 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6549 window.focus(&editor.focus_handle(cx));
6550 editor.toggle_code_actions(
6551 &crate::actions::ToggleCodeActions {
6552 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6553 display_row,
6554 )),
6555 quick_launch: false,
6556 },
6557 window,
6558 cx,
6559 );
6560 }))
6561 .into_any_element()
6562 }
6563
6564 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6565 &self.context_menu
6566 }
6567
6568 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6569 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6570 cx.background_executor()
6571 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6572 .await;
6573
6574 let (start_buffer, start, _, end, newest_selection) = this
6575 .update(cx, |this, cx| {
6576 let newest_selection = this.selections.newest_anchor().clone();
6577 if newest_selection.head().diff_base_anchor.is_some() {
6578 return None;
6579 }
6580 let display_snapshot = this.display_snapshot(cx);
6581 let newest_selection_adjusted =
6582 this.selections.newest_adjusted(&display_snapshot);
6583 let buffer = this.buffer.read(cx);
6584
6585 let (start_buffer, start) =
6586 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6587 let (end_buffer, end) =
6588 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6589
6590 Some((start_buffer, start, end_buffer, end, newest_selection))
6591 })?
6592 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6593 .context(
6594 "Expected selection to lie in a single buffer when refreshing code actions",
6595 )?;
6596 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6597 let providers = this.code_action_providers.clone();
6598 let tasks = this
6599 .code_action_providers
6600 .iter()
6601 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6602 .collect::<Vec<_>>();
6603 (providers, tasks)
6604 })?;
6605
6606 let mut actions = Vec::new();
6607 for (provider, provider_actions) in
6608 providers.into_iter().zip(future::join_all(tasks).await)
6609 {
6610 if let Some(provider_actions) = provider_actions.log_err() {
6611 actions.extend(provider_actions.into_iter().map(|action| {
6612 AvailableCodeAction {
6613 excerpt_id: newest_selection.start.excerpt_id,
6614 action,
6615 provider: provider.clone(),
6616 }
6617 }));
6618 }
6619 }
6620
6621 this.update(cx, |this, cx| {
6622 this.available_code_actions = if actions.is_empty() {
6623 None
6624 } else {
6625 Some((
6626 Location {
6627 buffer: start_buffer,
6628 range: start..end,
6629 },
6630 actions.into(),
6631 ))
6632 };
6633 cx.notify();
6634 })
6635 }));
6636 }
6637
6638 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6639 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6640 self.show_git_blame_inline = false;
6641
6642 self.show_git_blame_inline_delay_task =
6643 Some(cx.spawn_in(window, async move |this, cx| {
6644 cx.background_executor().timer(delay).await;
6645
6646 this.update(cx, |this, cx| {
6647 this.show_git_blame_inline = true;
6648 cx.notify();
6649 })
6650 .log_err();
6651 }));
6652 }
6653 }
6654
6655 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6656 let snapshot = self.snapshot(window, cx);
6657 let cursor = self
6658 .selections
6659 .newest::<Point>(&snapshot.display_snapshot)
6660 .head();
6661 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6662 else {
6663 return;
6664 };
6665
6666 let Some(blame) = self.blame.as_ref() else {
6667 return;
6668 };
6669
6670 let row_info = RowInfo {
6671 buffer_id: Some(buffer.remote_id()),
6672 buffer_row: Some(point.row),
6673 ..Default::default()
6674 };
6675 let Some((buffer, blame_entry)) = blame
6676 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6677 .flatten()
6678 else {
6679 return;
6680 };
6681
6682 let anchor = self.selections.newest_anchor().head();
6683 let position = self.to_pixel_point(anchor, &snapshot, window);
6684 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6685 self.show_blame_popover(
6686 buffer,
6687 &blame_entry,
6688 position + last_bounds.origin,
6689 true,
6690 cx,
6691 );
6692 };
6693 }
6694
6695 fn show_blame_popover(
6696 &mut self,
6697 buffer: BufferId,
6698 blame_entry: &BlameEntry,
6699 position: gpui::Point<Pixels>,
6700 ignore_timeout: bool,
6701 cx: &mut Context<Self>,
6702 ) {
6703 if let Some(state) = &mut self.inline_blame_popover {
6704 state.hide_task.take();
6705 } else {
6706 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6707 let blame_entry = blame_entry.clone();
6708 let show_task = cx.spawn(async move |editor, cx| {
6709 if !ignore_timeout {
6710 cx.background_executor()
6711 .timer(std::time::Duration::from_millis(blame_popover_delay))
6712 .await;
6713 }
6714 editor
6715 .update(cx, |editor, cx| {
6716 editor.inline_blame_popover_show_task.take();
6717 let Some(blame) = editor.blame.as_ref() else {
6718 return;
6719 };
6720 let blame = blame.read(cx);
6721 let details = blame.details_for_entry(buffer, &blame_entry);
6722 let markdown = cx.new(|cx| {
6723 Markdown::new(
6724 details
6725 .as_ref()
6726 .map(|message| message.message.clone())
6727 .unwrap_or_default(),
6728 None,
6729 None,
6730 cx,
6731 )
6732 });
6733 editor.inline_blame_popover = Some(InlineBlamePopover {
6734 position,
6735 hide_task: None,
6736 popover_bounds: None,
6737 popover_state: InlineBlamePopoverState {
6738 scroll_handle: ScrollHandle::new(),
6739 commit_message: details,
6740 markdown,
6741 },
6742 keyboard_grace: ignore_timeout,
6743 });
6744 cx.notify();
6745 })
6746 .ok();
6747 });
6748 self.inline_blame_popover_show_task = Some(show_task);
6749 }
6750 }
6751
6752 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6753 self.inline_blame_popover_show_task.take();
6754 if let Some(state) = &mut self.inline_blame_popover {
6755 let hide_task = cx.spawn(async move |editor, cx| {
6756 if !ignore_timeout {
6757 cx.background_executor()
6758 .timer(std::time::Duration::from_millis(100))
6759 .await;
6760 }
6761 editor
6762 .update(cx, |editor, cx| {
6763 editor.inline_blame_popover.take();
6764 cx.notify();
6765 })
6766 .ok();
6767 });
6768 state.hide_task = Some(hide_task);
6769 true
6770 } else {
6771 false
6772 }
6773 }
6774
6775 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6776 if self.pending_rename.is_some() {
6777 return None;
6778 }
6779
6780 let provider = self.semantics_provider.clone()?;
6781 let buffer = self.buffer.read(cx);
6782 let newest_selection = self.selections.newest_anchor().clone();
6783 let cursor_position = newest_selection.head();
6784 let (cursor_buffer, cursor_buffer_position) =
6785 buffer.text_anchor_for_position(cursor_position, cx)?;
6786 let (tail_buffer, tail_buffer_position) =
6787 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6788 if cursor_buffer != tail_buffer {
6789 return None;
6790 }
6791
6792 let snapshot = cursor_buffer.read(cx).snapshot();
6793 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6794 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6795 if start_word_range != end_word_range {
6796 self.document_highlights_task.take();
6797 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6798 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6799 return None;
6800 }
6801
6802 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6803 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6804 cx.background_executor()
6805 .timer(Duration::from_millis(debounce))
6806 .await;
6807
6808 let highlights = if let Some(highlights) = cx
6809 .update(|cx| {
6810 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6811 })
6812 .ok()
6813 .flatten()
6814 {
6815 highlights.await.log_err()
6816 } else {
6817 None
6818 };
6819
6820 if let Some(highlights) = highlights {
6821 this.update(cx, |this, cx| {
6822 if this.pending_rename.is_some() {
6823 return;
6824 }
6825
6826 let buffer = this.buffer.read(cx);
6827 if buffer
6828 .text_anchor_for_position(cursor_position, cx)
6829 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6830 {
6831 return;
6832 }
6833
6834 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6835 let mut write_ranges = Vec::new();
6836 let mut read_ranges = Vec::new();
6837 for highlight in highlights {
6838 let buffer_id = cursor_buffer.read(cx).remote_id();
6839 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6840 {
6841 let start = highlight
6842 .range
6843 .start
6844 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6845 let end = highlight
6846 .range
6847 .end
6848 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6849 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6850 continue;
6851 }
6852
6853 let range =
6854 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6855 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6856 write_ranges.push(range);
6857 } else {
6858 read_ranges.push(range);
6859 }
6860 }
6861 }
6862
6863 this.highlight_background::<DocumentHighlightRead>(
6864 &read_ranges,
6865 |theme| theme.colors().editor_document_highlight_read_background,
6866 cx,
6867 );
6868 this.highlight_background::<DocumentHighlightWrite>(
6869 &write_ranges,
6870 |theme| theme.colors().editor_document_highlight_write_background,
6871 cx,
6872 );
6873 cx.notify();
6874 })
6875 .log_err();
6876 }
6877 }));
6878 None
6879 }
6880
6881 fn prepare_highlight_query_from_selection(
6882 &mut self,
6883 window: &Window,
6884 cx: &mut Context<Editor>,
6885 ) -> Option<(String, Range<Anchor>)> {
6886 if matches!(self.mode, EditorMode::SingleLine) {
6887 return None;
6888 }
6889 if !EditorSettings::get_global(cx).selection_highlight {
6890 return None;
6891 }
6892 if self.selections.count() != 1 || self.selections.line_mode() {
6893 return None;
6894 }
6895 let snapshot = self.snapshot(window, cx);
6896 let selection = self.selections.newest::<Point>(&snapshot);
6897 // If the selection spans multiple rows OR it is empty
6898 if selection.start.row != selection.end.row
6899 || selection.start.column == selection.end.column
6900 {
6901 return None;
6902 }
6903 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6904 let query = snapshot
6905 .buffer_snapshot()
6906 .text_for_range(selection_anchor_range.clone())
6907 .collect::<String>();
6908 if query.trim().is_empty() {
6909 return None;
6910 }
6911 Some((query, selection_anchor_range))
6912 }
6913
6914 fn update_selection_occurrence_highlights(
6915 &mut self,
6916 query_text: String,
6917 query_range: Range<Anchor>,
6918 multi_buffer_range_to_query: Range<Point>,
6919 use_debounce: bool,
6920 window: &mut Window,
6921 cx: &mut Context<Editor>,
6922 ) -> Task<()> {
6923 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6924 cx.spawn_in(window, async move |editor, cx| {
6925 if use_debounce {
6926 cx.background_executor()
6927 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6928 .await;
6929 }
6930 let match_task = cx.background_spawn(async move {
6931 let buffer_ranges = multi_buffer_snapshot
6932 .range_to_buffer_ranges(multi_buffer_range_to_query)
6933 .into_iter()
6934 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6935 let mut match_ranges = Vec::new();
6936 let Ok(regex) = project::search::SearchQuery::text(
6937 query_text.clone(),
6938 false,
6939 false,
6940 false,
6941 Default::default(),
6942 Default::default(),
6943 false,
6944 None,
6945 ) else {
6946 return Vec::default();
6947 };
6948 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6949 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6950 match_ranges.extend(
6951 regex
6952 .search(buffer_snapshot, Some(search_range.clone()))
6953 .await
6954 .into_iter()
6955 .filter_map(|match_range| {
6956 let match_start = buffer_snapshot
6957 .anchor_after(search_range.start + match_range.start);
6958 let match_end = buffer_snapshot
6959 .anchor_before(search_range.start + match_range.end);
6960 let match_anchor_range = Anchor::range_in_buffer(
6961 excerpt_id,
6962 buffer_snapshot.remote_id(),
6963 match_start..match_end,
6964 );
6965 (match_anchor_range != query_range).then_some(match_anchor_range)
6966 }),
6967 );
6968 }
6969 match_ranges
6970 });
6971 let match_ranges = match_task.await;
6972 editor
6973 .update_in(cx, |editor, _, cx| {
6974 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6975 if !match_ranges.is_empty() {
6976 editor.highlight_background::<SelectedTextHighlight>(
6977 &match_ranges,
6978 |theme| theme.colors().editor_document_highlight_bracket_background,
6979 cx,
6980 )
6981 }
6982 })
6983 .log_err();
6984 })
6985 }
6986
6987 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6988 struct NewlineFold;
6989 let type_id = std::any::TypeId::of::<NewlineFold>();
6990 if !self.mode.is_single_line() {
6991 return;
6992 }
6993 let snapshot = self.snapshot(window, cx);
6994 if snapshot.buffer_snapshot().max_point().row == 0 {
6995 return;
6996 }
6997 let task = cx.background_spawn(async move {
6998 let new_newlines = snapshot
6999 .buffer_chars_at(0)
7000 .filter_map(|(c, i)| {
7001 if c == '\n' {
7002 Some(
7003 snapshot.buffer_snapshot().anchor_after(i)
7004 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7005 )
7006 } else {
7007 None
7008 }
7009 })
7010 .collect::<Vec<_>>();
7011 let existing_newlines = snapshot
7012 .folds_in_range(0..snapshot.buffer_snapshot().len())
7013 .filter_map(|fold| {
7014 if fold.placeholder.type_tag == Some(type_id) {
7015 Some(fold.range.start..fold.range.end)
7016 } else {
7017 None
7018 }
7019 })
7020 .collect::<Vec<_>>();
7021
7022 (new_newlines, existing_newlines)
7023 });
7024 self.folding_newlines = cx.spawn(async move |this, cx| {
7025 let (new_newlines, existing_newlines) = task.await;
7026 if new_newlines == existing_newlines {
7027 return;
7028 }
7029 let placeholder = FoldPlaceholder {
7030 render: Arc::new(move |_, _, cx| {
7031 div()
7032 .bg(cx.theme().status().hint_background)
7033 .border_b_1()
7034 .size_full()
7035 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7036 .border_color(cx.theme().status().hint)
7037 .child("\\n")
7038 .into_any()
7039 }),
7040 constrain_width: false,
7041 merge_adjacent: false,
7042 type_tag: Some(type_id),
7043 };
7044 let creases = new_newlines
7045 .into_iter()
7046 .map(|range| Crease::simple(range, placeholder.clone()))
7047 .collect();
7048 this.update(cx, |this, cx| {
7049 this.display_map.update(cx, |display_map, cx| {
7050 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7051 display_map.fold(creases, cx);
7052 });
7053 })
7054 .ok();
7055 });
7056 }
7057
7058 fn refresh_selected_text_highlights(
7059 &mut self,
7060 on_buffer_edit: bool,
7061 window: &mut Window,
7062 cx: &mut Context<Editor>,
7063 ) {
7064 let Some((query_text, query_range)) =
7065 self.prepare_highlight_query_from_selection(window, cx)
7066 else {
7067 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7068 self.quick_selection_highlight_task.take();
7069 self.debounced_selection_highlight_task.take();
7070 return;
7071 };
7072 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7073 if on_buffer_edit
7074 || self
7075 .quick_selection_highlight_task
7076 .as_ref()
7077 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7078 {
7079 let multi_buffer_visible_start = self
7080 .scroll_manager
7081 .anchor()
7082 .anchor
7083 .to_point(&multi_buffer_snapshot);
7084 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7085 multi_buffer_visible_start
7086 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7087 Bias::Left,
7088 );
7089 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7090 self.quick_selection_highlight_task = Some((
7091 query_range.clone(),
7092 self.update_selection_occurrence_highlights(
7093 query_text.clone(),
7094 query_range.clone(),
7095 multi_buffer_visible_range,
7096 false,
7097 window,
7098 cx,
7099 ),
7100 ));
7101 }
7102 if on_buffer_edit
7103 || self
7104 .debounced_selection_highlight_task
7105 .as_ref()
7106 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7107 {
7108 let multi_buffer_start = multi_buffer_snapshot
7109 .anchor_before(0)
7110 .to_point(&multi_buffer_snapshot);
7111 let multi_buffer_end = multi_buffer_snapshot
7112 .anchor_after(multi_buffer_snapshot.len())
7113 .to_point(&multi_buffer_snapshot);
7114 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7115 self.debounced_selection_highlight_task = Some((
7116 query_range.clone(),
7117 self.update_selection_occurrence_highlights(
7118 query_text,
7119 query_range,
7120 multi_buffer_full_range,
7121 true,
7122 window,
7123 cx,
7124 ),
7125 ));
7126 }
7127 }
7128
7129 pub fn refresh_edit_prediction(
7130 &mut self,
7131 debounce: bool,
7132 user_requested: bool,
7133 window: &mut Window,
7134 cx: &mut Context<Self>,
7135 ) -> Option<()> {
7136 if DisableAiSettings::get_global(cx).disable_ai {
7137 return None;
7138 }
7139
7140 let provider = self.edit_prediction_provider()?;
7141 let cursor = self.selections.newest_anchor().head();
7142 let (buffer, cursor_buffer_position) =
7143 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7144
7145 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7146 self.discard_edit_prediction(false, cx);
7147 return None;
7148 }
7149
7150 self.update_visible_edit_prediction(window, cx);
7151
7152 if !user_requested
7153 && (!self.should_show_edit_predictions()
7154 || !self.is_focused(window)
7155 || buffer.read(cx).is_empty())
7156 {
7157 self.discard_edit_prediction(false, cx);
7158 return None;
7159 }
7160
7161 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7162 Some(())
7163 }
7164
7165 fn show_edit_predictions_in_menu(&self) -> bool {
7166 match self.edit_prediction_settings {
7167 EditPredictionSettings::Disabled => false,
7168 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7169 }
7170 }
7171
7172 pub fn edit_predictions_enabled(&self) -> bool {
7173 match self.edit_prediction_settings {
7174 EditPredictionSettings::Disabled => false,
7175 EditPredictionSettings::Enabled { .. } => true,
7176 }
7177 }
7178
7179 fn edit_prediction_requires_modifier(&self) -> bool {
7180 match self.edit_prediction_settings {
7181 EditPredictionSettings::Disabled => false,
7182 EditPredictionSettings::Enabled {
7183 preview_requires_modifier,
7184 ..
7185 } => preview_requires_modifier,
7186 }
7187 }
7188
7189 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7190 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7191 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7192 self.discard_edit_prediction(false, cx);
7193 } else {
7194 let selection = self.selections.newest_anchor();
7195 let cursor = selection.head();
7196
7197 if let Some((buffer, cursor_buffer_position)) =
7198 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7199 {
7200 self.edit_prediction_settings =
7201 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7202 }
7203 }
7204 }
7205
7206 fn edit_prediction_settings_at_position(
7207 &self,
7208 buffer: &Entity<Buffer>,
7209 buffer_position: language::Anchor,
7210 cx: &App,
7211 ) -> EditPredictionSettings {
7212 if !self.mode.is_full()
7213 || !self.show_edit_predictions_override.unwrap_or(true)
7214 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7215 {
7216 return EditPredictionSettings::Disabled;
7217 }
7218
7219 let buffer = buffer.read(cx);
7220
7221 let file = buffer.file();
7222
7223 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7224 return EditPredictionSettings::Disabled;
7225 };
7226
7227 let by_provider = matches!(
7228 self.menu_edit_predictions_policy,
7229 MenuEditPredictionsPolicy::ByProvider
7230 );
7231
7232 let show_in_menu = by_provider
7233 && self
7234 .edit_prediction_provider
7235 .as_ref()
7236 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7237
7238 let preview_requires_modifier =
7239 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7240
7241 EditPredictionSettings::Enabled {
7242 show_in_menu,
7243 preview_requires_modifier,
7244 }
7245 }
7246
7247 fn should_show_edit_predictions(&self) -> bool {
7248 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7249 }
7250
7251 pub fn edit_prediction_preview_is_active(&self) -> bool {
7252 matches!(
7253 self.edit_prediction_preview,
7254 EditPredictionPreview::Active { .. }
7255 )
7256 }
7257
7258 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7259 let cursor = self.selections.newest_anchor().head();
7260 if let Some((buffer, cursor_position)) =
7261 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7262 {
7263 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7264 } else {
7265 false
7266 }
7267 }
7268
7269 pub fn supports_minimap(&self, cx: &App) -> bool {
7270 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7271 }
7272
7273 fn edit_predictions_enabled_in_buffer(
7274 &self,
7275 buffer: &Entity<Buffer>,
7276 buffer_position: language::Anchor,
7277 cx: &App,
7278 ) -> bool {
7279 maybe!({
7280 if self.read_only(cx) {
7281 return Some(false);
7282 }
7283 let provider = self.edit_prediction_provider()?;
7284 if !provider.is_enabled(buffer, buffer_position, cx) {
7285 return Some(false);
7286 }
7287 let buffer = buffer.read(cx);
7288 let Some(file) = buffer.file() else {
7289 return Some(true);
7290 };
7291 let settings = all_language_settings(Some(file), cx);
7292 Some(settings.edit_predictions_enabled_for_file(file, cx))
7293 })
7294 .unwrap_or(false)
7295 }
7296
7297 fn cycle_edit_prediction(
7298 &mut self,
7299 direction: Direction,
7300 window: &mut Window,
7301 cx: &mut Context<Self>,
7302 ) -> Option<()> {
7303 let provider = self.edit_prediction_provider()?;
7304 let cursor = self.selections.newest_anchor().head();
7305 let (buffer, cursor_buffer_position) =
7306 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7307 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7308 return None;
7309 }
7310
7311 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7312 self.update_visible_edit_prediction(window, cx);
7313
7314 Some(())
7315 }
7316
7317 pub fn show_edit_prediction(
7318 &mut self,
7319 _: &ShowEditPrediction,
7320 window: &mut Window,
7321 cx: &mut Context<Self>,
7322 ) {
7323 if !self.has_active_edit_prediction() {
7324 self.refresh_edit_prediction(false, true, window, cx);
7325 return;
7326 }
7327
7328 self.update_visible_edit_prediction(window, cx);
7329 }
7330
7331 pub fn display_cursor_names(
7332 &mut self,
7333 _: &DisplayCursorNames,
7334 window: &mut Window,
7335 cx: &mut Context<Self>,
7336 ) {
7337 self.show_cursor_names(window, cx);
7338 }
7339
7340 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7341 self.show_cursor_names = true;
7342 cx.notify();
7343 cx.spawn_in(window, async move |this, cx| {
7344 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7345 this.update(cx, |this, cx| {
7346 this.show_cursor_names = false;
7347 cx.notify()
7348 })
7349 .ok()
7350 })
7351 .detach();
7352 }
7353
7354 pub fn next_edit_prediction(
7355 &mut self,
7356 _: &NextEditPrediction,
7357 window: &mut Window,
7358 cx: &mut Context<Self>,
7359 ) {
7360 if self.has_active_edit_prediction() {
7361 self.cycle_edit_prediction(Direction::Next, window, cx);
7362 } else {
7363 let is_copilot_disabled = self
7364 .refresh_edit_prediction(false, true, window, cx)
7365 .is_none();
7366 if is_copilot_disabled {
7367 cx.propagate();
7368 }
7369 }
7370 }
7371
7372 pub fn previous_edit_prediction(
7373 &mut self,
7374 _: &PreviousEditPrediction,
7375 window: &mut Window,
7376 cx: &mut Context<Self>,
7377 ) {
7378 if self.has_active_edit_prediction() {
7379 self.cycle_edit_prediction(Direction::Prev, window, cx);
7380 } else {
7381 let is_copilot_disabled = self
7382 .refresh_edit_prediction(false, true, window, cx)
7383 .is_none();
7384 if is_copilot_disabled {
7385 cx.propagate();
7386 }
7387 }
7388 }
7389
7390 pub fn accept_edit_prediction(
7391 &mut self,
7392 _: &AcceptEditPrediction,
7393 window: &mut Window,
7394 cx: &mut Context<Self>,
7395 ) {
7396 if self.show_edit_predictions_in_menu() {
7397 self.hide_context_menu(window, cx);
7398 }
7399
7400 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7401 return;
7402 };
7403
7404 match &active_edit_prediction.completion {
7405 EditPrediction::MoveWithin { target, .. } => {
7406 let target = *target;
7407
7408 if let Some(position_map) = &self.last_position_map {
7409 if position_map
7410 .visible_row_range
7411 .contains(&target.to_display_point(&position_map.snapshot).row())
7412 || !self.edit_prediction_requires_modifier()
7413 {
7414 self.unfold_ranges(&[target..target], true, false, cx);
7415 // Note that this is also done in vim's handler of the Tab action.
7416 self.change_selections(
7417 SelectionEffects::scroll(Autoscroll::newest()),
7418 window,
7419 cx,
7420 |selections| {
7421 selections.select_anchor_ranges([target..target]);
7422 },
7423 );
7424 self.clear_row_highlights::<EditPredictionPreview>();
7425
7426 self.edit_prediction_preview
7427 .set_previous_scroll_position(None);
7428 } else {
7429 self.edit_prediction_preview
7430 .set_previous_scroll_position(Some(
7431 position_map.snapshot.scroll_anchor,
7432 ));
7433
7434 self.highlight_rows::<EditPredictionPreview>(
7435 target..target,
7436 cx.theme().colors().editor_highlighted_line_background,
7437 RowHighlightOptions {
7438 autoscroll: true,
7439 ..Default::default()
7440 },
7441 cx,
7442 );
7443 self.request_autoscroll(Autoscroll::fit(), cx);
7444 }
7445 }
7446 }
7447 EditPrediction::MoveOutside { snapshot, target } => {
7448 if let Some(workspace) = self.workspace() {
7449 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7450 .detach_and_log_err(cx);
7451 }
7452 }
7453 EditPrediction::Edit { edits, .. } => {
7454 self.report_edit_prediction_event(
7455 active_edit_prediction.completion_id.clone(),
7456 true,
7457 cx,
7458 );
7459
7460 if let Some(provider) = self.edit_prediction_provider() {
7461 provider.accept(cx);
7462 }
7463
7464 // Store the transaction ID and selections before applying the edit
7465 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7466
7467 let snapshot = self.buffer.read(cx).snapshot(cx);
7468 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7469
7470 self.buffer.update(cx, |buffer, cx| {
7471 buffer.edit(edits.iter().cloned(), None, cx)
7472 });
7473
7474 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7475 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7476 });
7477
7478 let selections = self.selections.disjoint_anchors_arc();
7479 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7480 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7481 if has_new_transaction {
7482 self.selection_history
7483 .insert_transaction(transaction_id_now, selections);
7484 }
7485 }
7486
7487 self.update_visible_edit_prediction(window, cx);
7488 if self.active_edit_prediction.is_none() {
7489 self.refresh_edit_prediction(true, true, window, cx);
7490 }
7491
7492 cx.notify();
7493 }
7494 }
7495
7496 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7497 }
7498
7499 pub fn accept_partial_edit_prediction(
7500 &mut self,
7501 _: &AcceptPartialEditPrediction,
7502 window: &mut Window,
7503 cx: &mut Context<Self>,
7504 ) {
7505 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7506 return;
7507 };
7508 if self.selections.count() != 1 {
7509 return;
7510 }
7511
7512 match &active_edit_prediction.completion {
7513 EditPrediction::MoveWithin { target, .. } => {
7514 let target = *target;
7515 self.change_selections(
7516 SelectionEffects::scroll(Autoscroll::newest()),
7517 window,
7518 cx,
7519 |selections| {
7520 selections.select_anchor_ranges([target..target]);
7521 },
7522 );
7523 }
7524 EditPrediction::MoveOutside { snapshot, target } => {
7525 if let Some(workspace) = self.workspace() {
7526 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7527 .detach_and_log_err(cx);
7528 }
7529 }
7530 EditPrediction::Edit { edits, .. } => {
7531 self.report_edit_prediction_event(
7532 active_edit_prediction.completion_id.clone(),
7533 true,
7534 cx,
7535 );
7536
7537 // Find an insertion that starts at the cursor position.
7538 let snapshot = self.buffer.read(cx).snapshot(cx);
7539 let cursor_offset = self
7540 .selections
7541 .newest::<usize>(&self.display_snapshot(cx))
7542 .head();
7543 let insertion = edits.iter().find_map(|(range, text)| {
7544 let range = range.to_offset(&snapshot);
7545 if range.is_empty() && range.start == cursor_offset {
7546 Some(text)
7547 } else {
7548 None
7549 }
7550 });
7551
7552 if let Some(text) = insertion {
7553 let mut partial_completion = text
7554 .chars()
7555 .by_ref()
7556 .take_while(|c| c.is_alphabetic())
7557 .collect::<String>();
7558 if partial_completion.is_empty() {
7559 partial_completion = text
7560 .chars()
7561 .by_ref()
7562 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7563 .collect::<String>();
7564 }
7565
7566 cx.emit(EditorEvent::InputHandled {
7567 utf16_range_to_replace: None,
7568 text: partial_completion.clone().into(),
7569 });
7570
7571 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7572
7573 self.refresh_edit_prediction(true, true, window, cx);
7574 cx.notify();
7575 } else {
7576 self.accept_edit_prediction(&Default::default(), window, cx);
7577 }
7578 }
7579 }
7580 }
7581
7582 fn discard_edit_prediction(
7583 &mut self,
7584 should_report_edit_prediction_event: bool,
7585 cx: &mut Context<Self>,
7586 ) -> bool {
7587 if should_report_edit_prediction_event {
7588 let completion_id = self
7589 .active_edit_prediction
7590 .as_ref()
7591 .and_then(|active_completion| active_completion.completion_id.clone());
7592
7593 self.report_edit_prediction_event(completion_id, false, cx);
7594 }
7595
7596 if let Some(provider) = self.edit_prediction_provider() {
7597 provider.discard(cx);
7598 }
7599
7600 self.take_active_edit_prediction(cx)
7601 }
7602
7603 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7604 let Some(provider) = self.edit_prediction_provider() else {
7605 return;
7606 };
7607
7608 let Some((_, buffer, _)) = self
7609 .buffer
7610 .read(cx)
7611 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7612 else {
7613 return;
7614 };
7615
7616 let extension = buffer
7617 .read(cx)
7618 .file()
7619 .and_then(|file| Some(file.path().extension()?.to_string()));
7620
7621 let event_type = match accepted {
7622 true => "Edit Prediction Accepted",
7623 false => "Edit Prediction Discarded",
7624 };
7625 telemetry::event!(
7626 event_type,
7627 provider = provider.name(),
7628 prediction_id = id,
7629 suggestion_accepted = accepted,
7630 file_extension = extension,
7631 );
7632 }
7633
7634 fn open_editor_at_anchor(
7635 snapshot: &language::BufferSnapshot,
7636 target: language::Anchor,
7637 workspace: &Entity<Workspace>,
7638 window: &mut Window,
7639 cx: &mut App,
7640 ) -> Task<Result<()>> {
7641 workspace.update(cx, |workspace, cx| {
7642 let path = snapshot.file().map(|file| file.full_path(cx));
7643 let Some(path) =
7644 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7645 else {
7646 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7647 };
7648 let target = text::ToPoint::to_point(&target, snapshot);
7649 let item = workspace.open_path(path, None, true, window, cx);
7650 window.spawn(cx, async move |cx| {
7651 let Some(editor) = item.await?.downcast::<Editor>() else {
7652 return Ok(());
7653 };
7654 editor
7655 .update_in(cx, |editor, window, cx| {
7656 editor.go_to_singleton_buffer_point(target, window, cx);
7657 })
7658 .ok();
7659 anyhow::Ok(())
7660 })
7661 })
7662 }
7663
7664 pub fn has_active_edit_prediction(&self) -> bool {
7665 self.active_edit_prediction.is_some()
7666 }
7667
7668 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7669 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7670 return false;
7671 };
7672
7673 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7674 self.clear_highlights::<EditPredictionHighlight>(cx);
7675 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7676 true
7677 }
7678
7679 /// Returns true when we're displaying the edit prediction popover below the cursor
7680 /// like we are not previewing and the LSP autocomplete menu is visible
7681 /// or we are in `when_holding_modifier` mode.
7682 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7683 if self.edit_prediction_preview_is_active()
7684 || !self.show_edit_predictions_in_menu()
7685 || !self.edit_predictions_enabled()
7686 {
7687 return false;
7688 }
7689
7690 if self.has_visible_completions_menu() {
7691 return true;
7692 }
7693
7694 has_completion && self.edit_prediction_requires_modifier()
7695 }
7696
7697 fn handle_modifiers_changed(
7698 &mut self,
7699 modifiers: Modifiers,
7700 position_map: &PositionMap,
7701 window: &mut Window,
7702 cx: &mut Context<Self>,
7703 ) {
7704 // Ensure that the edit prediction preview is updated, even when not
7705 // enabled, if there's an active edit prediction preview.
7706 if self.show_edit_predictions_in_menu()
7707 || matches!(
7708 self.edit_prediction_preview,
7709 EditPredictionPreview::Active { .. }
7710 )
7711 {
7712 self.update_edit_prediction_preview(&modifiers, window, cx);
7713 }
7714
7715 self.update_selection_mode(&modifiers, position_map, window, cx);
7716
7717 let mouse_position = window.mouse_position();
7718 if !position_map.text_hitbox.is_hovered(window) {
7719 return;
7720 }
7721
7722 self.update_hovered_link(
7723 position_map.point_for_position(mouse_position),
7724 &position_map.snapshot,
7725 modifiers,
7726 window,
7727 cx,
7728 )
7729 }
7730
7731 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7732 match EditorSettings::get_global(cx).multi_cursor_modifier {
7733 MultiCursorModifier::Alt => modifiers.secondary(),
7734 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7735 }
7736 }
7737
7738 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7739 match EditorSettings::get_global(cx).multi_cursor_modifier {
7740 MultiCursorModifier::Alt => modifiers.alt,
7741 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7742 }
7743 }
7744
7745 fn columnar_selection_mode(
7746 modifiers: &Modifiers,
7747 cx: &mut Context<Self>,
7748 ) -> Option<ColumnarMode> {
7749 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7750 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7751 Some(ColumnarMode::FromMouse)
7752 } else if Self::is_alt_pressed(modifiers, cx) {
7753 Some(ColumnarMode::FromSelection)
7754 } else {
7755 None
7756 }
7757 } else {
7758 None
7759 }
7760 }
7761
7762 fn update_selection_mode(
7763 &mut self,
7764 modifiers: &Modifiers,
7765 position_map: &PositionMap,
7766 window: &mut Window,
7767 cx: &mut Context<Self>,
7768 ) {
7769 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7770 return;
7771 };
7772 if self.selections.pending_anchor().is_none() {
7773 return;
7774 }
7775
7776 let mouse_position = window.mouse_position();
7777 let point_for_position = position_map.point_for_position(mouse_position);
7778 let position = point_for_position.previous_valid;
7779
7780 self.select(
7781 SelectPhase::BeginColumnar {
7782 position,
7783 reset: false,
7784 mode,
7785 goal_column: point_for_position.exact_unclipped.column(),
7786 },
7787 window,
7788 cx,
7789 );
7790 }
7791
7792 fn update_edit_prediction_preview(
7793 &mut self,
7794 modifiers: &Modifiers,
7795 window: &mut Window,
7796 cx: &mut Context<Self>,
7797 ) {
7798 let mut modifiers_held = false;
7799 if let Some(accept_keystroke) = self
7800 .accept_edit_prediction_keybind(false, window, cx)
7801 .keystroke()
7802 {
7803 modifiers_held = modifiers_held
7804 || (accept_keystroke.modifiers() == modifiers
7805 && accept_keystroke.modifiers().modified());
7806 };
7807 if let Some(accept_partial_keystroke) = self
7808 .accept_edit_prediction_keybind(true, window, cx)
7809 .keystroke()
7810 {
7811 modifiers_held = modifiers_held
7812 || (accept_partial_keystroke.modifiers() == modifiers
7813 && accept_partial_keystroke.modifiers().modified());
7814 }
7815
7816 if modifiers_held {
7817 if matches!(
7818 self.edit_prediction_preview,
7819 EditPredictionPreview::Inactive { .. }
7820 ) {
7821 self.edit_prediction_preview = EditPredictionPreview::Active {
7822 previous_scroll_position: None,
7823 since: Instant::now(),
7824 };
7825
7826 self.update_visible_edit_prediction(window, cx);
7827 cx.notify();
7828 }
7829 } else if let EditPredictionPreview::Active {
7830 previous_scroll_position,
7831 since,
7832 } = self.edit_prediction_preview
7833 {
7834 if let (Some(previous_scroll_position), Some(position_map)) =
7835 (previous_scroll_position, self.last_position_map.as_ref())
7836 {
7837 self.set_scroll_position(
7838 previous_scroll_position
7839 .scroll_position(&position_map.snapshot.display_snapshot),
7840 window,
7841 cx,
7842 );
7843 }
7844
7845 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7846 released_too_fast: since.elapsed() < Duration::from_millis(200),
7847 };
7848 self.clear_row_highlights::<EditPredictionPreview>();
7849 self.update_visible_edit_prediction(window, cx);
7850 cx.notify();
7851 }
7852 }
7853
7854 fn update_visible_edit_prediction(
7855 &mut self,
7856 _window: &mut Window,
7857 cx: &mut Context<Self>,
7858 ) -> Option<()> {
7859 if DisableAiSettings::get_global(cx).disable_ai {
7860 return None;
7861 }
7862
7863 if self.ime_transaction.is_some() {
7864 self.discard_edit_prediction(false, cx);
7865 return None;
7866 }
7867
7868 let selection = self.selections.newest_anchor();
7869 let cursor = selection.head();
7870 let multibuffer = self.buffer.read(cx).snapshot(cx);
7871 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7872 let excerpt_id = cursor.excerpt_id;
7873
7874 let show_in_menu = self.show_edit_predictions_in_menu();
7875 let completions_menu_has_precedence = !show_in_menu
7876 && (self.context_menu.borrow().is_some()
7877 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7878
7879 if completions_menu_has_precedence
7880 || !offset_selection.is_empty()
7881 || self
7882 .active_edit_prediction
7883 .as_ref()
7884 .is_some_and(|completion| {
7885 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7886 return false;
7887 };
7888 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7889 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7890 !invalidation_range.contains(&offset_selection.head())
7891 })
7892 {
7893 self.discard_edit_prediction(false, cx);
7894 return None;
7895 }
7896
7897 self.take_active_edit_prediction(cx);
7898 let Some(provider) = self.edit_prediction_provider() else {
7899 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7900 return None;
7901 };
7902
7903 let (buffer, cursor_buffer_position) =
7904 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7905
7906 self.edit_prediction_settings =
7907 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7908
7909 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7910
7911 if self.edit_prediction_indent_conflict {
7912 let cursor_point = cursor.to_point(&multibuffer);
7913
7914 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7915
7916 if let Some((_, indent)) = indents.iter().next()
7917 && indent.len == cursor_point.column
7918 {
7919 self.edit_prediction_indent_conflict = false;
7920 }
7921 }
7922
7923 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7924
7925 let (completion_id, edits, edit_preview) = match edit_prediction {
7926 edit_prediction::EditPrediction::Local {
7927 id,
7928 edits,
7929 edit_preview,
7930 } => (id, edits, edit_preview),
7931 edit_prediction::EditPrediction::Jump {
7932 id,
7933 snapshot,
7934 target,
7935 } => {
7936 self.stale_edit_prediction_in_menu = None;
7937 self.active_edit_prediction = Some(EditPredictionState {
7938 inlay_ids: vec![],
7939 completion: EditPrediction::MoveOutside { snapshot, target },
7940 completion_id: id,
7941 invalidation_range: None,
7942 });
7943 cx.notify();
7944 return Some(());
7945 }
7946 };
7947
7948 let edits = edits
7949 .into_iter()
7950 .flat_map(|(range, new_text)| {
7951 Some((
7952 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7953 new_text,
7954 ))
7955 })
7956 .collect::<Vec<_>>();
7957 if edits.is_empty() {
7958 return None;
7959 }
7960
7961 let first_edit_start = edits.first().unwrap().0.start;
7962 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7963 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7964
7965 let last_edit_end = edits.last().unwrap().0.end;
7966 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7967 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7968
7969 let cursor_row = cursor.to_point(&multibuffer).row;
7970
7971 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7972
7973 let mut inlay_ids = Vec::new();
7974 let invalidation_row_range;
7975 let move_invalidation_row_range = if cursor_row < edit_start_row {
7976 Some(cursor_row..edit_end_row)
7977 } else if cursor_row > edit_end_row {
7978 Some(edit_start_row..cursor_row)
7979 } else {
7980 None
7981 };
7982 let supports_jump = self
7983 .edit_prediction_provider
7984 .as_ref()
7985 .map(|provider| provider.provider.supports_jump_to_edit())
7986 .unwrap_or(true);
7987
7988 let is_move = supports_jump
7989 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7990 let completion = if is_move {
7991 invalidation_row_range =
7992 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7993 let target = first_edit_start;
7994 EditPrediction::MoveWithin { target, snapshot }
7995 } else {
7996 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7997 && !self.edit_predictions_hidden_for_vim_mode;
7998
7999 if show_completions_in_buffer {
8000 if edits
8001 .iter()
8002 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8003 {
8004 let mut inlays = Vec::new();
8005 for (range, new_text) in &edits {
8006 let inlay = Inlay::edit_prediction(
8007 post_inc(&mut self.next_inlay_id),
8008 range.start,
8009 new_text.as_ref(),
8010 );
8011 inlay_ids.push(inlay.id);
8012 inlays.push(inlay);
8013 }
8014
8015 self.splice_inlays(&[], inlays, cx);
8016 } else {
8017 let background_color = cx.theme().status().deleted_background;
8018 self.highlight_text::<EditPredictionHighlight>(
8019 edits.iter().map(|(range, _)| range.clone()).collect(),
8020 HighlightStyle {
8021 background_color: Some(background_color),
8022 ..Default::default()
8023 },
8024 cx,
8025 );
8026 }
8027 }
8028
8029 invalidation_row_range = edit_start_row..edit_end_row;
8030
8031 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8032 if provider.show_tab_accept_marker() {
8033 EditDisplayMode::TabAccept
8034 } else {
8035 EditDisplayMode::Inline
8036 }
8037 } else {
8038 EditDisplayMode::DiffPopover
8039 };
8040
8041 EditPrediction::Edit {
8042 edits,
8043 edit_preview,
8044 display_mode,
8045 snapshot,
8046 }
8047 };
8048
8049 let invalidation_range = multibuffer
8050 .anchor_before(Point::new(invalidation_row_range.start, 0))
8051 ..multibuffer.anchor_after(Point::new(
8052 invalidation_row_range.end,
8053 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8054 ));
8055
8056 self.stale_edit_prediction_in_menu = None;
8057 self.active_edit_prediction = Some(EditPredictionState {
8058 inlay_ids,
8059 completion,
8060 completion_id,
8061 invalidation_range: Some(invalidation_range),
8062 });
8063
8064 cx.notify();
8065
8066 Some(())
8067 }
8068
8069 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8070 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8071 }
8072
8073 fn clear_tasks(&mut self) {
8074 self.tasks.clear()
8075 }
8076
8077 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8078 if self.tasks.insert(key, value).is_some() {
8079 // This case should hopefully be rare, but just in case...
8080 log::error!(
8081 "multiple different run targets found on a single line, only the last target will be rendered"
8082 )
8083 }
8084 }
8085
8086 /// Get all display points of breakpoints that will be rendered within editor
8087 ///
8088 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8089 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8090 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8091 fn active_breakpoints(
8092 &self,
8093 range: Range<DisplayRow>,
8094 window: &mut Window,
8095 cx: &mut Context<Self>,
8096 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8097 let mut breakpoint_display_points = HashMap::default();
8098
8099 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8100 return breakpoint_display_points;
8101 };
8102
8103 let snapshot = self.snapshot(window, cx);
8104
8105 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8106 let Some(project) = self.project() else {
8107 return breakpoint_display_points;
8108 };
8109
8110 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8111 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8112
8113 for (buffer_snapshot, range, excerpt_id) in
8114 multi_buffer_snapshot.range_to_buffer_ranges(range)
8115 {
8116 let Some(buffer) = project
8117 .read(cx)
8118 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8119 else {
8120 continue;
8121 };
8122 let breakpoints = breakpoint_store.read(cx).breakpoints(
8123 &buffer,
8124 Some(
8125 buffer_snapshot.anchor_before(range.start)
8126 ..buffer_snapshot.anchor_after(range.end),
8127 ),
8128 buffer_snapshot,
8129 cx,
8130 );
8131 for (breakpoint, state) in breakpoints {
8132 let multi_buffer_anchor =
8133 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8134 let position = multi_buffer_anchor
8135 .to_point(&multi_buffer_snapshot)
8136 .to_display_point(&snapshot);
8137
8138 breakpoint_display_points.insert(
8139 position.row(),
8140 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8141 );
8142 }
8143 }
8144
8145 breakpoint_display_points
8146 }
8147
8148 fn breakpoint_context_menu(
8149 &self,
8150 anchor: Anchor,
8151 window: &mut Window,
8152 cx: &mut Context<Self>,
8153 ) -> Entity<ui::ContextMenu> {
8154 let weak_editor = cx.weak_entity();
8155 let focus_handle = self.focus_handle(cx);
8156
8157 let row = self
8158 .buffer
8159 .read(cx)
8160 .snapshot(cx)
8161 .summary_for_anchor::<Point>(&anchor)
8162 .row;
8163
8164 let breakpoint = self
8165 .breakpoint_at_row(row, window, cx)
8166 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8167
8168 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8169 "Edit Log Breakpoint"
8170 } else {
8171 "Set Log Breakpoint"
8172 };
8173
8174 let condition_breakpoint_msg = if breakpoint
8175 .as_ref()
8176 .is_some_and(|bp| bp.1.condition.is_some())
8177 {
8178 "Edit Condition Breakpoint"
8179 } else {
8180 "Set Condition Breakpoint"
8181 };
8182
8183 let hit_condition_breakpoint_msg = if breakpoint
8184 .as_ref()
8185 .is_some_and(|bp| bp.1.hit_condition.is_some())
8186 {
8187 "Edit Hit Condition Breakpoint"
8188 } else {
8189 "Set Hit Condition Breakpoint"
8190 };
8191
8192 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8193 "Unset Breakpoint"
8194 } else {
8195 "Set Breakpoint"
8196 };
8197
8198 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8199
8200 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8201 BreakpointState::Enabled => Some("Disable"),
8202 BreakpointState::Disabled => Some("Enable"),
8203 });
8204
8205 let (anchor, breakpoint) =
8206 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8207
8208 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8209 menu.on_blur_subscription(Subscription::new(|| {}))
8210 .context(focus_handle)
8211 .when(run_to_cursor, |this| {
8212 let weak_editor = weak_editor.clone();
8213 this.entry("Run to cursor", None, move |window, cx| {
8214 weak_editor
8215 .update(cx, |editor, cx| {
8216 editor.change_selections(
8217 SelectionEffects::no_scroll(),
8218 window,
8219 cx,
8220 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8221 );
8222 })
8223 .ok();
8224
8225 window.dispatch_action(Box::new(RunToCursor), cx);
8226 })
8227 .separator()
8228 })
8229 .when_some(toggle_state_msg, |this, msg| {
8230 this.entry(msg, None, {
8231 let weak_editor = weak_editor.clone();
8232 let breakpoint = breakpoint.clone();
8233 move |_window, cx| {
8234 weak_editor
8235 .update(cx, |this, cx| {
8236 this.edit_breakpoint_at_anchor(
8237 anchor,
8238 breakpoint.as_ref().clone(),
8239 BreakpointEditAction::InvertState,
8240 cx,
8241 );
8242 })
8243 .log_err();
8244 }
8245 })
8246 })
8247 .entry(set_breakpoint_msg, None, {
8248 let weak_editor = weak_editor.clone();
8249 let breakpoint = breakpoint.clone();
8250 move |_window, cx| {
8251 weak_editor
8252 .update(cx, |this, cx| {
8253 this.edit_breakpoint_at_anchor(
8254 anchor,
8255 breakpoint.as_ref().clone(),
8256 BreakpointEditAction::Toggle,
8257 cx,
8258 );
8259 })
8260 .log_err();
8261 }
8262 })
8263 .entry(log_breakpoint_msg, None, {
8264 let breakpoint = breakpoint.clone();
8265 let weak_editor = weak_editor.clone();
8266 move |window, cx| {
8267 weak_editor
8268 .update(cx, |this, cx| {
8269 this.add_edit_breakpoint_block(
8270 anchor,
8271 breakpoint.as_ref(),
8272 BreakpointPromptEditAction::Log,
8273 window,
8274 cx,
8275 );
8276 })
8277 .log_err();
8278 }
8279 })
8280 .entry(condition_breakpoint_msg, None, {
8281 let breakpoint = breakpoint.clone();
8282 let weak_editor = weak_editor.clone();
8283 move |window, cx| {
8284 weak_editor
8285 .update(cx, |this, cx| {
8286 this.add_edit_breakpoint_block(
8287 anchor,
8288 breakpoint.as_ref(),
8289 BreakpointPromptEditAction::Condition,
8290 window,
8291 cx,
8292 );
8293 })
8294 .log_err();
8295 }
8296 })
8297 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8298 weak_editor
8299 .update(cx, |this, cx| {
8300 this.add_edit_breakpoint_block(
8301 anchor,
8302 breakpoint.as_ref(),
8303 BreakpointPromptEditAction::HitCondition,
8304 window,
8305 cx,
8306 );
8307 })
8308 .log_err();
8309 })
8310 })
8311 }
8312
8313 fn render_breakpoint(
8314 &self,
8315 position: Anchor,
8316 row: DisplayRow,
8317 breakpoint: &Breakpoint,
8318 state: Option<BreakpointSessionState>,
8319 cx: &mut Context<Self>,
8320 ) -> IconButton {
8321 let is_rejected = state.is_some_and(|s| !s.verified);
8322 // Is it a breakpoint that shows up when hovering over gutter?
8323 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8324 (false, false),
8325 |PhantomBreakpointIndicator {
8326 is_active,
8327 display_row,
8328 collides_with_existing_breakpoint,
8329 }| {
8330 (
8331 is_active && display_row == row,
8332 collides_with_existing_breakpoint,
8333 )
8334 },
8335 );
8336
8337 let (color, icon) = {
8338 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8339 (false, false) => ui::IconName::DebugBreakpoint,
8340 (true, false) => ui::IconName::DebugLogBreakpoint,
8341 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8342 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8343 };
8344
8345 let color = if is_phantom {
8346 Color::Hint
8347 } else if is_rejected {
8348 Color::Disabled
8349 } else {
8350 Color::Debugger
8351 };
8352
8353 (color, icon)
8354 };
8355
8356 let breakpoint = Arc::from(breakpoint.clone());
8357
8358 let alt_as_text = gpui::Keystroke {
8359 modifiers: Modifiers::secondary_key(),
8360 ..Default::default()
8361 };
8362 let primary_action_text = if breakpoint.is_disabled() {
8363 "Enable breakpoint"
8364 } else if is_phantom && !collides_with_existing {
8365 "Set breakpoint"
8366 } else {
8367 "Unset breakpoint"
8368 };
8369 let focus_handle = self.focus_handle.clone();
8370
8371 let meta = if is_rejected {
8372 SharedString::from("No executable code is associated with this line.")
8373 } else if collides_with_existing && !breakpoint.is_disabled() {
8374 SharedString::from(format!(
8375 "{alt_as_text}-click to disable,\nright-click for more options."
8376 ))
8377 } else {
8378 SharedString::from("Right-click for more options.")
8379 };
8380 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8381 .icon_size(IconSize::XSmall)
8382 .size(ui::ButtonSize::None)
8383 .when(is_rejected, |this| {
8384 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8385 })
8386 .icon_color(color)
8387 .style(ButtonStyle::Transparent)
8388 .on_click(cx.listener({
8389 move |editor, event: &ClickEvent, window, cx| {
8390 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8391 BreakpointEditAction::InvertState
8392 } else {
8393 BreakpointEditAction::Toggle
8394 };
8395
8396 window.focus(&editor.focus_handle(cx));
8397 editor.edit_breakpoint_at_anchor(
8398 position,
8399 breakpoint.as_ref().clone(),
8400 edit_action,
8401 cx,
8402 );
8403 }
8404 }))
8405 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8406 editor.set_breakpoint_context_menu(
8407 row,
8408 Some(position),
8409 event.position(),
8410 window,
8411 cx,
8412 );
8413 }))
8414 .tooltip(move |_window, cx| {
8415 Tooltip::with_meta_in(
8416 primary_action_text,
8417 Some(&ToggleBreakpoint),
8418 meta.clone(),
8419 &focus_handle,
8420 cx,
8421 )
8422 })
8423 }
8424
8425 fn build_tasks_context(
8426 project: &Entity<Project>,
8427 buffer: &Entity<Buffer>,
8428 buffer_row: u32,
8429 tasks: &Arc<RunnableTasks>,
8430 cx: &mut Context<Self>,
8431 ) -> Task<Option<task::TaskContext>> {
8432 let position = Point::new(buffer_row, tasks.column);
8433 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8434 let location = Location {
8435 buffer: buffer.clone(),
8436 range: range_start..range_start,
8437 };
8438 // Fill in the environmental variables from the tree-sitter captures
8439 let mut captured_task_variables = TaskVariables::default();
8440 for (capture_name, value) in tasks.extra_variables.clone() {
8441 captured_task_variables.insert(
8442 task::VariableName::Custom(capture_name.into()),
8443 value.clone(),
8444 );
8445 }
8446 project.update(cx, |project, cx| {
8447 project.task_store().update(cx, |task_store, cx| {
8448 task_store.task_context_for_location(captured_task_variables, location, cx)
8449 })
8450 })
8451 }
8452
8453 pub fn spawn_nearest_task(
8454 &mut self,
8455 action: &SpawnNearestTask,
8456 window: &mut Window,
8457 cx: &mut Context<Self>,
8458 ) {
8459 let Some((workspace, _)) = self.workspace.clone() else {
8460 return;
8461 };
8462 let Some(project) = self.project.clone() else {
8463 return;
8464 };
8465
8466 // Try to find a closest, enclosing node using tree-sitter that has a task
8467 let Some((buffer, buffer_row, tasks)) = self
8468 .find_enclosing_node_task(cx)
8469 // Or find the task that's closest in row-distance.
8470 .or_else(|| self.find_closest_task(cx))
8471 else {
8472 return;
8473 };
8474
8475 let reveal_strategy = action.reveal;
8476 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8477 cx.spawn_in(window, async move |_, cx| {
8478 let context = task_context.await?;
8479 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8480
8481 let resolved = &mut resolved_task.resolved;
8482 resolved.reveal = reveal_strategy;
8483
8484 workspace
8485 .update_in(cx, |workspace, window, cx| {
8486 workspace.schedule_resolved_task(
8487 task_source_kind,
8488 resolved_task,
8489 false,
8490 window,
8491 cx,
8492 );
8493 })
8494 .ok()
8495 })
8496 .detach();
8497 }
8498
8499 fn find_closest_task(
8500 &mut self,
8501 cx: &mut Context<Self>,
8502 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8503 let cursor_row = self
8504 .selections
8505 .newest_adjusted(&self.display_snapshot(cx))
8506 .head()
8507 .row;
8508
8509 let ((buffer_id, row), tasks) = self
8510 .tasks
8511 .iter()
8512 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8513
8514 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8515 let tasks = Arc::new(tasks.to_owned());
8516 Some((buffer, *row, tasks))
8517 }
8518
8519 fn find_enclosing_node_task(
8520 &mut self,
8521 cx: &mut Context<Self>,
8522 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8523 let snapshot = self.buffer.read(cx).snapshot(cx);
8524 let offset = self
8525 .selections
8526 .newest::<usize>(&self.display_snapshot(cx))
8527 .head();
8528 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8529 let buffer_id = excerpt.buffer().remote_id();
8530
8531 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8532 let mut cursor = layer.node().walk();
8533
8534 while cursor.goto_first_child_for_byte(offset).is_some() {
8535 if cursor.node().end_byte() == offset {
8536 cursor.goto_next_sibling();
8537 }
8538 }
8539
8540 // Ascend to the smallest ancestor that contains the range and has a task.
8541 loop {
8542 let node = cursor.node();
8543 let node_range = node.byte_range();
8544 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8545
8546 // Check if this node contains our offset
8547 if node_range.start <= offset && node_range.end >= offset {
8548 // If it contains offset, check for task
8549 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8550 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8551 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8552 }
8553 }
8554
8555 if !cursor.goto_parent() {
8556 break;
8557 }
8558 }
8559 None
8560 }
8561
8562 fn render_run_indicator(
8563 &self,
8564 _style: &EditorStyle,
8565 is_active: bool,
8566 row: DisplayRow,
8567 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8568 cx: &mut Context<Self>,
8569 ) -> IconButton {
8570 let color = Color::Muted;
8571 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8572
8573 IconButton::new(
8574 ("run_indicator", row.0 as usize),
8575 ui::IconName::PlayOutlined,
8576 )
8577 .shape(ui::IconButtonShape::Square)
8578 .icon_size(IconSize::XSmall)
8579 .icon_color(color)
8580 .toggle_state(is_active)
8581 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8582 let quick_launch = match e {
8583 ClickEvent::Keyboard(_) => true,
8584 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8585 };
8586
8587 window.focus(&editor.focus_handle(cx));
8588 editor.toggle_code_actions(
8589 &ToggleCodeActions {
8590 deployed_from: Some(CodeActionSource::RunMenu(row)),
8591 quick_launch,
8592 },
8593 window,
8594 cx,
8595 );
8596 }))
8597 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8598 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8599 }))
8600 }
8601
8602 pub fn context_menu_visible(&self) -> bool {
8603 !self.edit_prediction_preview_is_active()
8604 && self
8605 .context_menu
8606 .borrow()
8607 .as_ref()
8608 .is_some_and(|menu| menu.visible())
8609 }
8610
8611 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8612 self.context_menu
8613 .borrow()
8614 .as_ref()
8615 .map(|menu| menu.origin())
8616 }
8617
8618 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8619 self.context_menu_options = Some(options);
8620 }
8621
8622 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8623 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8624
8625 fn render_edit_prediction_popover(
8626 &mut self,
8627 text_bounds: &Bounds<Pixels>,
8628 content_origin: gpui::Point<Pixels>,
8629 right_margin: Pixels,
8630 editor_snapshot: &EditorSnapshot,
8631 visible_row_range: Range<DisplayRow>,
8632 scroll_top: ScrollOffset,
8633 scroll_bottom: ScrollOffset,
8634 line_layouts: &[LineWithInvisibles],
8635 line_height: Pixels,
8636 scroll_position: gpui::Point<ScrollOffset>,
8637 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8638 newest_selection_head: Option<DisplayPoint>,
8639 editor_width: Pixels,
8640 style: &EditorStyle,
8641 window: &mut Window,
8642 cx: &mut App,
8643 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8644 if self.mode().is_minimap() {
8645 return None;
8646 }
8647 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8648
8649 if self.edit_prediction_visible_in_cursor_popover(true) {
8650 return None;
8651 }
8652
8653 match &active_edit_prediction.completion {
8654 EditPrediction::MoveWithin { target, .. } => {
8655 let target_display_point = target.to_display_point(editor_snapshot);
8656
8657 if self.edit_prediction_requires_modifier() {
8658 if !self.edit_prediction_preview_is_active() {
8659 return None;
8660 }
8661
8662 self.render_edit_prediction_modifier_jump_popover(
8663 text_bounds,
8664 content_origin,
8665 visible_row_range,
8666 line_layouts,
8667 line_height,
8668 scroll_pixel_position,
8669 newest_selection_head,
8670 target_display_point,
8671 window,
8672 cx,
8673 )
8674 } else {
8675 self.render_edit_prediction_eager_jump_popover(
8676 text_bounds,
8677 content_origin,
8678 editor_snapshot,
8679 visible_row_range,
8680 scroll_top,
8681 scroll_bottom,
8682 line_height,
8683 scroll_pixel_position,
8684 target_display_point,
8685 editor_width,
8686 window,
8687 cx,
8688 )
8689 }
8690 }
8691 EditPrediction::Edit {
8692 display_mode: EditDisplayMode::Inline,
8693 ..
8694 } => None,
8695 EditPrediction::Edit {
8696 display_mode: EditDisplayMode::TabAccept,
8697 edits,
8698 ..
8699 } => {
8700 let range = &edits.first()?.0;
8701 let target_display_point = range.end.to_display_point(editor_snapshot);
8702
8703 self.render_edit_prediction_end_of_line_popover(
8704 "Accept",
8705 editor_snapshot,
8706 visible_row_range,
8707 target_display_point,
8708 line_height,
8709 scroll_pixel_position,
8710 content_origin,
8711 editor_width,
8712 window,
8713 cx,
8714 )
8715 }
8716 EditPrediction::Edit {
8717 edits,
8718 edit_preview,
8719 display_mode: EditDisplayMode::DiffPopover,
8720 snapshot,
8721 } => self.render_edit_prediction_diff_popover(
8722 text_bounds,
8723 content_origin,
8724 right_margin,
8725 editor_snapshot,
8726 visible_row_range,
8727 line_layouts,
8728 line_height,
8729 scroll_position,
8730 scroll_pixel_position,
8731 newest_selection_head,
8732 editor_width,
8733 style,
8734 edits,
8735 edit_preview,
8736 snapshot,
8737 window,
8738 cx,
8739 ),
8740 EditPrediction::MoveOutside { snapshot, .. } => {
8741 let file_name = snapshot
8742 .file()
8743 .map(|file| file.file_name(cx))
8744 .unwrap_or("untitled");
8745 let mut element = self
8746 .render_edit_prediction_line_popover(
8747 format!("Jump to {file_name}"),
8748 Some(IconName::ZedPredict),
8749 window,
8750 cx,
8751 )
8752 .into_any();
8753
8754 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8755 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8756 let origin_y = text_bounds.size.height - size.height - px(30.);
8757 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8758 element.prepaint_at(origin, window, cx);
8759
8760 Some((element, origin))
8761 }
8762 }
8763 }
8764
8765 fn render_edit_prediction_modifier_jump_popover(
8766 &mut self,
8767 text_bounds: &Bounds<Pixels>,
8768 content_origin: gpui::Point<Pixels>,
8769 visible_row_range: Range<DisplayRow>,
8770 line_layouts: &[LineWithInvisibles],
8771 line_height: Pixels,
8772 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8773 newest_selection_head: Option<DisplayPoint>,
8774 target_display_point: DisplayPoint,
8775 window: &mut Window,
8776 cx: &mut App,
8777 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8778 let scrolled_content_origin =
8779 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8780
8781 const SCROLL_PADDING_Y: Pixels = px(12.);
8782
8783 if target_display_point.row() < visible_row_range.start {
8784 return self.render_edit_prediction_scroll_popover(
8785 |_| SCROLL_PADDING_Y,
8786 IconName::ArrowUp,
8787 visible_row_range,
8788 line_layouts,
8789 newest_selection_head,
8790 scrolled_content_origin,
8791 window,
8792 cx,
8793 );
8794 } else if target_display_point.row() >= visible_row_range.end {
8795 return self.render_edit_prediction_scroll_popover(
8796 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8797 IconName::ArrowDown,
8798 visible_row_range,
8799 line_layouts,
8800 newest_selection_head,
8801 scrolled_content_origin,
8802 window,
8803 cx,
8804 );
8805 }
8806
8807 const POLE_WIDTH: Pixels = px(2.);
8808
8809 let line_layout =
8810 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8811 let target_column = target_display_point.column() as usize;
8812
8813 let target_x = line_layout.x_for_index(target_column);
8814 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8815 - scroll_pixel_position.y;
8816
8817 let flag_on_right = target_x < text_bounds.size.width / 2.;
8818
8819 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8820 border_color.l += 0.001;
8821
8822 let mut element = v_flex()
8823 .items_end()
8824 .when(flag_on_right, |el| el.items_start())
8825 .child(if flag_on_right {
8826 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8827 .rounded_bl(px(0.))
8828 .rounded_tl(px(0.))
8829 .border_l_2()
8830 .border_color(border_color)
8831 } else {
8832 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8833 .rounded_br(px(0.))
8834 .rounded_tr(px(0.))
8835 .border_r_2()
8836 .border_color(border_color)
8837 })
8838 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8839 .into_any();
8840
8841 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8842
8843 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8844 - point(
8845 if flag_on_right {
8846 POLE_WIDTH
8847 } else {
8848 size.width - POLE_WIDTH
8849 },
8850 size.height - line_height,
8851 );
8852
8853 origin.x = origin.x.max(content_origin.x);
8854
8855 element.prepaint_at(origin, window, cx);
8856
8857 Some((element, origin))
8858 }
8859
8860 fn render_edit_prediction_scroll_popover(
8861 &mut self,
8862 to_y: impl Fn(Size<Pixels>) -> Pixels,
8863 scroll_icon: IconName,
8864 visible_row_range: Range<DisplayRow>,
8865 line_layouts: &[LineWithInvisibles],
8866 newest_selection_head: Option<DisplayPoint>,
8867 scrolled_content_origin: gpui::Point<Pixels>,
8868 window: &mut Window,
8869 cx: &mut App,
8870 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8871 let mut element = self
8872 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8873 .into_any();
8874
8875 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8876
8877 let cursor = newest_selection_head?;
8878 let cursor_row_layout =
8879 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8880 let cursor_column = cursor.column() as usize;
8881
8882 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8883
8884 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8885
8886 element.prepaint_at(origin, window, cx);
8887 Some((element, origin))
8888 }
8889
8890 fn render_edit_prediction_eager_jump_popover(
8891 &mut self,
8892 text_bounds: &Bounds<Pixels>,
8893 content_origin: gpui::Point<Pixels>,
8894 editor_snapshot: &EditorSnapshot,
8895 visible_row_range: Range<DisplayRow>,
8896 scroll_top: ScrollOffset,
8897 scroll_bottom: ScrollOffset,
8898 line_height: Pixels,
8899 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8900 target_display_point: DisplayPoint,
8901 editor_width: Pixels,
8902 window: &mut Window,
8903 cx: &mut App,
8904 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8905 if target_display_point.row().as_f64() < scroll_top {
8906 let mut element = self
8907 .render_edit_prediction_line_popover(
8908 "Jump to Edit",
8909 Some(IconName::ArrowUp),
8910 window,
8911 cx,
8912 )
8913 .into_any();
8914
8915 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8916 let offset = point(
8917 (text_bounds.size.width - size.width) / 2.,
8918 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8919 );
8920
8921 let origin = text_bounds.origin + offset;
8922 element.prepaint_at(origin, window, cx);
8923 Some((element, origin))
8924 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8925 let mut element = self
8926 .render_edit_prediction_line_popover(
8927 "Jump to Edit",
8928 Some(IconName::ArrowDown),
8929 window,
8930 cx,
8931 )
8932 .into_any();
8933
8934 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8935 let offset = point(
8936 (text_bounds.size.width - size.width) / 2.,
8937 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8938 );
8939
8940 let origin = text_bounds.origin + offset;
8941 element.prepaint_at(origin, window, cx);
8942 Some((element, origin))
8943 } else {
8944 self.render_edit_prediction_end_of_line_popover(
8945 "Jump to Edit",
8946 editor_snapshot,
8947 visible_row_range,
8948 target_display_point,
8949 line_height,
8950 scroll_pixel_position,
8951 content_origin,
8952 editor_width,
8953 window,
8954 cx,
8955 )
8956 }
8957 }
8958
8959 fn render_edit_prediction_end_of_line_popover(
8960 self: &mut Editor,
8961 label: &'static str,
8962 editor_snapshot: &EditorSnapshot,
8963 visible_row_range: Range<DisplayRow>,
8964 target_display_point: DisplayPoint,
8965 line_height: Pixels,
8966 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8967 content_origin: gpui::Point<Pixels>,
8968 editor_width: Pixels,
8969 window: &mut Window,
8970 cx: &mut App,
8971 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8972 let target_line_end = DisplayPoint::new(
8973 target_display_point.row(),
8974 editor_snapshot.line_len(target_display_point.row()),
8975 );
8976
8977 let mut element = self
8978 .render_edit_prediction_line_popover(label, None, window, cx)
8979 .into_any();
8980
8981 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8982
8983 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8984
8985 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8986 let mut origin = start_point
8987 + line_origin
8988 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8989 origin.x = origin.x.max(content_origin.x);
8990
8991 let max_x = content_origin.x + editor_width - size.width;
8992
8993 if origin.x > max_x {
8994 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8995
8996 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8997 origin.y += offset;
8998 IconName::ArrowUp
8999 } else {
9000 origin.y -= offset;
9001 IconName::ArrowDown
9002 };
9003
9004 element = self
9005 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9006 .into_any();
9007
9008 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9009
9010 origin.x = content_origin.x + editor_width - size.width - px(2.);
9011 }
9012
9013 element.prepaint_at(origin, window, cx);
9014 Some((element, origin))
9015 }
9016
9017 fn render_edit_prediction_diff_popover(
9018 self: &Editor,
9019 text_bounds: &Bounds<Pixels>,
9020 content_origin: gpui::Point<Pixels>,
9021 right_margin: Pixels,
9022 editor_snapshot: &EditorSnapshot,
9023 visible_row_range: Range<DisplayRow>,
9024 line_layouts: &[LineWithInvisibles],
9025 line_height: Pixels,
9026 scroll_position: gpui::Point<ScrollOffset>,
9027 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9028 newest_selection_head: Option<DisplayPoint>,
9029 editor_width: Pixels,
9030 style: &EditorStyle,
9031 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9032 edit_preview: &Option<language::EditPreview>,
9033 snapshot: &language::BufferSnapshot,
9034 window: &mut Window,
9035 cx: &mut App,
9036 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9037 let edit_start = edits
9038 .first()
9039 .unwrap()
9040 .0
9041 .start
9042 .to_display_point(editor_snapshot);
9043 let edit_end = edits
9044 .last()
9045 .unwrap()
9046 .0
9047 .end
9048 .to_display_point(editor_snapshot);
9049
9050 let is_visible = visible_row_range.contains(&edit_start.row())
9051 || visible_row_range.contains(&edit_end.row());
9052 if !is_visible {
9053 return None;
9054 }
9055
9056 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9057 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9058 } else {
9059 // Fallback for providers without edit_preview
9060 crate::edit_prediction_fallback_text(edits, cx)
9061 };
9062
9063 let styled_text = highlighted_edits.to_styled_text(&style.text);
9064 let line_count = highlighted_edits.text.lines().count();
9065
9066 const BORDER_WIDTH: Pixels = px(1.);
9067
9068 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9069 let has_keybind = keybind.is_some();
9070
9071 let mut element = h_flex()
9072 .items_start()
9073 .child(
9074 h_flex()
9075 .bg(cx.theme().colors().editor_background)
9076 .border(BORDER_WIDTH)
9077 .shadow_xs()
9078 .border_color(cx.theme().colors().border)
9079 .rounded_l_lg()
9080 .when(line_count > 1, |el| el.rounded_br_lg())
9081 .pr_1()
9082 .child(styled_text),
9083 )
9084 .child(
9085 h_flex()
9086 .h(line_height + BORDER_WIDTH * 2.)
9087 .px_1p5()
9088 .gap_1()
9089 // Workaround: For some reason, there's a gap if we don't do this
9090 .ml(-BORDER_WIDTH)
9091 .shadow(vec![gpui::BoxShadow {
9092 color: gpui::black().opacity(0.05),
9093 offset: point(px(1.), px(1.)),
9094 blur_radius: px(2.),
9095 spread_radius: px(0.),
9096 }])
9097 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9098 .border(BORDER_WIDTH)
9099 .border_color(cx.theme().colors().border)
9100 .rounded_r_lg()
9101 .id("edit_prediction_diff_popover_keybind")
9102 .when(!has_keybind, |el| {
9103 let status_colors = cx.theme().status();
9104
9105 el.bg(status_colors.error_background)
9106 .border_color(status_colors.error.opacity(0.6))
9107 .child(Icon::new(IconName::Info).color(Color::Error))
9108 .cursor_default()
9109 .hoverable_tooltip(move |_window, cx| {
9110 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9111 })
9112 })
9113 .children(keybind),
9114 )
9115 .into_any();
9116
9117 let longest_row =
9118 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9119 let longest_line_width = if visible_row_range.contains(&longest_row) {
9120 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9121 } else {
9122 layout_line(
9123 longest_row,
9124 editor_snapshot,
9125 style,
9126 editor_width,
9127 |_| false,
9128 window,
9129 cx,
9130 )
9131 .width
9132 };
9133
9134 let viewport_bounds =
9135 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9136 right: -right_margin,
9137 ..Default::default()
9138 });
9139
9140 let x_after_longest = Pixels::from(
9141 ScrollPixelOffset::from(
9142 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9143 ) - scroll_pixel_position.x,
9144 );
9145
9146 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9147
9148 // Fully visible if it can be displayed within the window (allow overlapping other
9149 // panes). However, this is only allowed if the popover starts within text_bounds.
9150 let can_position_to_the_right = x_after_longest < text_bounds.right()
9151 && x_after_longest + element_bounds.width < viewport_bounds.right();
9152
9153 let mut origin = if can_position_to_the_right {
9154 point(
9155 x_after_longest,
9156 text_bounds.origin.y
9157 + Pixels::from(
9158 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9159 - scroll_pixel_position.y,
9160 ),
9161 )
9162 } else {
9163 let cursor_row = newest_selection_head.map(|head| head.row());
9164 let above_edit = edit_start
9165 .row()
9166 .0
9167 .checked_sub(line_count as u32)
9168 .map(DisplayRow);
9169 let below_edit = Some(edit_end.row() + 1);
9170 let above_cursor =
9171 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9172 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9173
9174 // Place the edit popover adjacent to the edit if there is a location
9175 // available that is onscreen and does not obscure the cursor. Otherwise,
9176 // place it adjacent to the cursor.
9177 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9178 .into_iter()
9179 .flatten()
9180 .find(|&start_row| {
9181 let end_row = start_row + line_count as u32;
9182 visible_row_range.contains(&start_row)
9183 && visible_row_range.contains(&end_row)
9184 && cursor_row
9185 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9186 })?;
9187
9188 content_origin
9189 + point(
9190 Pixels::from(-scroll_pixel_position.x),
9191 Pixels::from(
9192 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9193 ),
9194 )
9195 };
9196
9197 origin.x -= BORDER_WIDTH;
9198
9199 window.defer_draw(element, origin, 1);
9200
9201 // Do not return an element, since it will already be drawn due to defer_draw.
9202 None
9203 }
9204
9205 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9206 px(30.)
9207 }
9208
9209 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9210 if self.read_only(cx) {
9211 cx.theme().players().read_only()
9212 } else {
9213 self.style.as_ref().unwrap().local_player
9214 }
9215 }
9216
9217 fn render_edit_prediction_accept_keybind(
9218 &self,
9219 window: &mut Window,
9220 cx: &mut App,
9221 ) -> Option<AnyElement> {
9222 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9223 let accept_keystroke = accept_binding.keystroke()?;
9224
9225 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9226
9227 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9228 Color::Accent
9229 } else {
9230 Color::Muted
9231 };
9232
9233 h_flex()
9234 .px_0p5()
9235 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9236 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9237 .text_size(TextSize::XSmall.rems(cx))
9238 .child(h_flex().children(ui::render_modifiers(
9239 accept_keystroke.modifiers(),
9240 PlatformStyle::platform(),
9241 Some(modifiers_color),
9242 Some(IconSize::XSmall.rems().into()),
9243 true,
9244 )))
9245 .when(is_platform_style_mac, |parent| {
9246 parent.child(accept_keystroke.key().to_string())
9247 })
9248 .when(!is_platform_style_mac, |parent| {
9249 parent.child(
9250 Key::new(
9251 util::capitalize(accept_keystroke.key()),
9252 Some(Color::Default),
9253 )
9254 .size(Some(IconSize::XSmall.rems().into())),
9255 )
9256 })
9257 .into_any()
9258 .into()
9259 }
9260
9261 fn render_edit_prediction_line_popover(
9262 &self,
9263 label: impl Into<SharedString>,
9264 icon: Option<IconName>,
9265 window: &mut Window,
9266 cx: &mut App,
9267 ) -> Stateful<Div> {
9268 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9269
9270 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9271 let has_keybind = keybind.is_some();
9272
9273 h_flex()
9274 .id("ep-line-popover")
9275 .py_0p5()
9276 .pl_1()
9277 .pr(padding_right)
9278 .gap_1()
9279 .rounded_md()
9280 .border_1()
9281 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9282 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9283 .shadow_xs()
9284 .when(!has_keybind, |el| {
9285 let status_colors = cx.theme().status();
9286
9287 el.bg(status_colors.error_background)
9288 .border_color(status_colors.error.opacity(0.6))
9289 .pl_2()
9290 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9291 .cursor_default()
9292 .hoverable_tooltip(move |_window, cx| {
9293 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9294 })
9295 })
9296 .children(keybind)
9297 .child(
9298 Label::new(label)
9299 .size(LabelSize::Small)
9300 .when(!has_keybind, |el| {
9301 el.color(cx.theme().status().error.into()).strikethrough()
9302 }),
9303 )
9304 .when(!has_keybind, |el| {
9305 el.child(
9306 h_flex().ml_1().child(
9307 Icon::new(IconName::Info)
9308 .size(IconSize::Small)
9309 .color(cx.theme().status().error.into()),
9310 ),
9311 )
9312 })
9313 .when_some(icon, |element, icon| {
9314 element.child(
9315 div()
9316 .mt(px(1.5))
9317 .child(Icon::new(icon).size(IconSize::Small)),
9318 )
9319 })
9320 }
9321
9322 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9323 let accent_color = cx.theme().colors().text_accent;
9324 let editor_bg_color = cx.theme().colors().editor_background;
9325 editor_bg_color.blend(accent_color.opacity(0.1))
9326 }
9327
9328 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9329 let accent_color = cx.theme().colors().text_accent;
9330 let editor_bg_color = cx.theme().colors().editor_background;
9331 editor_bg_color.blend(accent_color.opacity(0.6))
9332 }
9333 fn get_prediction_provider_icon_name(
9334 provider: &Option<RegisteredEditPredictionProvider>,
9335 ) -> IconName {
9336 match provider {
9337 Some(provider) => match provider.provider.name() {
9338 "copilot" => IconName::Copilot,
9339 "supermaven" => IconName::Supermaven,
9340 _ => IconName::ZedPredict,
9341 },
9342 None => IconName::ZedPredict,
9343 }
9344 }
9345
9346 fn render_edit_prediction_cursor_popover(
9347 &self,
9348 min_width: Pixels,
9349 max_width: Pixels,
9350 cursor_point: Point,
9351 style: &EditorStyle,
9352 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9353 _window: &Window,
9354 cx: &mut Context<Editor>,
9355 ) -> Option<AnyElement> {
9356 let provider = self.edit_prediction_provider.as_ref()?;
9357 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9358
9359 let is_refreshing = provider.provider.is_refreshing(cx);
9360
9361 fn pending_completion_container(icon: IconName) -> Div {
9362 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9363 }
9364
9365 let completion = match &self.active_edit_prediction {
9366 Some(prediction) => {
9367 if !self.has_visible_completions_menu() {
9368 const RADIUS: Pixels = px(6.);
9369 const BORDER_WIDTH: Pixels = px(1.);
9370
9371 return Some(
9372 h_flex()
9373 .elevation_2(cx)
9374 .border(BORDER_WIDTH)
9375 .border_color(cx.theme().colors().border)
9376 .when(accept_keystroke.is_none(), |el| {
9377 el.border_color(cx.theme().status().error)
9378 })
9379 .rounded(RADIUS)
9380 .rounded_tl(px(0.))
9381 .overflow_hidden()
9382 .child(div().px_1p5().child(match &prediction.completion {
9383 EditPrediction::MoveWithin { target, snapshot } => {
9384 use text::ToPoint as _;
9385 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9386 {
9387 Icon::new(IconName::ZedPredictDown)
9388 } else {
9389 Icon::new(IconName::ZedPredictUp)
9390 }
9391 }
9392 EditPrediction::MoveOutside { .. } => {
9393 // TODO [zeta2] custom icon for external jump?
9394 Icon::new(provider_icon)
9395 }
9396 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9397 }))
9398 .child(
9399 h_flex()
9400 .gap_1()
9401 .py_1()
9402 .px_2()
9403 .rounded_r(RADIUS - BORDER_WIDTH)
9404 .border_l_1()
9405 .border_color(cx.theme().colors().border)
9406 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9407 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9408 el.child(
9409 Label::new("Hold")
9410 .size(LabelSize::Small)
9411 .when(accept_keystroke.is_none(), |el| {
9412 el.strikethrough()
9413 })
9414 .line_height_style(LineHeightStyle::UiLabel),
9415 )
9416 })
9417 .id("edit_prediction_cursor_popover_keybind")
9418 .when(accept_keystroke.is_none(), |el| {
9419 let status_colors = cx.theme().status();
9420
9421 el.bg(status_colors.error_background)
9422 .border_color(status_colors.error.opacity(0.6))
9423 .child(Icon::new(IconName::Info).color(Color::Error))
9424 .cursor_default()
9425 .hoverable_tooltip(move |_window, cx| {
9426 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9427 .into()
9428 })
9429 })
9430 .when_some(
9431 accept_keystroke.as_ref(),
9432 |el, accept_keystroke| {
9433 el.child(h_flex().children(ui::render_modifiers(
9434 accept_keystroke.modifiers(),
9435 PlatformStyle::platform(),
9436 Some(Color::Default),
9437 Some(IconSize::XSmall.rems().into()),
9438 false,
9439 )))
9440 },
9441 ),
9442 )
9443 .into_any(),
9444 );
9445 }
9446
9447 self.render_edit_prediction_cursor_popover_preview(
9448 prediction,
9449 cursor_point,
9450 style,
9451 cx,
9452 )?
9453 }
9454
9455 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9456 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9457 stale_completion,
9458 cursor_point,
9459 style,
9460 cx,
9461 )?,
9462
9463 None => pending_completion_container(provider_icon)
9464 .child(Label::new("...").size(LabelSize::Small)),
9465 },
9466
9467 None => pending_completion_container(provider_icon)
9468 .child(Label::new("...").size(LabelSize::Small)),
9469 };
9470
9471 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9472 completion
9473 .with_animation(
9474 "loading-completion",
9475 Animation::new(Duration::from_secs(2))
9476 .repeat()
9477 .with_easing(pulsating_between(0.4, 0.8)),
9478 |label, delta| label.opacity(delta),
9479 )
9480 .into_any_element()
9481 } else {
9482 completion.into_any_element()
9483 };
9484
9485 let has_completion = self.active_edit_prediction.is_some();
9486
9487 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9488 Some(
9489 h_flex()
9490 .min_w(min_width)
9491 .max_w(max_width)
9492 .flex_1()
9493 .elevation_2(cx)
9494 .border_color(cx.theme().colors().border)
9495 .child(
9496 div()
9497 .flex_1()
9498 .py_1()
9499 .px_2()
9500 .overflow_hidden()
9501 .child(completion),
9502 )
9503 .when_some(accept_keystroke, |el, accept_keystroke| {
9504 if !accept_keystroke.modifiers().modified() {
9505 return el;
9506 }
9507
9508 el.child(
9509 h_flex()
9510 .h_full()
9511 .border_l_1()
9512 .rounded_r_lg()
9513 .border_color(cx.theme().colors().border)
9514 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9515 .gap_1()
9516 .py_1()
9517 .px_2()
9518 .child(
9519 h_flex()
9520 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9521 .when(is_platform_style_mac, |parent| parent.gap_1())
9522 .child(h_flex().children(ui::render_modifiers(
9523 accept_keystroke.modifiers(),
9524 PlatformStyle::platform(),
9525 Some(if !has_completion {
9526 Color::Muted
9527 } else {
9528 Color::Default
9529 }),
9530 None,
9531 false,
9532 ))),
9533 )
9534 .child(Label::new("Preview").into_any_element())
9535 .opacity(if has_completion { 1.0 } else { 0.4 }),
9536 )
9537 })
9538 .into_any(),
9539 )
9540 }
9541
9542 fn render_edit_prediction_cursor_popover_preview(
9543 &self,
9544 completion: &EditPredictionState,
9545 cursor_point: Point,
9546 style: &EditorStyle,
9547 cx: &mut Context<Editor>,
9548 ) -> Option<Div> {
9549 use text::ToPoint as _;
9550
9551 fn render_relative_row_jump(
9552 prefix: impl Into<String>,
9553 current_row: u32,
9554 target_row: u32,
9555 ) -> Div {
9556 let (row_diff, arrow) = if target_row < current_row {
9557 (current_row - target_row, IconName::ArrowUp)
9558 } else {
9559 (target_row - current_row, IconName::ArrowDown)
9560 };
9561
9562 h_flex()
9563 .child(
9564 Label::new(format!("{}{}", prefix.into(), row_diff))
9565 .color(Color::Muted)
9566 .size(LabelSize::Small),
9567 )
9568 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9569 }
9570
9571 let supports_jump = self
9572 .edit_prediction_provider
9573 .as_ref()
9574 .map(|provider| provider.provider.supports_jump_to_edit())
9575 .unwrap_or(true);
9576
9577 match &completion.completion {
9578 EditPrediction::MoveWithin {
9579 target, snapshot, ..
9580 } => {
9581 if !supports_jump {
9582 return None;
9583 }
9584
9585 Some(
9586 h_flex()
9587 .px_2()
9588 .gap_2()
9589 .flex_1()
9590 .child(
9591 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9592 Icon::new(IconName::ZedPredictDown)
9593 } else {
9594 Icon::new(IconName::ZedPredictUp)
9595 },
9596 )
9597 .child(Label::new("Jump to Edit")),
9598 )
9599 }
9600 EditPrediction::MoveOutside { snapshot, .. } => {
9601 let file_name = snapshot
9602 .file()
9603 .map(|file| file.file_name(cx))
9604 .unwrap_or("untitled");
9605 Some(
9606 h_flex()
9607 .px_2()
9608 .gap_2()
9609 .flex_1()
9610 .child(Icon::new(IconName::ZedPredict))
9611 .child(Label::new(format!("Jump to {file_name}"))),
9612 )
9613 }
9614 EditPrediction::Edit {
9615 edits,
9616 edit_preview,
9617 snapshot,
9618 display_mode: _,
9619 } => {
9620 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9621
9622 let (highlighted_edits, has_more_lines) =
9623 if let Some(edit_preview) = edit_preview.as_ref() {
9624 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9625 .first_line_preview()
9626 } else {
9627 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9628 };
9629
9630 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9631 .with_default_highlights(&style.text, highlighted_edits.highlights);
9632
9633 let preview = h_flex()
9634 .gap_1()
9635 .min_w_16()
9636 .child(styled_text)
9637 .when(has_more_lines, |parent| parent.child("…"));
9638
9639 let left = if supports_jump && first_edit_row != cursor_point.row {
9640 render_relative_row_jump("", cursor_point.row, first_edit_row)
9641 .into_any_element()
9642 } else {
9643 let icon_name =
9644 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9645 Icon::new(icon_name).into_any_element()
9646 };
9647
9648 Some(
9649 h_flex()
9650 .h_full()
9651 .flex_1()
9652 .gap_2()
9653 .pr_1()
9654 .overflow_x_hidden()
9655 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9656 .child(left)
9657 .child(preview),
9658 )
9659 }
9660 }
9661 }
9662
9663 pub fn render_context_menu(
9664 &self,
9665 style: &EditorStyle,
9666 max_height_in_lines: u32,
9667 window: &mut Window,
9668 cx: &mut Context<Editor>,
9669 ) -> Option<AnyElement> {
9670 let menu = self.context_menu.borrow();
9671 let menu = menu.as_ref()?;
9672 if !menu.visible() {
9673 return None;
9674 };
9675 Some(menu.render(style, max_height_in_lines, window, cx))
9676 }
9677
9678 fn render_context_menu_aside(
9679 &mut self,
9680 max_size: Size<Pixels>,
9681 window: &mut Window,
9682 cx: &mut Context<Editor>,
9683 ) -> Option<AnyElement> {
9684 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9685 if menu.visible() {
9686 menu.render_aside(max_size, window, cx)
9687 } else {
9688 None
9689 }
9690 })
9691 }
9692
9693 fn hide_context_menu(
9694 &mut self,
9695 window: &mut Window,
9696 cx: &mut Context<Self>,
9697 ) -> Option<CodeContextMenu> {
9698 cx.notify();
9699 self.completion_tasks.clear();
9700 let context_menu = self.context_menu.borrow_mut().take();
9701 self.stale_edit_prediction_in_menu.take();
9702 self.update_visible_edit_prediction(window, cx);
9703 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9704 && let Some(completion_provider) = &self.completion_provider
9705 {
9706 completion_provider.selection_changed(None, window, cx);
9707 }
9708 context_menu
9709 }
9710
9711 fn show_snippet_choices(
9712 &mut self,
9713 choices: &Vec<String>,
9714 selection: Range<Anchor>,
9715 cx: &mut Context<Self>,
9716 ) {
9717 let Some((_, buffer, _)) = self
9718 .buffer()
9719 .read(cx)
9720 .excerpt_containing(selection.start, cx)
9721 else {
9722 return;
9723 };
9724 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9725 else {
9726 return;
9727 };
9728 if buffer != end_buffer {
9729 log::error!("expected anchor range to have matching buffer IDs");
9730 return;
9731 }
9732
9733 let id = post_inc(&mut self.next_completion_id);
9734 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9735 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9736 CompletionsMenu::new_snippet_choices(
9737 id,
9738 true,
9739 choices,
9740 selection,
9741 buffer,
9742 snippet_sort_order,
9743 ),
9744 ));
9745 }
9746
9747 pub fn insert_snippet(
9748 &mut self,
9749 insertion_ranges: &[Range<usize>],
9750 snippet: Snippet,
9751 window: &mut Window,
9752 cx: &mut Context<Self>,
9753 ) -> Result<()> {
9754 struct Tabstop<T> {
9755 is_end_tabstop: bool,
9756 ranges: Vec<Range<T>>,
9757 choices: Option<Vec<String>>,
9758 }
9759
9760 let tabstops = self.buffer.update(cx, |buffer, cx| {
9761 let snippet_text: Arc<str> = snippet.text.clone().into();
9762 let edits = insertion_ranges
9763 .iter()
9764 .cloned()
9765 .map(|range| (range, snippet_text.clone()));
9766 let autoindent_mode = AutoindentMode::Block {
9767 original_indent_columns: Vec::new(),
9768 };
9769 buffer.edit(edits, Some(autoindent_mode), cx);
9770
9771 let snapshot = &*buffer.read(cx);
9772 let snippet = &snippet;
9773 snippet
9774 .tabstops
9775 .iter()
9776 .map(|tabstop| {
9777 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9778 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9779 });
9780 let mut tabstop_ranges = tabstop
9781 .ranges
9782 .iter()
9783 .flat_map(|tabstop_range| {
9784 let mut delta = 0_isize;
9785 insertion_ranges.iter().map(move |insertion_range| {
9786 let insertion_start = insertion_range.start as isize + delta;
9787 delta +=
9788 snippet.text.len() as isize - insertion_range.len() as isize;
9789
9790 let start = ((insertion_start + tabstop_range.start) as usize)
9791 .min(snapshot.len());
9792 let end = ((insertion_start + tabstop_range.end) as usize)
9793 .min(snapshot.len());
9794 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9795 })
9796 })
9797 .collect::<Vec<_>>();
9798 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9799
9800 Tabstop {
9801 is_end_tabstop,
9802 ranges: tabstop_ranges,
9803 choices: tabstop.choices.clone(),
9804 }
9805 })
9806 .collect::<Vec<_>>()
9807 });
9808 if let Some(tabstop) = tabstops.first() {
9809 self.change_selections(Default::default(), window, cx, |s| {
9810 // Reverse order so that the first range is the newest created selection.
9811 // Completions will use it and autoscroll will prioritize it.
9812 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9813 });
9814
9815 if let Some(choices) = &tabstop.choices
9816 && let Some(selection) = tabstop.ranges.first()
9817 {
9818 self.show_snippet_choices(choices, selection.clone(), cx)
9819 }
9820
9821 // If we're already at the last tabstop and it's at the end of the snippet,
9822 // we're done, we don't need to keep the state around.
9823 if !tabstop.is_end_tabstop {
9824 let choices = tabstops
9825 .iter()
9826 .map(|tabstop| tabstop.choices.clone())
9827 .collect();
9828
9829 let ranges = tabstops
9830 .into_iter()
9831 .map(|tabstop| tabstop.ranges)
9832 .collect::<Vec<_>>();
9833
9834 self.snippet_stack.push(SnippetState {
9835 active_index: 0,
9836 ranges,
9837 choices,
9838 });
9839 }
9840
9841 // Check whether the just-entered snippet ends with an auto-closable bracket.
9842 if self.autoclose_regions.is_empty() {
9843 let snapshot = self.buffer.read(cx).snapshot(cx);
9844 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9845 let selection_head = selection.head();
9846 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9847 continue;
9848 };
9849
9850 let mut bracket_pair = None;
9851 let max_lookup_length = scope
9852 .brackets()
9853 .map(|(pair, _)| {
9854 pair.start
9855 .as_str()
9856 .chars()
9857 .count()
9858 .max(pair.end.as_str().chars().count())
9859 })
9860 .max();
9861 if let Some(max_lookup_length) = max_lookup_length {
9862 let next_text = snapshot
9863 .chars_at(selection_head)
9864 .take(max_lookup_length)
9865 .collect::<String>();
9866 let prev_text = snapshot
9867 .reversed_chars_at(selection_head)
9868 .take(max_lookup_length)
9869 .collect::<String>();
9870
9871 for (pair, enabled) in scope.brackets() {
9872 if enabled
9873 && pair.close
9874 && prev_text.starts_with(pair.start.as_str())
9875 && next_text.starts_with(pair.end.as_str())
9876 {
9877 bracket_pair = Some(pair.clone());
9878 break;
9879 }
9880 }
9881 }
9882
9883 if let Some(pair) = bracket_pair {
9884 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9885 let autoclose_enabled =
9886 self.use_autoclose && snapshot_settings.use_autoclose;
9887 if autoclose_enabled {
9888 let start = snapshot.anchor_after(selection_head);
9889 let end = snapshot.anchor_after(selection_head);
9890 self.autoclose_regions.push(AutocloseRegion {
9891 selection_id: selection.id,
9892 range: start..end,
9893 pair,
9894 });
9895 }
9896 }
9897 }
9898 }
9899 }
9900 Ok(())
9901 }
9902
9903 pub fn move_to_next_snippet_tabstop(
9904 &mut self,
9905 window: &mut Window,
9906 cx: &mut Context<Self>,
9907 ) -> bool {
9908 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9909 }
9910
9911 pub fn move_to_prev_snippet_tabstop(
9912 &mut self,
9913 window: &mut Window,
9914 cx: &mut Context<Self>,
9915 ) -> bool {
9916 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9917 }
9918
9919 pub fn move_to_snippet_tabstop(
9920 &mut self,
9921 bias: Bias,
9922 window: &mut Window,
9923 cx: &mut Context<Self>,
9924 ) -> bool {
9925 if let Some(mut snippet) = self.snippet_stack.pop() {
9926 match bias {
9927 Bias::Left => {
9928 if snippet.active_index > 0 {
9929 snippet.active_index -= 1;
9930 } else {
9931 self.snippet_stack.push(snippet);
9932 return false;
9933 }
9934 }
9935 Bias::Right => {
9936 if snippet.active_index + 1 < snippet.ranges.len() {
9937 snippet.active_index += 1;
9938 } else {
9939 self.snippet_stack.push(snippet);
9940 return false;
9941 }
9942 }
9943 }
9944 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9945 self.change_selections(Default::default(), window, cx, |s| {
9946 // Reverse order so that the first range is the newest created selection.
9947 // Completions will use it and autoscroll will prioritize it.
9948 s.select_ranges(current_ranges.iter().rev().cloned())
9949 });
9950
9951 if let Some(choices) = &snippet.choices[snippet.active_index]
9952 && let Some(selection) = current_ranges.first()
9953 {
9954 self.show_snippet_choices(choices, selection.clone(), cx);
9955 }
9956
9957 // If snippet state is not at the last tabstop, push it back on the stack
9958 if snippet.active_index + 1 < snippet.ranges.len() {
9959 self.snippet_stack.push(snippet);
9960 }
9961 return true;
9962 }
9963 }
9964
9965 false
9966 }
9967
9968 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9969 self.transact(window, cx, |this, window, cx| {
9970 this.select_all(&SelectAll, window, cx);
9971 this.insert("", window, cx);
9972 });
9973 }
9974
9975 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9976 if self.read_only(cx) {
9977 return;
9978 }
9979 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9980 self.transact(window, cx, |this, window, cx| {
9981 this.select_autoclose_pair(window, cx);
9982
9983 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9984
9985 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9986 if !this.linked_edit_ranges.is_empty() {
9987 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
9988 let snapshot = this.buffer.read(cx).snapshot(cx);
9989
9990 for selection in selections.iter() {
9991 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9992 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9993 if selection_start.buffer_id != selection_end.buffer_id {
9994 continue;
9995 }
9996 if let Some(ranges) =
9997 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9998 {
9999 for (buffer, entries) in ranges {
10000 linked_ranges.entry(buffer).or_default().extend(entries);
10001 }
10002 }
10003 }
10004 }
10005
10006 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10007 for selection in &mut selections {
10008 if selection.is_empty() {
10009 let old_head = selection.head();
10010 let mut new_head =
10011 movement::left(&display_map, old_head.to_display_point(&display_map))
10012 .to_point(&display_map);
10013 if let Some((buffer, line_buffer_range)) = display_map
10014 .buffer_snapshot()
10015 .buffer_line_for_row(MultiBufferRow(old_head.row))
10016 {
10017 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10018 let indent_len = match indent_size.kind {
10019 IndentKind::Space => {
10020 buffer.settings_at(line_buffer_range.start, cx).tab_size
10021 }
10022 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10023 };
10024 if old_head.column <= indent_size.len && old_head.column > 0 {
10025 let indent_len = indent_len.get();
10026 new_head = cmp::min(
10027 new_head,
10028 MultiBufferPoint::new(
10029 old_head.row,
10030 ((old_head.column - 1) / indent_len) * indent_len,
10031 ),
10032 );
10033 }
10034 }
10035
10036 selection.set_head(new_head, SelectionGoal::None);
10037 }
10038 }
10039
10040 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10041 this.insert("", window, cx);
10042 let empty_str: Arc<str> = Arc::from("");
10043 for (buffer, edits) in linked_ranges {
10044 let snapshot = buffer.read(cx).snapshot();
10045 use text::ToPoint as TP;
10046
10047 let edits = edits
10048 .into_iter()
10049 .map(|range| {
10050 let end_point = TP::to_point(&range.end, &snapshot);
10051 let mut start_point = TP::to_point(&range.start, &snapshot);
10052
10053 if end_point == start_point {
10054 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10055 .saturating_sub(1);
10056 start_point =
10057 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10058 };
10059
10060 (start_point..end_point, empty_str.clone())
10061 })
10062 .sorted_by_key(|(range, _)| range.start)
10063 .collect::<Vec<_>>();
10064 buffer.update(cx, |this, cx| {
10065 this.edit(edits, None, cx);
10066 })
10067 }
10068 this.refresh_edit_prediction(true, false, window, cx);
10069 refresh_linked_ranges(this, window, cx);
10070 });
10071 }
10072
10073 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10074 if self.read_only(cx) {
10075 return;
10076 }
10077 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10078 self.transact(window, cx, |this, window, cx| {
10079 this.change_selections(Default::default(), window, cx, |s| {
10080 s.move_with(|map, selection| {
10081 if selection.is_empty() {
10082 let cursor = movement::right(map, selection.head());
10083 selection.end = cursor;
10084 selection.reversed = true;
10085 selection.goal = SelectionGoal::None;
10086 }
10087 })
10088 });
10089 this.insert("", window, cx);
10090 this.refresh_edit_prediction(true, false, window, cx);
10091 });
10092 }
10093
10094 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10095 if self.mode.is_single_line() {
10096 cx.propagate();
10097 return;
10098 }
10099
10100 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10101 if self.move_to_prev_snippet_tabstop(window, cx) {
10102 return;
10103 }
10104 self.outdent(&Outdent, window, cx);
10105 }
10106
10107 pub fn next_snippet_tabstop(
10108 &mut self,
10109 _: &NextSnippetTabstop,
10110 window: &mut Window,
10111 cx: &mut Context<Self>,
10112 ) {
10113 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10114 cx.propagate();
10115 return;
10116 }
10117
10118 if self.move_to_next_snippet_tabstop(window, cx) {
10119 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10120 return;
10121 }
10122 cx.propagate();
10123 }
10124
10125 pub fn previous_snippet_tabstop(
10126 &mut self,
10127 _: &PreviousSnippetTabstop,
10128 window: &mut Window,
10129 cx: &mut Context<Self>,
10130 ) {
10131 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10132 cx.propagate();
10133 return;
10134 }
10135
10136 if self.move_to_prev_snippet_tabstop(window, cx) {
10137 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10138 return;
10139 }
10140 cx.propagate();
10141 }
10142
10143 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10144 if self.mode.is_single_line() {
10145 cx.propagate();
10146 return;
10147 }
10148
10149 if self.move_to_next_snippet_tabstop(window, cx) {
10150 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10151 return;
10152 }
10153 if self.read_only(cx) {
10154 return;
10155 }
10156 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10157 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10158 let buffer = self.buffer.read(cx);
10159 let snapshot = buffer.snapshot(cx);
10160 let rows_iter = selections.iter().map(|s| s.head().row);
10161 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10162
10163 let has_some_cursor_in_whitespace = selections
10164 .iter()
10165 .filter(|selection| selection.is_empty())
10166 .any(|selection| {
10167 let cursor = selection.head();
10168 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10169 cursor.column < current_indent.len
10170 });
10171
10172 let mut edits = Vec::new();
10173 let mut prev_edited_row = 0;
10174 let mut row_delta = 0;
10175 for selection in &mut selections {
10176 if selection.start.row != prev_edited_row {
10177 row_delta = 0;
10178 }
10179 prev_edited_row = selection.end.row;
10180
10181 // If the selection is non-empty, then increase the indentation of the selected lines.
10182 if !selection.is_empty() {
10183 row_delta =
10184 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10185 continue;
10186 }
10187
10188 let cursor = selection.head();
10189 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10190 if let Some(suggested_indent) =
10191 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10192 {
10193 // Don't do anything if already at suggested indent
10194 // and there is any other cursor which is not
10195 if has_some_cursor_in_whitespace
10196 && cursor.column == current_indent.len
10197 && current_indent.len == suggested_indent.len
10198 {
10199 continue;
10200 }
10201
10202 // Adjust line and move cursor to suggested indent
10203 // if cursor is not at suggested indent
10204 if cursor.column < suggested_indent.len
10205 && cursor.column <= current_indent.len
10206 && current_indent.len <= suggested_indent.len
10207 {
10208 selection.start = Point::new(cursor.row, suggested_indent.len);
10209 selection.end = selection.start;
10210 if row_delta == 0 {
10211 edits.extend(Buffer::edit_for_indent_size_adjustment(
10212 cursor.row,
10213 current_indent,
10214 suggested_indent,
10215 ));
10216 row_delta = suggested_indent.len - current_indent.len;
10217 }
10218 continue;
10219 }
10220
10221 // If current indent is more than suggested indent
10222 // only move cursor to current indent and skip indent
10223 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10224 selection.start = Point::new(cursor.row, current_indent.len);
10225 selection.end = selection.start;
10226 continue;
10227 }
10228 }
10229
10230 // Otherwise, insert a hard or soft tab.
10231 let settings = buffer.language_settings_at(cursor, cx);
10232 let tab_size = if settings.hard_tabs {
10233 IndentSize::tab()
10234 } else {
10235 let tab_size = settings.tab_size.get();
10236 let indent_remainder = snapshot
10237 .text_for_range(Point::new(cursor.row, 0)..cursor)
10238 .flat_map(str::chars)
10239 .fold(row_delta % tab_size, |counter: u32, c| {
10240 if c == '\t' {
10241 0
10242 } else {
10243 (counter + 1) % tab_size
10244 }
10245 });
10246
10247 let chars_to_next_tab_stop = tab_size - indent_remainder;
10248 IndentSize::spaces(chars_to_next_tab_stop)
10249 };
10250 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10251 selection.end = selection.start;
10252 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10253 row_delta += tab_size.len;
10254 }
10255
10256 self.transact(window, cx, |this, window, cx| {
10257 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10258 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10259 this.refresh_edit_prediction(true, false, window, cx);
10260 });
10261 }
10262
10263 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10264 if self.read_only(cx) {
10265 return;
10266 }
10267 if self.mode.is_single_line() {
10268 cx.propagate();
10269 return;
10270 }
10271
10272 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10273 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10274 let mut prev_edited_row = 0;
10275 let mut row_delta = 0;
10276 let mut edits = Vec::new();
10277 let buffer = self.buffer.read(cx);
10278 let snapshot = buffer.snapshot(cx);
10279 for selection in &mut selections {
10280 if selection.start.row != prev_edited_row {
10281 row_delta = 0;
10282 }
10283 prev_edited_row = selection.end.row;
10284
10285 row_delta =
10286 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10287 }
10288
10289 self.transact(window, cx, |this, window, cx| {
10290 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10291 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10292 });
10293 }
10294
10295 fn indent_selection(
10296 buffer: &MultiBuffer,
10297 snapshot: &MultiBufferSnapshot,
10298 selection: &mut Selection<Point>,
10299 edits: &mut Vec<(Range<Point>, String)>,
10300 delta_for_start_row: u32,
10301 cx: &App,
10302 ) -> u32 {
10303 let settings = buffer.language_settings_at(selection.start, cx);
10304 let tab_size = settings.tab_size.get();
10305 let indent_kind = if settings.hard_tabs {
10306 IndentKind::Tab
10307 } else {
10308 IndentKind::Space
10309 };
10310 let mut start_row = selection.start.row;
10311 let mut end_row = selection.end.row + 1;
10312
10313 // If a selection ends at the beginning of a line, don't indent
10314 // that last line.
10315 if selection.end.column == 0 && selection.end.row > selection.start.row {
10316 end_row -= 1;
10317 }
10318
10319 // Avoid re-indenting a row that has already been indented by a
10320 // previous selection, but still update this selection's column
10321 // to reflect that indentation.
10322 if delta_for_start_row > 0 {
10323 start_row += 1;
10324 selection.start.column += delta_for_start_row;
10325 if selection.end.row == selection.start.row {
10326 selection.end.column += delta_for_start_row;
10327 }
10328 }
10329
10330 let mut delta_for_end_row = 0;
10331 let has_multiple_rows = start_row + 1 != end_row;
10332 for row in start_row..end_row {
10333 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10334 let indent_delta = match (current_indent.kind, indent_kind) {
10335 (IndentKind::Space, IndentKind::Space) => {
10336 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10337 IndentSize::spaces(columns_to_next_tab_stop)
10338 }
10339 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10340 (_, IndentKind::Tab) => IndentSize::tab(),
10341 };
10342
10343 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10344 0
10345 } else {
10346 selection.start.column
10347 };
10348 let row_start = Point::new(row, start);
10349 edits.push((
10350 row_start..row_start,
10351 indent_delta.chars().collect::<String>(),
10352 ));
10353
10354 // Update this selection's endpoints to reflect the indentation.
10355 if row == selection.start.row {
10356 selection.start.column += indent_delta.len;
10357 }
10358 if row == selection.end.row {
10359 selection.end.column += indent_delta.len;
10360 delta_for_end_row = indent_delta.len;
10361 }
10362 }
10363
10364 if selection.start.row == selection.end.row {
10365 delta_for_start_row + delta_for_end_row
10366 } else {
10367 delta_for_end_row
10368 }
10369 }
10370
10371 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10372 if self.read_only(cx) {
10373 return;
10374 }
10375 if self.mode.is_single_line() {
10376 cx.propagate();
10377 return;
10378 }
10379
10380 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10381 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10382 let selections = self.selections.all::<Point>(&display_map);
10383 let mut deletion_ranges = Vec::new();
10384 let mut last_outdent = None;
10385 {
10386 let buffer = self.buffer.read(cx);
10387 let snapshot = buffer.snapshot(cx);
10388 for selection in &selections {
10389 let settings = buffer.language_settings_at(selection.start, cx);
10390 let tab_size = settings.tab_size.get();
10391 let mut rows = selection.spanned_rows(false, &display_map);
10392
10393 // Avoid re-outdenting a row that has already been outdented by a
10394 // previous selection.
10395 if let Some(last_row) = last_outdent
10396 && last_row == rows.start
10397 {
10398 rows.start = rows.start.next_row();
10399 }
10400 let has_multiple_rows = rows.len() > 1;
10401 for row in rows.iter_rows() {
10402 let indent_size = snapshot.indent_size_for_line(row);
10403 if indent_size.len > 0 {
10404 let deletion_len = match indent_size.kind {
10405 IndentKind::Space => {
10406 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10407 if columns_to_prev_tab_stop == 0 {
10408 tab_size
10409 } else {
10410 columns_to_prev_tab_stop
10411 }
10412 }
10413 IndentKind::Tab => 1,
10414 };
10415 let start = if has_multiple_rows
10416 || deletion_len > selection.start.column
10417 || indent_size.len < selection.start.column
10418 {
10419 0
10420 } else {
10421 selection.start.column - deletion_len
10422 };
10423 deletion_ranges.push(
10424 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10425 );
10426 last_outdent = Some(row);
10427 }
10428 }
10429 }
10430 }
10431
10432 self.transact(window, cx, |this, window, cx| {
10433 this.buffer.update(cx, |buffer, cx| {
10434 let empty_str: Arc<str> = Arc::default();
10435 buffer.edit(
10436 deletion_ranges
10437 .into_iter()
10438 .map(|range| (range, empty_str.clone())),
10439 None,
10440 cx,
10441 );
10442 });
10443 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10444 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10445 });
10446 }
10447
10448 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10449 if self.read_only(cx) {
10450 return;
10451 }
10452 if self.mode.is_single_line() {
10453 cx.propagate();
10454 return;
10455 }
10456
10457 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10458 let selections = self
10459 .selections
10460 .all::<usize>(&self.display_snapshot(cx))
10461 .into_iter()
10462 .map(|s| s.range());
10463
10464 self.transact(window, cx, |this, window, cx| {
10465 this.buffer.update(cx, |buffer, cx| {
10466 buffer.autoindent_ranges(selections, cx);
10467 });
10468 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10469 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10470 });
10471 }
10472
10473 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10474 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10475 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10476 let selections = self.selections.all::<Point>(&display_map);
10477
10478 let mut new_cursors = Vec::new();
10479 let mut edit_ranges = Vec::new();
10480 let mut selections = selections.iter().peekable();
10481 while let Some(selection) = selections.next() {
10482 let mut rows = selection.spanned_rows(false, &display_map);
10483
10484 // Accumulate contiguous regions of rows that we want to delete.
10485 while let Some(next_selection) = selections.peek() {
10486 let next_rows = next_selection.spanned_rows(false, &display_map);
10487 if next_rows.start <= rows.end {
10488 rows.end = next_rows.end;
10489 selections.next().unwrap();
10490 } else {
10491 break;
10492 }
10493 }
10494
10495 let buffer = display_map.buffer_snapshot();
10496 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10497 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10498 // If there's a line after the range, delete the \n from the end of the row range
10499 (
10500 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10501 rows.end,
10502 )
10503 } else {
10504 // If there isn't a line after the range, delete the \n from the line before the
10505 // start of the row range
10506 edit_start = edit_start.saturating_sub(1);
10507 (buffer.len(), rows.start.previous_row())
10508 };
10509
10510 let text_layout_details = self.text_layout_details(window);
10511 let x = display_map.x_for_display_point(
10512 selection.head().to_display_point(&display_map),
10513 &text_layout_details,
10514 );
10515 let row = Point::new(target_row.0, 0)
10516 .to_display_point(&display_map)
10517 .row();
10518 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10519
10520 new_cursors.push((
10521 selection.id,
10522 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10523 SelectionGoal::None,
10524 ));
10525 edit_ranges.push(edit_start..edit_end);
10526 }
10527
10528 self.transact(window, cx, |this, window, cx| {
10529 let buffer = this.buffer.update(cx, |buffer, cx| {
10530 let empty_str: Arc<str> = Arc::default();
10531 buffer.edit(
10532 edit_ranges
10533 .into_iter()
10534 .map(|range| (range, empty_str.clone())),
10535 None,
10536 cx,
10537 );
10538 buffer.snapshot(cx)
10539 });
10540 let new_selections = new_cursors
10541 .into_iter()
10542 .map(|(id, cursor, goal)| {
10543 let cursor = cursor.to_point(&buffer);
10544 Selection {
10545 id,
10546 start: cursor,
10547 end: cursor,
10548 reversed: false,
10549 goal,
10550 }
10551 })
10552 .collect();
10553
10554 this.change_selections(Default::default(), window, cx, |s| {
10555 s.select(new_selections);
10556 });
10557 });
10558 }
10559
10560 pub fn join_lines_impl(
10561 &mut self,
10562 insert_whitespace: bool,
10563 window: &mut Window,
10564 cx: &mut Context<Self>,
10565 ) {
10566 if self.read_only(cx) {
10567 return;
10568 }
10569 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10570 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10571 let start = MultiBufferRow(selection.start.row);
10572 // Treat single line selections as if they include the next line. Otherwise this action
10573 // would do nothing for single line selections individual cursors.
10574 let end = if selection.start.row == selection.end.row {
10575 MultiBufferRow(selection.start.row + 1)
10576 } else {
10577 MultiBufferRow(selection.end.row)
10578 };
10579
10580 if let Some(last_row_range) = row_ranges.last_mut()
10581 && start <= last_row_range.end
10582 {
10583 last_row_range.end = end;
10584 continue;
10585 }
10586 row_ranges.push(start..end);
10587 }
10588
10589 let snapshot = self.buffer.read(cx).snapshot(cx);
10590 let mut cursor_positions = Vec::new();
10591 for row_range in &row_ranges {
10592 let anchor = snapshot.anchor_before(Point::new(
10593 row_range.end.previous_row().0,
10594 snapshot.line_len(row_range.end.previous_row()),
10595 ));
10596 cursor_positions.push(anchor..anchor);
10597 }
10598
10599 self.transact(window, cx, |this, window, cx| {
10600 for row_range in row_ranges.into_iter().rev() {
10601 for row in row_range.iter_rows().rev() {
10602 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10603 let next_line_row = row.next_row();
10604 let indent = snapshot.indent_size_for_line(next_line_row);
10605 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10606
10607 let replace =
10608 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10609 " "
10610 } else {
10611 ""
10612 };
10613
10614 this.buffer.update(cx, |buffer, cx| {
10615 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10616 });
10617 }
10618 }
10619
10620 this.change_selections(Default::default(), window, cx, |s| {
10621 s.select_anchor_ranges(cursor_positions)
10622 });
10623 });
10624 }
10625
10626 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10627 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10628 self.join_lines_impl(true, window, cx);
10629 }
10630
10631 pub fn sort_lines_case_sensitive(
10632 &mut self,
10633 _: &SortLinesCaseSensitive,
10634 window: &mut Window,
10635 cx: &mut Context<Self>,
10636 ) {
10637 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10638 }
10639
10640 pub fn sort_lines_by_length(
10641 &mut self,
10642 _: &SortLinesByLength,
10643 window: &mut Window,
10644 cx: &mut Context<Self>,
10645 ) {
10646 self.manipulate_immutable_lines(window, cx, |lines| {
10647 lines.sort_by_key(|&line| line.chars().count())
10648 })
10649 }
10650
10651 pub fn sort_lines_case_insensitive(
10652 &mut self,
10653 _: &SortLinesCaseInsensitive,
10654 window: &mut Window,
10655 cx: &mut Context<Self>,
10656 ) {
10657 self.manipulate_immutable_lines(window, cx, |lines| {
10658 lines.sort_by_key(|line| line.to_lowercase())
10659 })
10660 }
10661
10662 pub fn unique_lines_case_insensitive(
10663 &mut self,
10664 _: &UniqueLinesCaseInsensitive,
10665 window: &mut Window,
10666 cx: &mut Context<Self>,
10667 ) {
10668 self.manipulate_immutable_lines(window, cx, |lines| {
10669 let mut seen = HashSet::default();
10670 lines.retain(|line| seen.insert(line.to_lowercase()));
10671 })
10672 }
10673
10674 pub fn unique_lines_case_sensitive(
10675 &mut self,
10676 _: &UniqueLinesCaseSensitive,
10677 window: &mut Window,
10678 cx: &mut Context<Self>,
10679 ) {
10680 self.manipulate_immutable_lines(window, cx, |lines| {
10681 let mut seen = HashSet::default();
10682 lines.retain(|line| seen.insert(*line));
10683 })
10684 }
10685
10686 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10687 let snapshot = self.buffer.read(cx).snapshot(cx);
10688 for selection in self.selections.disjoint_anchors_arc().iter() {
10689 if snapshot
10690 .language_at(selection.start)
10691 .and_then(|lang| lang.config().wrap_characters.as_ref())
10692 .is_some()
10693 {
10694 return true;
10695 }
10696 }
10697 false
10698 }
10699
10700 fn wrap_selections_in_tag(
10701 &mut self,
10702 _: &WrapSelectionsInTag,
10703 window: &mut Window,
10704 cx: &mut Context<Self>,
10705 ) {
10706 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10707
10708 let snapshot = self.buffer.read(cx).snapshot(cx);
10709
10710 let mut edits = Vec::new();
10711 let mut boundaries = Vec::new();
10712
10713 for selection in self
10714 .selections
10715 .all_adjusted(&self.display_snapshot(cx))
10716 .iter()
10717 {
10718 let Some(wrap_config) = snapshot
10719 .language_at(selection.start)
10720 .and_then(|lang| lang.config().wrap_characters.clone())
10721 else {
10722 continue;
10723 };
10724
10725 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10726 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10727
10728 let start_before = snapshot.anchor_before(selection.start);
10729 let end_after = snapshot.anchor_after(selection.end);
10730
10731 edits.push((start_before..start_before, open_tag));
10732 edits.push((end_after..end_after, close_tag));
10733
10734 boundaries.push((
10735 start_before,
10736 end_after,
10737 wrap_config.start_prefix.len(),
10738 wrap_config.end_suffix.len(),
10739 ));
10740 }
10741
10742 if edits.is_empty() {
10743 return;
10744 }
10745
10746 self.transact(window, cx, |this, window, cx| {
10747 let buffer = this.buffer.update(cx, |buffer, cx| {
10748 buffer.edit(edits, None, cx);
10749 buffer.snapshot(cx)
10750 });
10751
10752 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10753 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10754 boundaries.into_iter()
10755 {
10756 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10757 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10758 new_selections.push(open_offset..open_offset);
10759 new_selections.push(close_offset..close_offset);
10760 }
10761
10762 this.change_selections(Default::default(), window, cx, |s| {
10763 s.select_ranges(new_selections);
10764 });
10765
10766 this.request_autoscroll(Autoscroll::fit(), cx);
10767 });
10768 }
10769
10770 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10771 let Some(project) = self.project.clone() else {
10772 return;
10773 };
10774 self.reload(project, window, cx)
10775 .detach_and_notify_err(window, cx);
10776 }
10777
10778 pub fn restore_file(
10779 &mut self,
10780 _: &::git::RestoreFile,
10781 window: &mut Window,
10782 cx: &mut Context<Self>,
10783 ) {
10784 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10785 let mut buffer_ids = HashSet::default();
10786 let snapshot = self.buffer().read(cx).snapshot(cx);
10787 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10788 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10789 }
10790
10791 let buffer = self.buffer().read(cx);
10792 let ranges = buffer_ids
10793 .into_iter()
10794 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10795 .collect::<Vec<_>>();
10796
10797 self.restore_hunks_in_ranges(ranges, window, cx);
10798 }
10799
10800 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10801 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10802 let selections = self
10803 .selections
10804 .all(&self.display_snapshot(cx))
10805 .into_iter()
10806 .map(|s| s.range())
10807 .collect();
10808 self.restore_hunks_in_ranges(selections, window, cx);
10809 }
10810
10811 pub fn restore_hunks_in_ranges(
10812 &mut self,
10813 ranges: Vec<Range<Point>>,
10814 window: &mut Window,
10815 cx: &mut Context<Editor>,
10816 ) {
10817 let mut revert_changes = HashMap::default();
10818 let chunk_by = self
10819 .snapshot(window, cx)
10820 .hunks_for_ranges(ranges)
10821 .into_iter()
10822 .chunk_by(|hunk| hunk.buffer_id);
10823 for (buffer_id, hunks) in &chunk_by {
10824 let hunks = hunks.collect::<Vec<_>>();
10825 for hunk in &hunks {
10826 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10827 }
10828 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10829 }
10830 drop(chunk_by);
10831 if !revert_changes.is_empty() {
10832 self.transact(window, cx, |editor, window, cx| {
10833 editor.restore(revert_changes, window, cx);
10834 });
10835 }
10836 }
10837
10838 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10839 if let Some(status) = self
10840 .addons
10841 .iter()
10842 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10843 {
10844 return Some(status);
10845 }
10846 self.project
10847 .as_ref()?
10848 .read(cx)
10849 .status_for_buffer_id(buffer_id, cx)
10850 }
10851
10852 pub fn open_active_item_in_terminal(
10853 &mut self,
10854 _: &OpenInTerminal,
10855 window: &mut Window,
10856 cx: &mut Context<Self>,
10857 ) {
10858 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10859 let project_path = buffer.read(cx).project_path(cx)?;
10860 let project = self.project()?.read(cx);
10861 let entry = project.entry_for_path(&project_path, cx)?;
10862 let parent = match &entry.canonical_path {
10863 Some(canonical_path) => canonical_path.to_path_buf(),
10864 None => project.absolute_path(&project_path, cx)?,
10865 }
10866 .parent()?
10867 .to_path_buf();
10868 Some(parent)
10869 }) {
10870 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10871 }
10872 }
10873
10874 fn set_breakpoint_context_menu(
10875 &mut self,
10876 display_row: DisplayRow,
10877 position: Option<Anchor>,
10878 clicked_point: gpui::Point<Pixels>,
10879 window: &mut Window,
10880 cx: &mut Context<Self>,
10881 ) {
10882 let source = self
10883 .buffer
10884 .read(cx)
10885 .snapshot(cx)
10886 .anchor_before(Point::new(display_row.0, 0u32));
10887
10888 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10889
10890 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10891 self,
10892 source,
10893 clicked_point,
10894 context_menu,
10895 window,
10896 cx,
10897 );
10898 }
10899
10900 fn add_edit_breakpoint_block(
10901 &mut self,
10902 anchor: Anchor,
10903 breakpoint: &Breakpoint,
10904 edit_action: BreakpointPromptEditAction,
10905 window: &mut Window,
10906 cx: &mut Context<Self>,
10907 ) {
10908 let weak_editor = cx.weak_entity();
10909 let bp_prompt = cx.new(|cx| {
10910 BreakpointPromptEditor::new(
10911 weak_editor,
10912 anchor,
10913 breakpoint.clone(),
10914 edit_action,
10915 window,
10916 cx,
10917 )
10918 });
10919
10920 let height = bp_prompt.update(cx, |this, cx| {
10921 this.prompt
10922 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10923 });
10924 let cloned_prompt = bp_prompt.clone();
10925 let blocks = vec![BlockProperties {
10926 style: BlockStyle::Sticky,
10927 placement: BlockPlacement::Above(anchor),
10928 height: Some(height),
10929 render: Arc::new(move |cx| {
10930 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10931 cloned_prompt.clone().into_any_element()
10932 }),
10933 priority: 0,
10934 }];
10935
10936 let focus_handle = bp_prompt.focus_handle(cx);
10937 window.focus(&focus_handle);
10938
10939 let block_ids = self.insert_blocks(blocks, None, cx);
10940 bp_prompt.update(cx, |prompt, _| {
10941 prompt.add_block_ids(block_ids);
10942 });
10943 }
10944
10945 pub(crate) fn breakpoint_at_row(
10946 &self,
10947 row: u32,
10948 window: &mut Window,
10949 cx: &mut Context<Self>,
10950 ) -> Option<(Anchor, Breakpoint)> {
10951 let snapshot = self.snapshot(window, cx);
10952 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10953
10954 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10955 }
10956
10957 pub(crate) fn breakpoint_at_anchor(
10958 &self,
10959 breakpoint_position: Anchor,
10960 snapshot: &EditorSnapshot,
10961 cx: &mut Context<Self>,
10962 ) -> Option<(Anchor, Breakpoint)> {
10963 let buffer = self
10964 .buffer
10965 .read(cx)
10966 .buffer_for_anchor(breakpoint_position, cx)?;
10967
10968 let enclosing_excerpt = breakpoint_position.excerpt_id;
10969 let buffer_snapshot = buffer.read(cx).snapshot();
10970
10971 let row = buffer_snapshot
10972 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10973 .row;
10974
10975 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10976 let anchor_end = snapshot
10977 .buffer_snapshot()
10978 .anchor_after(Point::new(row, line_len));
10979
10980 self.breakpoint_store
10981 .as_ref()?
10982 .read_with(cx, |breakpoint_store, cx| {
10983 breakpoint_store
10984 .breakpoints(
10985 &buffer,
10986 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10987 &buffer_snapshot,
10988 cx,
10989 )
10990 .next()
10991 .and_then(|(bp, _)| {
10992 let breakpoint_row = buffer_snapshot
10993 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10994 .row;
10995
10996 if breakpoint_row == row {
10997 snapshot
10998 .buffer_snapshot()
10999 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11000 .map(|position| (position, bp.bp.clone()))
11001 } else {
11002 None
11003 }
11004 })
11005 })
11006 }
11007
11008 pub fn edit_log_breakpoint(
11009 &mut self,
11010 _: &EditLogBreakpoint,
11011 window: &mut Window,
11012 cx: &mut Context<Self>,
11013 ) {
11014 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11015 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11016 message: None,
11017 state: BreakpointState::Enabled,
11018 condition: None,
11019 hit_condition: None,
11020 });
11021
11022 self.add_edit_breakpoint_block(
11023 anchor,
11024 &breakpoint,
11025 BreakpointPromptEditAction::Log,
11026 window,
11027 cx,
11028 );
11029 }
11030 }
11031
11032 fn breakpoints_at_cursors(
11033 &self,
11034 window: &mut Window,
11035 cx: &mut Context<Self>,
11036 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11037 let snapshot = self.snapshot(window, cx);
11038 let cursors = self
11039 .selections
11040 .disjoint_anchors_arc()
11041 .iter()
11042 .map(|selection| {
11043 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11044
11045 let breakpoint_position = self
11046 .breakpoint_at_row(cursor_position.row, window, cx)
11047 .map(|bp| bp.0)
11048 .unwrap_or_else(|| {
11049 snapshot
11050 .display_snapshot
11051 .buffer_snapshot()
11052 .anchor_after(Point::new(cursor_position.row, 0))
11053 });
11054
11055 let breakpoint = self
11056 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11057 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11058
11059 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11060 })
11061 // 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.
11062 .collect::<HashMap<Anchor, _>>();
11063
11064 cursors.into_iter().collect()
11065 }
11066
11067 pub fn enable_breakpoint(
11068 &mut self,
11069 _: &crate::actions::EnableBreakpoint,
11070 window: &mut Window,
11071 cx: &mut Context<Self>,
11072 ) {
11073 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11074 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11075 continue;
11076 };
11077 self.edit_breakpoint_at_anchor(
11078 anchor,
11079 breakpoint,
11080 BreakpointEditAction::InvertState,
11081 cx,
11082 );
11083 }
11084 }
11085
11086 pub fn disable_breakpoint(
11087 &mut self,
11088 _: &crate::actions::DisableBreakpoint,
11089 window: &mut Window,
11090 cx: &mut Context<Self>,
11091 ) {
11092 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11093 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11094 continue;
11095 };
11096 self.edit_breakpoint_at_anchor(
11097 anchor,
11098 breakpoint,
11099 BreakpointEditAction::InvertState,
11100 cx,
11101 );
11102 }
11103 }
11104
11105 pub fn toggle_breakpoint(
11106 &mut self,
11107 _: &crate::actions::ToggleBreakpoint,
11108 window: &mut Window,
11109 cx: &mut Context<Self>,
11110 ) {
11111 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11112 if let Some(breakpoint) = breakpoint {
11113 self.edit_breakpoint_at_anchor(
11114 anchor,
11115 breakpoint,
11116 BreakpointEditAction::Toggle,
11117 cx,
11118 );
11119 } else {
11120 self.edit_breakpoint_at_anchor(
11121 anchor,
11122 Breakpoint::new_standard(),
11123 BreakpointEditAction::Toggle,
11124 cx,
11125 );
11126 }
11127 }
11128 }
11129
11130 pub fn edit_breakpoint_at_anchor(
11131 &mut self,
11132 breakpoint_position: Anchor,
11133 breakpoint: Breakpoint,
11134 edit_action: BreakpointEditAction,
11135 cx: &mut Context<Self>,
11136 ) {
11137 let Some(breakpoint_store) = &self.breakpoint_store else {
11138 return;
11139 };
11140
11141 let Some(buffer) = self
11142 .buffer
11143 .read(cx)
11144 .buffer_for_anchor(breakpoint_position, cx)
11145 else {
11146 return;
11147 };
11148
11149 breakpoint_store.update(cx, |breakpoint_store, cx| {
11150 breakpoint_store.toggle_breakpoint(
11151 buffer,
11152 BreakpointWithPosition {
11153 position: breakpoint_position.text_anchor,
11154 bp: breakpoint,
11155 },
11156 edit_action,
11157 cx,
11158 );
11159 });
11160
11161 cx.notify();
11162 }
11163
11164 #[cfg(any(test, feature = "test-support"))]
11165 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11166 self.breakpoint_store.clone()
11167 }
11168
11169 pub fn prepare_restore_change(
11170 &self,
11171 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11172 hunk: &MultiBufferDiffHunk,
11173 cx: &mut App,
11174 ) -> Option<()> {
11175 if hunk.is_created_file() {
11176 return None;
11177 }
11178 let buffer = self.buffer.read(cx);
11179 let diff = buffer.diff_for(hunk.buffer_id)?;
11180 let buffer = buffer.buffer(hunk.buffer_id)?;
11181 let buffer = buffer.read(cx);
11182 let original_text = diff
11183 .read(cx)
11184 .base_text()
11185 .as_rope()
11186 .slice(hunk.diff_base_byte_range.clone());
11187 let buffer_snapshot = buffer.snapshot();
11188 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11189 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11190 probe
11191 .0
11192 .start
11193 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11194 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11195 }) {
11196 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11197 Some(())
11198 } else {
11199 None
11200 }
11201 }
11202
11203 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11204 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11205 }
11206
11207 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11208 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11209 }
11210
11211 fn manipulate_lines<M>(
11212 &mut self,
11213 window: &mut Window,
11214 cx: &mut Context<Self>,
11215 mut manipulate: M,
11216 ) where
11217 M: FnMut(&str) -> LineManipulationResult,
11218 {
11219 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11220
11221 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11222 let buffer = self.buffer.read(cx).snapshot(cx);
11223
11224 let mut edits = Vec::new();
11225
11226 let selections = self.selections.all::<Point>(&display_map);
11227 let mut selections = selections.iter().peekable();
11228 let mut contiguous_row_selections = Vec::new();
11229 let mut new_selections = Vec::new();
11230 let mut added_lines = 0;
11231 let mut removed_lines = 0;
11232
11233 while let Some(selection) = selections.next() {
11234 let (start_row, end_row) = consume_contiguous_rows(
11235 &mut contiguous_row_selections,
11236 selection,
11237 &display_map,
11238 &mut selections,
11239 );
11240
11241 let start_point = Point::new(start_row.0, 0);
11242 let end_point = Point::new(
11243 end_row.previous_row().0,
11244 buffer.line_len(end_row.previous_row()),
11245 );
11246 let text = buffer
11247 .text_for_range(start_point..end_point)
11248 .collect::<String>();
11249
11250 let LineManipulationResult {
11251 new_text,
11252 line_count_before,
11253 line_count_after,
11254 } = manipulate(&text);
11255
11256 edits.push((start_point..end_point, new_text));
11257
11258 // Selections must change based on added and removed line count
11259 let start_row =
11260 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11261 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11262 new_selections.push(Selection {
11263 id: selection.id,
11264 start: start_row,
11265 end: end_row,
11266 goal: SelectionGoal::None,
11267 reversed: selection.reversed,
11268 });
11269
11270 if line_count_after > line_count_before {
11271 added_lines += line_count_after - line_count_before;
11272 } else if line_count_before > line_count_after {
11273 removed_lines += line_count_before - line_count_after;
11274 }
11275 }
11276
11277 self.transact(window, cx, |this, window, cx| {
11278 let buffer = this.buffer.update(cx, |buffer, cx| {
11279 buffer.edit(edits, None, cx);
11280 buffer.snapshot(cx)
11281 });
11282
11283 // Recalculate offsets on newly edited buffer
11284 let new_selections = new_selections
11285 .iter()
11286 .map(|s| {
11287 let start_point = Point::new(s.start.0, 0);
11288 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11289 Selection {
11290 id: s.id,
11291 start: buffer.point_to_offset(start_point),
11292 end: buffer.point_to_offset(end_point),
11293 goal: s.goal,
11294 reversed: s.reversed,
11295 }
11296 })
11297 .collect();
11298
11299 this.change_selections(Default::default(), window, cx, |s| {
11300 s.select(new_selections);
11301 });
11302
11303 this.request_autoscroll(Autoscroll::fit(), cx);
11304 });
11305 }
11306
11307 fn manipulate_immutable_lines<Fn>(
11308 &mut self,
11309 window: &mut Window,
11310 cx: &mut Context<Self>,
11311 mut callback: Fn,
11312 ) where
11313 Fn: FnMut(&mut Vec<&str>),
11314 {
11315 self.manipulate_lines(window, cx, |text| {
11316 let mut lines: Vec<&str> = text.split('\n').collect();
11317 let line_count_before = lines.len();
11318
11319 callback(&mut lines);
11320
11321 LineManipulationResult {
11322 new_text: lines.join("\n"),
11323 line_count_before,
11324 line_count_after: lines.len(),
11325 }
11326 });
11327 }
11328
11329 fn manipulate_mutable_lines<Fn>(
11330 &mut self,
11331 window: &mut Window,
11332 cx: &mut Context<Self>,
11333 mut callback: Fn,
11334 ) where
11335 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11336 {
11337 self.manipulate_lines(window, cx, |text| {
11338 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11339 let line_count_before = lines.len();
11340
11341 callback(&mut lines);
11342
11343 LineManipulationResult {
11344 new_text: lines.join("\n"),
11345 line_count_before,
11346 line_count_after: lines.len(),
11347 }
11348 });
11349 }
11350
11351 pub fn convert_indentation_to_spaces(
11352 &mut self,
11353 _: &ConvertIndentationToSpaces,
11354 window: &mut Window,
11355 cx: &mut Context<Self>,
11356 ) {
11357 let settings = self.buffer.read(cx).language_settings(cx);
11358 let tab_size = settings.tab_size.get() as usize;
11359
11360 self.manipulate_mutable_lines(window, cx, |lines| {
11361 // Allocates a reasonably sized scratch buffer once for the whole loop
11362 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11363 // Avoids recomputing spaces that could be inserted many times
11364 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11365 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11366 .collect();
11367
11368 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11369 let mut chars = line.as_ref().chars();
11370 let mut col = 0;
11371 let mut changed = false;
11372
11373 for ch in chars.by_ref() {
11374 match ch {
11375 ' ' => {
11376 reindented_line.push(' ');
11377 col += 1;
11378 }
11379 '\t' => {
11380 // \t are converted to spaces depending on the current column
11381 let spaces_len = tab_size - (col % tab_size);
11382 reindented_line.extend(&space_cache[spaces_len - 1]);
11383 col += spaces_len;
11384 changed = true;
11385 }
11386 _ => {
11387 // If we dont append before break, the character is consumed
11388 reindented_line.push(ch);
11389 break;
11390 }
11391 }
11392 }
11393
11394 if !changed {
11395 reindented_line.clear();
11396 continue;
11397 }
11398 // Append the rest of the line and replace old reference with new one
11399 reindented_line.extend(chars);
11400 *line = Cow::Owned(reindented_line.clone());
11401 reindented_line.clear();
11402 }
11403 });
11404 }
11405
11406 pub fn convert_indentation_to_tabs(
11407 &mut self,
11408 _: &ConvertIndentationToTabs,
11409 window: &mut Window,
11410 cx: &mut Context<Self>,
11411 ) {
11412 let settings = self.buffer.read(cx).language_settings(cx);
11413 let tab_size = settings.tab_size.get() as usize;
11414
11415 self.manipulate_mutable_lines(window, cx, |lines| {
11416 // Allocates a reasonably sized buffer once for the whole loop
11417 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11418 // Avoids recomputing spaces that could be inserted many times
11419 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11420 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11421 .collect();
11422
11423 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11424 let mut chars = line.chars();
11425 let mut spaces_count = 0;
11426 let mut first_non_indent_char = None;
11427 let mut changed = false;
11428
11429 for ch in chars.by_ref() {
11430 match ch {
11431 ' ' => {
11432 // Keep track of spaces. Append \t when we reach tab_size
11433 spaces_count += 1;
11434 changed = true;
11435 if spaces_count == tab_size {
11436 reindented_line.push('\t');
11437 spaces_count = 0;
11438 }
11439 }
11440 '\t' => {
11441 reindented_line.push('\t');
11442 spaces_count = 0;
11443 }
11444 _ => {
11445 // Dont append it yet, we might have remaining spaces
11446 first_non_indent_char = Some(ch);
11447 break;
11448 }
11449 }
11450 }
11451
11452 if !changed {
11453 reindented_line.clear();
11454 continue;
11455 }
11456 // Remaining spaces that didn't make a full tab stop
11457 if spaces_count > 0 {
11458 reindented_line.extend(&space_cache[spaces_count - 1]);
11459 }
11460 // If we consume an extra character that was not indentation, add it back
11461 if let Some(extra_char) = first_non_indent_char {
11462 reindented_line.push(extra_char);
11463 }
11464 // Append the rest of the line and replace old reference with new one
11465 reindented_line.extend(chars);
11466 *line = Cow::Owned(reindented_line.clone());
11467 reindented_line.clear();
11468 }
11469 });
11470 }
11471
11472 pub fn convert_to_upper_case(
11473 &mut self,
11474 _: &ConvertToUpperCase,
11475 window: &mut Window,
11476 cx: &mut Context<Self>,
11477 ) {
11478 self.manipulate_text(window, cx, |text| text.to_uppercase())
11479 }
11480
11481 pub fn convert_to_lower_case(
11482 &mut self,
11483 _: &ConvertToLowerCase,
11484 window: &mut Window,
11485 cx: &mut Context<Self>,
11486 ) {
11487 self.manipulate_text(window, cx, |text| text.to_lowercase())
11488 }
11489
11490 pub fn convert_to_title_case(
11491 &mut self,
11492 _: &ConvertToTitleCase,
11493 window: &mut Window,
11494 cx: &mut Context<Self>,
11495 ) {
11496 self.manipulate_text(window, cx, |text| {
11497 text.split('\n')
11498 .map(|line| line.to_case(Case::Title))
11499 .join("\n")
11500 })
11501 }
11502
11503 pub fn convert_to_snake_case(
11504 &mut self,
11505 _: &ConvertToSnakeCase,
11506 window: &mut Window,
11507 cx: &mut Context<Self>,
11508 ) {
11509 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11510 }
11511
11512 pub fn convert_to_kebab_case(
11513 &mut self,
11514 _: &ConvertToKebabCase,
11515 window: &mut Window,
11516 cx: &mut Context<Self>,
11517 ) {
11518 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11519 }
11520
11521 pub fn convert_to_upper_camel_case(
11522 &mut self,
11523 _: &ConvertToUpperCamelCase,
11524 window: &mut Window,
11525 cx: &mut Context<Self>,
11526 ) {
11527 self.manipulate_text(window, cx, |text| {
11528 text.split('\n')
11529 .map(|line| line.to_case(Case::UpperCamel))
11530 .join("\n")
11531 })
11532 }
11533
11534 pub fn convert_to_lower_camel_case(
11535 &mut self,
11536 _: &ConvertToLowerCamelCase,
11537 window: &mut Window,
11538 cx: &mut Context<Self>,
11539 ) {
11540 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11541 }
11542
11543 pub fn convert_to_opposite_case(
11544 &mut self,
11545 _: &ConvertToOppositeCase,
11546 window: &mut Window,
11547 cx: &mut Context<Self>,
11548 ) {
11549 self.manipulate_text(window, cx, |text| {
11550 text.chars()
11551 .fold(String::with_capacity(text.len()), |mut t, c| {
11552 if c.is_uppercase() {
11553 t.extend(c.to_lowercase());
11554 } else {
11555 t.extend(c.to_uppercase());
11556 }
11557 t
11558 })
11559 })
11560 }
11561
11562 pub fn convert_to_sentence_case(
11563 &mut self,
11564 _: &ConvertToSentenceCase,
11565 window: &mut Window,
11566 cx: &mut Context<Self>,
11567 ) {
11568 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11569 }
11570
11571 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11572 self.manipulate_text(window, cx, |text| {
11573 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11574 if has_upper_case_characters {
11575 text.to_lowercase()
11576 } else {
11577 text.to_uppercase()
11578 }
11579 })
11580 }
11581
11582 pub fn convert_to_rot13(
11583 &mut self,
11584 _: &ConvertToRot13,
11585 window: &mut Window,
11586 cx: &mut Context<Self>,
11587 ) {
11588 self.manipulate_text(window, cx, |text| {
11589 text.chars()
11590 .map(|c| match c {
11591 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11592 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11593 _ => c,
11594 })
11595 .collect()
11596 })
11597 }
11598
11599 pub fn convert_to_rot47(
11600 &mut self,
11601 _: &ConvertToRot47,
11602 window: &mut Window,
11603 cx: &mut Context<Self>,
11604 ) {
11605 self.manipulate_text(window, cx, |text| {
11606 text.chars()
11607 .map(|c| {
11608 let code_point = c as u32;
11609 if code_point >= 33 && code_point <= 126 {
11610 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11611 }
11612 c
11613 })
11614 .collect()
11615 })
11616 }
11617
11618 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11619 where
11620 Fn: FnMut(&str) -> String,
11621 {
11622 let buffer = self.buffer.read(cx).snapshot(cx);
11623
11624 let mut new_selections = Vec::new();
11625 let mut edits = Vec::new();
11626 let mut selection_adjustment = 0i32;
11627
11628 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11629 let selection_is_empty = selection.is_empty();
11630
11631 let (start, end) = if selection_is_empty {
11632 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11633 (word_range.start, word_range.end)
11634 } else {
11635 (
11636 buffer.point_to_offset(selection.start),
11637 buffer.point_to_offset(selection.end),
11638 )
11639 };
11640
11641 let text = buffer.text_for_range(start..end).collect::<String>();
11642 let old_length = text.len() as i32;
11643 let text = callback(&text);
11644
11645 new_selections.push(Selection {
11646 start: (start as i32 - selection_adjustment) as usize,
11647 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11648 goal: SelectionGoal::None,
11649 id: selection.id,
11650 reversed: selection.reversed,
11651 });
11652
11653 selection_adjustment += old_length - text.len() as i32;
11654
11655 edits.push((start..end, text));
11656 }
11657
11658 self.transact(window, cx, |this, window, cx| {
11659 this.buffer.update(cx, |buffer, cx| {
11660 buffer.edit(edits, None, cx);
11661 });
11662
11663 this.change_selections(Default::default(), window, cx, |s| {
11664 s.select(new_selections);
11665 });
11666
11667 this.request_autoscroll(Autoscroll::fit(), cx);
11668 });
11669 }
11670
11671 pub fn move_selection_on_drop(
11672 &mut self,
11673 selection: &Selection<Anchor>,
11674 target: DisplayPoint,
11675 is_cut: bool,
11676 window: &mut Window,
11677 cx: &mut Context<Self>,
11678 ) {
11679 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11680 let buffer = display_map.buffer_snapshot();
11681 let mut edits = Vec::new();
11682 let insert_point = display_map
11683 .clip_point(target, Bias::Left)
11684 .to_point(&display_map);
11685 let text = buffer
11686 .text_for_range(selection.start..selection.end)
11687 .collect::<String>();
11688 if is_cut {
11689 edits.push(((selection.start..selection.end), String::new()));
11690 }
11691 let insert_anchor = buffer.anchor_before(insert_point);
11692 edits.push(((insert_anchor..insert_anchor), text));
11693 let last_edit_start = insert_anchor.bias_left(buffer);
11694 let last_edit_end = insert_anchor.bias_right(buffer);
11695 self.transact(window, cx, |this, window, cx| {
11696 this.buffer.update(cx, |buffer, cx| {
11697 buffer.edit(edits, None, cx);
11698 });
11699 this.change_selections(Default::default(), window, cx, |s| {
11700 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11701 });
11702 });
11703 }
11704
11705 pub fn clear_selection_drag_state(&mut self) {
11706 self.selection_drag_state = SelectionDragState::None;
11707 }
11708
11709 pub fn duplicate(
11710 &mut self,
11711 upwards: bool,
11712 whole_lines: bool,
11713 window: &mut Window,
11714 cx: &mut Context<Self>,
11715 ) {
11716 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11717
11718 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11719 let buffer = display_map.buffer_snapshot();
11720 let selections = self.selections.all::<Point>(&display_map);
11721
11722 let mut edits = Vec::new();
11723 let mut selections_iter = selections.iter().peekable();
11724 while let Some(selection) = selections_iter.next() {
11725 let mut rows = selection.spanned_rows(false, &display_map);
11726 // duplicate line-wise
11727 if whole_lines || selection.start == selection.end {
11728 // Avoid duplicating the same lines twice.
11729 while let Some(next_selection) = selections_iter.peek() {
11730 let next_rows = next_selection.spanned_rows(false, &display_map);
11731 if next_rows.start < rows.end {
11732 rows.end = next_rows.end;
11733 selections_iter.next().unwrap();
11734 } else {
11735 break;
11736 }
11737 }
11738
11739 // Copy the text from the selected row region and splice it either at the start
11740 // or end of the region.
11741 let start = Point::new(rows.start.0, 0);
11742 let end = Point::new(
11743 rows.end.previous_row().0,
11744 buffer.line_len(rows.end.previous_row()),
11745 );
11746
11747 let mut text = buffer.text_for_range(start..end).collect::<String>();
11748
11749 let insert_location = if upwards {
11750 // When duplicating upward, we need to insert before the current line.
11751 // If we're on the last line and it doesn't end with a newline,
11752 // we need to add a newline before the duplicated content.
11753 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11754 && buffer.max_point().column > 0
11755 && !text.ends_with('\n');
11756
11757 if needs_leading_newline {
11758 text.insert(0, '\n');
11759 end
11760 } else {
11761 text.push('\n');
11762 Point::new(rows.start.0, 0)
11763 }
11764 } else {
11765 text.push('\n');
11766 start
11767 };
11768 edits.push((insert_location..insert_location, text));
11769 } else {
11770 // duplicate character-wise
11771 let start = selection.start;
11772 let end = selection.end;
11773 let text = buffer.text_for_range(start..end).collect::<String>();
11774 edits.push((selection.end..selection.end, text));
11775 }
11776 }
11777
11778 self.transact(window, cx, |this, window, cx| {
11779 this.buffer.update(cx, |buffer, cx| {
11780 buffer.edit(edits, None, cx);
11781 });
11782
11783 // When duplicating upward with whole lines, move the cursor to the duplicated line
11784 if upwards && whole_lines {
11785 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11786
11787 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11788 let mut new_ranges = Vec::new();
11789 let selections = s.all::<Point>(&display_map);
11790 let mut selections_iter = selections.iter().peekable();
11791
11792 while let Some(first_selection) = selections_iter.next() {
11793 // Group contiguous selections together to find the total row span
11794 let mut group_selections = vec![first_selection];
11795 let mut rows = first_selection.spanned_rows(false, &display_map);
11796
11797 while let Some(next_selection) = selections_iter.peek() {
11798 let next_rows = next_selection.spanned_rows(false, &display_map);
11799 if next_rows.start < rows.end {
11800 rows.end = next_rows.end;
11801 group_selections.push(selections_iter.next().unwrap());
11802 } else {
11803 break;
11804 }
11805 }
11806
11807 let row_count = rows.end.0 - rows.start.0;
11808
11809 // Move all selections in this group up by the total number of duplicated rows
11810 for selection in group_selections {
11811 let new_start = Point::new(
11812 selection.start.row.saturating_sub(row_count),
11813 selection.start.column,
11814 );
11815
11816 let new_end = Point::new(
11817 selection.end.row.saturating_sub(row_count),
11818 selection.end.column,
11819 );
11820
11821 new_ranges.push(new_start..new_end);
11822 }
11823 }
11824
11825 s.select_ranges(new_ranges);
11826 });
11827 }
11828
11829 this.request_autoscroll(Autoscroll::fit(), cx);
11830 });
11831 }
11832
11833 pub fn duplicate_line_up(
11834 &mut self,
11835 _: &DuplicateLineUp,
11836 window: &mut Window,
11837 cx: &mut Context<Self>,
11838 ) {
11839 self.duplicate(true, true, window, cx);
11840 }
11841
11842 pub fn duplicate_line_down(
11843 &mut self,
11844 _: &DuplicateLineDown,
11845 window: &mut Window,
11846 cx: &mut Context<Self>,
11847 ) {
11848 self.duplicate(false, true, window, cx);
11849 }
11850
11851 pub fn duplicate_selection(
11852 &mut self,
11853 _: &DuplicateSelection,
11854 window: &mut Window,
11855 cx: &mut Context<Self>,
11856 ) {
11857 self.duplicate(false, false, window, cx);
11858 }
11859
11860 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11861 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11862 if self.mode.is_single_line() {
11863 cx.propagate();
11864 return;
11865 }
11866
11867 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11868 let buffer = self.buffer.read(cx).snapshot(cx);
11869
11870 let mut edits = Vec::new();
11871 let mut unfold_ranges = Vec::new();
11872 let mut refold_creases = Vec::new();
11873
11874 let selections = self.selections.all::<Point>(&display_map);
11875 let mut selections = selections.iter().peekable();
11876 let mut contiguous_row_selections = Vec::new();
11877 let mut new_selections = Vec::new();
11878
11879 while let Some(selection) = selections.next() {
11880 // Find all the selections that span a contiguous row range
11881 let (start_row, end_row) = consume_contiguous_rows(
11882 &mut contiguous_row_selections,
11883 selection,
11884 &display_map,
11885 &mut selections,
11886 );
11887
11888 // Move the text spanned by the row range to be before the line preceding the row range
11889 if start_row.0 > 0 {
11890 let range_to_move = Point::new(
11891 start_row.previous_row().0,
11892 buffer.line_len(start_row.previous_row()),
11893 )
11894 ..Point::new(
11895 end_row.previous_row().0,
11896 buffer.line_len(end_row.previous_row()),
11897 );
11898 let insertion_point = display_map
11899 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11900 .0;
11901
11902 // Don't move lines across excerpts
11903 if buffer
11904 .excerpt_containing(insertion_point..range_to_move.end)
11905 .is_some()
11906 {
11907 let text = buffer
11908 .text_for_range(range_to_move.clone())
11909 .flat_map(|s| s.chars())
11910 .skip(1)
11911 .chain(['\n'])
11912 .collect::<String>();
11913
11914 edits.push((
11915 buffer.anchor_after(range_to_move.start)
11916 ..buffer.anchor_before(range_to_move.end),
11917 String::new(),
11918 ));
11919 let insertion_anchor = buffer.anchor_after(insertion_point);
11920 edits.push((insertion_anchor..insertion_anchor, text));
11921
11922 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11923
11924 // Move selections up
11925 new_selections.extend(contiguous_row_selections.drain(..).map(
11926 |mut selection| {
11927 selection.start.row -= row_delta;
11928 selection.end.row -= row_delta;
11929 selection
11930 },
11931 ));
11932
11933 // Move folds up
11934 unfold_ranges.push(range_to_move.clone());
11935 for fold in display_map.folds_in_range(
11936 buffer.anchor_before(range_to_move.start)
11937 ..buffer.anchor_after(range_to_move.end),
11938 ) {
11939 let mut start = fold.range.start.to_point(&buffer);
11940 let mut end = fold.range.end.to_point(&buffer);
11941 start.row -= row_delta;
11942 end.row -= row_delta;
11943 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11944 }
11945 }
11946 }
11947
11948 // If we didn't move line(s), preserve the existing selections
11949 new_selections.append(&mut contiguous_row_selections);
11950 }
11951
11952 self.transact(window, cx, |this, window, cx| {
11953 this.unfold_ranges(&unfold_ranges, true, true, cx);
11954 this.buffer.update(cx, |buffer, cx| {
11955 for (range, text) in edits {
11956 buffer.edit([(range, text)], None, cx);
11957 }
11958 });
11959 this.fold_creases(refold_creases, true, window, cx);
11960 this.change_selections(Default::default(), window, cx, |s| {
11961 s.select(new_selections);
11962 })
11963 });
11964 }
11965
11966 pub fn move_line_down(
11967 &mut self,
11968 _: &MoveLineDown,
11969 window: &mut Window,
11970 cx: &mut Context<Self>,
11971 ) {
11972 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11973 if self.mode.is_single_line() {
11974 cx.propagate();
11975 return;
11976 }
11977
11978 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11979 let buffer = self.buffer.read(cx).snapshot(cx);
11980
11981 let mut edits = Vec::new();
11982 let mut unfold_ranges = Vec::new();
11983 let mut refold_creases = Vec::new();
11984
11985 let selections = self.selections.all::<Point>(&display_map);
11986 let mut selections = selections.iter().peekable();
11987 let mut contiguous_row_selections = Vec::new();
11988 let mut new_selections = Vec::new();
11989
11990 while let Some(selection) = selections.next() {
11991 // Find all the selections that span a contiguous row range
11992 let (start_row, end_row) = consume_contiguous_rows(
11993 &mut contiguous_row_selections,
11994 selection,
11995 &display_map,
11996 &mut selections,
11997 );
11998
11999 // Move the text spanned by the row range to be after the last line of the row range
12000 if end_row.0 <= buffer.max_point().row {
12001 let range_to_move =
12002 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12003 let insertion_point = display_map
12004 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12005 .0;
12006
12007 // Don't move lines across excerpt boundaries
12008 if buffer
12009 .excerpt_containing(range_to_move.start..insertion_point)
12010 .is_some()
12011 {
12012 let mut text = String::from("\n");
12013 text.extend(buffer.text_for_range(range_to_move.clone()));
12014 text.pop(); // Drop trailing newline
12015 edits.push((
12016 buffer.anchor_after(range_to_move.start)
12017 ..buffer.anchor_before(range_to_move.end),
12018 String::new(),
12019 ));
12020 let insertion_anchor = buffer.anchor_after(insertion_point);
12021 edits.push((insertion_anchor..insertion_anchor, text));
12022
12023 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12024
12025 // Move selections down
12026 new_selections.extend(contiguous_row_selections.drain(..).map(
12027 |mut selection| {
12028 selection.start.row += row_delta;
12029 selection.end.row += row_delta;
12030 selection
12031 },
12032 ));
12033
12034 // Move folds down
12035 unfold_ranges.push(range_to_move.clone());
12036 for fold in display_map.folds_in_range(
12037 buffer.anchor_before(range_to_move.start)
12038 ..buffer.anchor_after(range_to_move.end),
12039 ) {
12040 let mut start = fold.range.start.to_point(&buffer);
12041 let mut end = fold.range.end.to_point(&buffer);
12042 start.row += row_delta;
12043 end.row += row_delta;
12044 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12045 }
12046 }
12047 }
12048
12049 // If we didn't move line(s), preserve the existing selections
12050 new_selections.append(&mut contiguous_row_selections);
12051 }
12052
12053 self.transact(window, cx, |this, window, cx| {
12054 this.unfold_ranges(&unfold_ranges, true, true, cx);
12055 this.buffer.update(cx, |buffer, cx| {
12056 for (range, text) in edits {
12057 buffer.edit([(range, text)], None, cx);
12058 }
12059 });
12060 this.fold_creases(refold_creases, true, window, cx);
12061 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12062 });
12063 }
12064
12065 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12066 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12067 let text_layout_details = &self.text_layout_details(window);
12068 self.transact(window, cx, |this, window, cx| {
12069 let edits = this.change_selections(Default::default(), window, cx, |s| {
12070 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12071 s.move_with(|display_map, selection| {
12072 if !selection.is_empty() {
12073 return;
12074 }
12075
12076 let mut head = selection.head();
12077 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12078 if head.column() == display_map.line_len(head.row()) {
12079 transpose_offset = display_map
12080 .buffer_snapshot()
12081 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12082 }
12083
12084 if transpose_offset == 0 {
12085 return;
12086 }
12087
12088 *head.column_mut() += 1;
12089 head = display_map.clip_point(head, Bias::Right);
12090 let goal = SelectionGoal::HorizontalPosition(
12091 display_map
12092 .x_for_display_point(head, text_layout_details)
12093 .into(),
12094 );
12095 selection.collapse_to(head, goal);
12096
12097 let transpose_start = display_map
12098 .buffer_snapshot()
12099 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12100 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12101 let transpose_end = display_map
12102 .buffer_snapshot()
12103 .clip_offset(transpose_offset + 1, Bias::Right);
12104 if let Some(ch) = display_map
12105 .buffer_snapshot()
12106 .chars_at(transpose_start)
12107 .next()
12108 {
12109 edits.push((transpose_start..transpose_offset, String::new()));
12110 edits.push((transpose_end..transpose_end, ch.to_string()));
12111 }
12112 }
12113 });
12114 edits
12115 });
12116 this.buffer
12117 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12118 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12119 this.change_selections(Default::default(), window, cx, |s| {
12120 s.select(selections);
12121 });
12122 });
12123 }
12124
12125 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12126 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12127 if self.mode.is_single_line() {
12128 cx.propagate();
12129 return;
12130 }
12131
12132 self.rewrap_impl(RewrapOptions::default(), cx)
12133 }
12134
12135 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12136 let buffer = self.buffer.read(cx).snapshot(cx);
12137 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12138
12139 #[derive(Clone, Debug, PartialEq)]
12140 enum CommentFormat {
12141 /// single line comment, with prefix for line
12142 Line(String),
12143 /// single line within a block comment, with prefix for line
12144 BlockLine(String),
12145 /// a single line of a block comment that includes the initial delimiter
12146 BlockCommentWithStart(BlockCommentConfig),
12147 /// a single line of a block comment that includes the ending delimiter
12148 BlockCommentWithEnd(BlockCommentConfig),
12149 }
12150
12151 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12152 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12153 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12154 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12155 .peekable();
12156
12157 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12158 row
12159 } else {
12160 return Vec::new();
12161 };
12162
12163 let language_settings = buffer.language_settings_at(selection.head(), cx);
12164 let language_scope = buffer.language_scope_at(selection.head());
12165
12166 let indent_and_prefix_for_row =
12167 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12168 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12169 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12170 &language_scope
12171 {
12172 let indent_end = Point::new(row, indent.len);
12173 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12174 let line_text_after_indent = buffer
12175 .text_for_range(indent_end..line_end)
12176 .collect::<String>();
12177
12178 let is_within_comment_override = buffer
12179 .language_scope_at(indent_end)
12180 .is_some_and(|scope| scope.override_name() == Some("comment"));
12181 let comment_delimiters = if is_within_comment_override {
12182 // we are within a comment syntax node, but we don't
12183 // yet know what kind of comment: block, doc or line
12184 match (
12185 language_scope.documentation_comment(),
12186 language_scope.block_comment(),
12187 ) {
12188 (Some(config), _) | (_, Some(config))
12189 if buffer.contains_str_at(indent_end, &config.start) =>
12190 {
12191 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12192 }
12193 (Some(config), _) | (_, Some(config))
12194 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12195 {
12196 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12197 }
12198 (Some(config), _) | (_, Some(config))
12199 if buffer.contains_str_at(indent_end, &config.prefix) =>
12200 {
12201 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12202 }
12203 (_, _) => language_scope
12204 .line_comment_prefixes()
12205 .iter()
12206 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12207 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12208 }
12209 } else {
12210 // we not in an overridden comment node, but we may
12211 // be within a non-overridden line comment node
12212 language_scope
12213 .line_comment_prefixes()
12214 .iter()
12215 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12216 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12217 };
12218
12219 let rewrap_prefix = language_scope
12220 .rewrap_prefixes()
12221 .iter()
12222 .find_map(|prefix_regex| {
12223 prefix_regex.find(&line_text_after_indent).map(|mat| {
12224 if mat.start() == 0 {
12225 Some(mat.as_str().to_string())
12226 } else {
12227 None
12228 }
12229 })
12230 })
12231 .flatten();
12232 (comment_delimiters, rewrap_prefix)
12233 } else {
12234 (None, None)
12235 };
12236 (indent, comment_prefix, rewrap_prefix)
12237 };
12238
12239 let mut ranges = Vec::new();
12240 let from_empty_selection = selection.is_empty();
12241
12242 let mut current_range_start = first_row;
12243 let mut prev_row = first_row;
12244 let (
12245 mut current_range_indent,
12246 mut current_range_comment_delimiters,
12247 mut current_range_rewrap_prefix,
12248 ) = indent_and_prefix_for_row(first_row);
12249
12250 for row in non_blank_rows_iter.skip(1) {
12251 let has_paragraph_break = row > prev_row + 1;
12252
12253 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12254 indent_and_prefix_for_row(row);
12255
12256 let has_indent_change = row_indent != current_range_indent;
12257 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12258
12259 let has_boundary_change = has_comment_change
12260 || row_rewrap_prefix.is_some()
12261 || (has_indent_change && current_range_comment_delimiters.is_some());
12262
12263 if has_paragraph_break || has_boundary_change {
12264 ranges.push((
12265 language_settings.clone(),
12266 Point::new(current_range_start, 0)
12267 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12268 current_range_indent,
12269 current_range_comment_delimiters.clone(),
12270 current_range_rewrap_prefix.clone(),
12271 from_empty_selection,
12272 ));
12273 current_range_start = row;
12274 current_range_indent = row_indent;
12275 current_range_comment_delimiters = row_comment_delimiters;
12276 current_range_rewrap_prefix = row_rewrap_prefix;
12277 }
12278 prev_row = row;
12279 }
12280
12281 ranges.push((
12282 language_settings.clone(),
12283 Point::new(current_range_start, 0)
12284 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12285 current_range_indent,
12286 current_range_comment_delimiters,
12287 current_range_rewrap_prefix,
12288 from_empty_selection,
12289 ));
12290
12291 ranges
12292 });
12293
12294 let mut edits = Vec::new();
12295 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12296
12297 for (
12298 language_settings,
12299 wrap_range,
12300 mut indent_size,
12301 comment_prefix,
12302 rewrap_prefix,
12303 from_empty_selection,
12304 ) in wrap_ranges
12305 {
12306 let mut start_row = wrap_range.start.row;
12307 let mut end_row = wrap_range.end.row;
12308
12309 // Skip selections that overlap with a range that has already been rewrapped.
12310 let selection_range = start_row..end_row;
12311 if rewrapped_row_ranges
12312 .iter()
12313 .any(|range| range.overlaps(&selection_range))
12314 {
12315 continue;
12316 }
12317
12318 let tab_size = language_settings.tab_size;
12319
12320 let (line_prefix, inside_comment) = match &comment_prefix {
12321 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12322 (Some(prefix.as_str()), true)
12323 }
12324 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12325 (Some(prefix.as_ref()), true)
12326 }
12327 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12328 start: _,
12329 end: _,
12330 prefix,
12331 tab_size,
12332 })) => {
12333 indent_size.len += tab_size;
12334 (Some(prefix.as_ref()), true)
12335 }
12336 None => (None, false),
12337 };
12338 let indent_prefix = indent_size.chars().collect::<String>();
12339 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12340
12341 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12342 RewrapBehavior::InComments => inside_comment,
12343 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12344 RewrapBehavior::Anywhere => true,
12345 };
12346
12347 let should_rewrap = options.override_language_settings
12348 || allow_rewrap_based_on_language
12349 || self.hard_wrap.is_some();
12350 if !should_rewrap {
12351 continue;
12352 }
12353
12354 if from_empty_selection {
12355 'expand_upwards: while start_row > 0 {
12356 let prev_row = start_row - 1;
12357 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12358 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12359 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12360 {
12361 start_row = prev_row;
12362 } else {
12363 break 'expand_upwards;
12364 }
12365 }
12366
12367 'expand_downwards: while end_row < buffer.max_point().row {
12368 let next_row = end_row + 1;
12369 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12370 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12371 && !buffer.is_line_blank(MultiBufferRow(next_row))
12372 {
12373 end_row = next_row;
12374 } else {
12375 break 'expand_downwards;
12376 }
12377 }
12378 }
12379
12380 let start = Point::new(start_row, 0);
12381 let start_offset = ToOffset::to_offset(&start, &buffer);
12382 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12383 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12384 let mut first_line_delimiter = None;
12385 let mut last_line_delimiter = None;
12386 let Some(lines_without_prefixes) = selection_text
12387 .lines()
12388 .enumerate()
12389 .map(|(ix, line)| {
12390 let line_trimmed = line.trim_start();
12391 if rewrap_prefix.is_some() && ix > 0 {
12392 Ok(line_trimmed)
12393 } else if let Some(
12394 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12395 start,
12396 prefix,
12397 end,
12398 tab_size,
12399 })
12400 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12401 start,
12402 prefix,
12403 end,
12404 tab_size,
12405 }),
12406 ) = &comment_prefix
12407 {
12408 let line_trimmed = line_trimmed
12409 .strip_prefix(start.as_ref())
12410 .map(|s| {
12411 let mut indent_size = indent_size;
12412 indent_size.len -= tab_size;
12413 let indent_prefix: String = indent_size.chars().collect();
12414 first_line_delimiter = Some((indent_prefix, start));
12415 s.trim_start()
12416 })
12417 .unwrap_or(line_trimmed);
12418 let line_trimmed = line_trimmed
12419 .strip_suffix(end.as_ref())
12420 .map(|s| {
12421 last_line_delimiter = Some(end);
12422 s.trim_end()
12423 })
12424 .unwrap_or(line_trimmed);
12425 let line_trimmed = line_trimmed
12426 .strip_prefix(prefix.as_ref())
12427 .unwrap_or(line_trimmed);
12428 Ok(line_trimmed)
12429 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12430 line_trimmed.strip_prefix(prefix).with_context(|| {
12431 format!("line did not start with prefix {prefix:?}: {line:?}")
12432 })
12433 } else {
12434 line_trimmed
12435 .strip_prefix(&line_prefix.trim_start())
12436 .with_context(|| {
12437 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12438 })
12439 }
12440 })
12441 .collect::<Result<Vec<_>, _>>()
12442 .log_err()
12443 else {
12444 continue;
12445 };
12446
12447 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12448 buffer
12449 .language_settings_at(Point::new(start_row, 0), cx)
12450 .preferred_line_length as usize
12451 });
12452
12453 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12454 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12455 } else {
12456 line_prefix.clone()
12457 };
12458
12459 let wrapped_text = {
12460 let mut wrapped_text = wrap_with_prefix(
12461 line_prefix,
12462 subsequent_lines_prefix,
12463 lines_without_prefixes.join("\n"),
12464 wrap_column,
12465 tab_size,
12466 options.preserve_existing_whitespace,
12467 );
12468
12469 if let Some((indent, delimiter)) = first_line_delimiter {
12470 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12471 }
12472 if let Some(last_line) = last_line_delimiter {
12473 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12474 }
12475
12476 wrapped_text
12477 };
12478
12479 // TODO: should always use char-based diff while still supporting cursor behavior that
12480 // matches vim.
12481 let mut diff_options = DiffOptions::default();
12482 if options.override_language_settings {
12483 diff_options.max_word_diff_len = 0;
12484 diff_options.max_word_diff_line_count = 0;
12485 } else {
12486 diff_options.max_word_diff_len = usize::MAX;
12487 diff_options.max_word_diff_line_count = usize::MAX;
12488 }
12489
12490 for (old_range, new_text) in
12491 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12492 {
12493 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12494 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12495 edits.push((edit_start..edit_end, new_text));
12496 }
12497
12498 rewrapped_row_ranges.push(start_row..=end_row);
12499 }
12500
12501 self.buffer
12502 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12503 }
12504
12505 pub fn cut_common(
12506 &mut self,
12507 cut_no_selection_line: bool,
12508 window: &mut Window,
12509 cx: &mut Context<Self>,
12510 ) -> ClipboardItem {
12511 let mut text = String::new();
12512 let buffer = self.buffer.read(cx).snapshot(cx);
12513 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12514 let mut clipboard_selections = Vec::with_capacity(selections.len());
12515 {
12516 let max_point = buffer.max_point();
12517 let mut is_first = true;
12518 for selection in &mut selections {
12519 let is_entire_line =
12520 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12521 if is_entire_line {
12522 selection.start = Point::new(selection.start.row, 0);
12523 if !selection.is_empty() && selection.end.column == 0 {
12524 selection.end = cmp::min(max_point, selection.end);
12525 } else {
12526 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12527 }
12528 selection.goal = SelectionGoal::None;
12529 }
12530 if is_first {
12531 is_first = false;
12532 } else {
12533 text += "\n";
12534 }
12535 let mut len = 0;
12536 for chunk in buffer.text_for_range(selection.start..selection.end) {
12537 text.push_str(chunk);
12538 len += chunk.len();
12539 }
12540 clipboard_selections.push(ClipboardSelection {
12541 len,
12542 is_entire_line,
12543 first_line_indent: buffer
12544 .indent_size_for_line(MultiBufferRow(selection.start.row))
12545 .len,
12546 });
12547 }
12548 }
12549
12550 self.transact(window, cx, |this, window, cx| {
12551 this.change_selections(Default::default(), window, cx, |s| {
12552 s.select(selections);
12553 });
12554 this.insert("", window, cx);
12555 });
12556 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12557 }
12558
12559 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12560 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12561 let item = self.cut_common(true, window, cx);
12562 cx.write_to_clipboard(item);
12563 }
12564
12565 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12566 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12567 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12568 s.move_with(|snapshot, sel| {
12569 if sel.is_empty() {
12570 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12571 }
12572 if sel.is_empty() {
12573 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12574 }
12575 });
12576 });
12577 let item = self.cut_common(false, window, cx);
12578 cx.set_global(KillRing(item))
12579 }
12580
12581 pub fn kill_ring_yank(
12582 &mut self,
12583 _: &KillRingYank,
12584 window: &mut Window,
12585 cx: &mut Context<Self>,
12586 ) {
12587 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12588 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12589 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12590 (kill_ring.text().to_string(), kill_ring.metadata_json())
12591 } else {
12592 return;
12593 }
12594 } else {
12595 return;
12596 };
12597 self.do_paste(&text, metadata, false, window, cx);
12598 }
12599
12600 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12601 self.do_copy(true, cx);
12602 }
12603
12604 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12605 self.do_copy(false, cx);
12606 }
12607
12608 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12609 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12610 let buffer = self.buffer.read(cx).read(cx);
12611 let mut text = String::new();
12612
12613 let mut clipboard_selections = Vec::with_capacity(selections.len());
12614 {
12615 let max_point = buffer.max_point();
12616 let mut is_first = true;
12617 for selection in &selections {
12618 let mut start = selection.start;
12619 let mut end = selection.end;
12620 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12621 let mut add_trailing_newline = false;
12622 if is_entire_line {
12623 start = Point::new(start.row, 0);
12624 let next_line_start = Point::new(end.row + 1, 0);
12625 if next_line_start <= max_point {
12626 end = next_line_start;
12627 } else {
12628 // We're on the last line without a trailing newline.
12629 // Copy to the end of the line and add a newline afterwards.
12630 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12631 add_trailing_newline = true;
12632 }
12633 }
12634
12635 let mut trimmed_selections = Vec::new();
12636 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12637 let row = MultiBufferRow(start.row);
12638 let first_indent = buffer.indent_size_for_line(row);
12639 if first_indent.len == 0 || start.column > first_indent.len {
12640 trimmed_selections.push(start..end);
12641 } else {
12642 trimmed_selections.push(
12643 Point::new(row.0, first_indent.len)
12644 ..Point::new(row.0, buffer.line_len(row)),
12645 );
12646 for row in start.row + 1..=end.row {
12647 let mut line_len = buffer.line_len(MultiBufferRow(row));
12648 if row == end.row {
12649 line_len = end.column;
12650 }
12651 if line_len == 0 {
12652 trimmed_selections
12653 .push(Point::new(row, 0)..Point::new(row, line_len));
12654 continue;
12655 }
12656 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12657 if row_indent_size.len >= first_indent.len {
12658 trimmed_selections.push(
12659 Point::new(row, first_indent.len)..Point::new(row, line_len),
12660 );
12661 } else {
12662 trimmed_selections.clear();
12663 trimmed_selections.push(start..end);
12664 break;
12665 }
12666 }
12667 }
12668 } else {
12669 trimmed_selections.push(start..end);
12670 }
12671
12672 for trimmed_range in trimmed_selections {
12673 if is_first {
12674 is_first = false;
12675 } else {
12676 text += "\n";
12677 }
12678 let mut len = 0;
12679 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12680 text.push_str(chunk);
12681 len += chunk.len();
12682 }
12683 if add_trailing_newline {
12684 text.push('\n');
12685 len += 1;
12686 }
12687 clipboard_selections.push(ClipboardSelection {
12688 len,
12689 is_entire_line,
12690 first_line_indent: buffer
12691 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12692 .len,
12693 });
12694 }
12695 }
12696 }
12697
12698 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12699 text,
12700 clipboard_selections,
12701 ));
12702 }
12703
12704 pub fn do_paste(
12705 &mut self,
12706 text: &String,
12707 clipboard_selections: Option<Vec<ClipboardSelection>>,
12708 handle_entire_lines: bool,
12709 window: &mut Window,
12710 cx: &mut Context<Self>,
12711 ) {
12712 if self.read_only(cx) {
12713 return;
12714 }
12715
12716 let clipboard_text = Cow::Borrowed(text.as_str());
12717
12718 self.transact(window, cx, |this, window, cx| {
12719 let had_active_edit_prediction = this.has_active_edit_prediction();
12720 let display_map = this.display_snapshot(cx);
12721 let old_selections = this.selections.all::<usize>(&display_map);
12722 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12723
12724 if let Some(mut clipboard_selections) = clipboard_selections {
12725 let all_selections_were_entire_line =
12726 clipboard_selections.iter().all(|s| s.is_entire_line);
12727 let first_selection_indent_column =
12728 clipboard_selections.first().map(|s| s.first_line_indent);
12729 if clipboard_selections.len() != old_selections.len() {
12730 clipboard_selections.drain(..);
12731 }
12732 let mut auto_indent_on_paste = true;
12733
12734 this.buffer.update(cx, |buffer, cx| {
12735 let snapshot = buffer.read(cx);
12736 auto_indent_on_paste = snapshot
12737 .language_settings_at(cursor_offset, cx)
12738 .auto_indent_on_paste;
12739
12740 let mut start_offset = 0;
12741 let mut edits = Vec::new();
12742 let mut original_indent_columns = Vec::new();
12743 for (ix, selection) in old_selections.iter().enumerate() {
12744 let to_insert;
12745 let entire_line;
12746 let original_indent_column;
12747 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12748 let end_offset = start_offset + clipboard_selection.len;
12749 to_insert = &clipboard_text[start_offset..end_offset];
12750 entire_line = clipboard_selection.is_entire_line;
12751 start_offset = end_offset + 1;
12752 original_indent_column = Some(clipboard_selection.first_line_indent);
12753 } else {
12754 to_insert = &*clipboard_text;
12755 entire_line = all_selections_were_entire_line;
12756 original_indent_column = first_selection_indent_column
12757 }
12758
12759 let (range, to_insert) =
12760 if selection.is_empty() && handle_entire_lines && entire_line {
12761 // If the corresponding selection was empty when this slice of the
12762 // clipboard text was written, then the entire line containing the
12763 // selection was copied. If this selection is also currently empty,
12764 // then paste the line before the current line of the buffer.
12765 let column = selection.start.to_point(&snapshot).column as usize;
12766 let line_start = selection.start - column;
12767 (line_start..line_start, Cow::Borrowed(to_insert))
12768 } else {
12769 let language = snapshot.language_at(selection.head());
12770 let range = selection.range();
12771 if let Some(language) = language
12772 && language.name() == "Markdown".into()
12773 {
12774 edit_for_markdown_paste(
12775 &snapshot,
12776 range,
12777 to_insert,
12778 url::Url::parse(to_insert).ok(),
12779 )
12780 } else {
12781 (range, Cow::Borrowed(to_insert))
12782 }
12783 };
12784
12785 edits.push((range, to_insert));
12786 original_indent_columns.push(original_indent_column);
12787 }
12788 drop(snapshot);
12789
12790 buffer.edit(
12791 edits,
12792 if auto_indent_on_paste {
12793 Some(AutoindentMode::Block {
12794 original_indent_columns,
12795 })
12796 } else {
12797 None
12798 },
12799 cx,
12800 );
12801 });
12802
12803 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12804 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12805 } else {
12806 let url = url::Url::parse(&clipboard_text).ok();
12807
12808 let auto_indent_mode = if !clipboard_text.is_empty() {
12809 Some(AutoindentMode::Block {
12810 original_indent_columns: Vec::new(),
12811 })
12812 } else {
12813 None
12814 };
12815
12816 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12817 let snapshot = buffer.snapshot(cx);
12818
12819 let anchors = old_selections
12820 .iter()
12821 .map(|s| {
12822 let anchor = snapshot.anchor_after(s.head());
12823 s.map(|_| anchor)
12824 })
12825 .collect::<Vec<_>>();
12826
12827 let mut edits = Vec::new();
12828
12829 for selection in old_selections.iter() {
12830 let language = snapshot.language_at(selection.head());
12831 let range = selection.range();
12832
12833 let (edit_range, edit_text) = if let Some(language) = language
12834 && language.name() == "Markdown".into()
12835 {
12836 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12837 } else {
12838 (range, clipboard_text.clone())
12839 };
12840
12841 edits.push((edit_range, edit_text));
12842 }
12843
12844 drop(snapshot);
12845 buffer.edit(edits, auto_indent_mode, cx);
12846
12847 anchors
12848 });
12849
12850 this.change_selections(Default::default(), window, cx, |s| {
12851 s.select_anchors(selection_anchors);
12852 });
12853 }
12854
12855 let trigger_in_words =
12856 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12857
12858 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12859 });
12860 }
12861
12862 pub fn diff_clipboard_with_selection(
12863 &mut self,
12864 _: &DiffClipboardWithSelection,
12865 window: &mut Window,
12866 cx: &mut Context<Self>,
12867 ) {
12868 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12869
12870 if selections.is_empty() {
12871 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12872 return;
12873 };
12874
12875 let clipboard_text = match cx.read_from_clipboard() {
12876 Some(item) => match item.entries().first() {
12877 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12878 _ => None,
12879 },
12880 None => None,
12881 };
12882
12883 let Some(clipboard_text) = clipboard_text else {
12884 log::warn!("Clipboard doesn't contain text.");
12885 return;
12886 };
12887
12888 window.dispatch_action(
12889 Box::new(DiffClipboardWithSelectionData {
12890 clipboard_text,
12891 editor: cx.entity(),
12892 }),
12893 cx,
12894 );
12895 }
12896
12897 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12898 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12899 if let Some(item) = cx.read_from_clipboard() {
12900 let entries = item.entries();
12901
12902 match entries.first() {
12903 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12904 // of all the pasted entries.
12905 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12906 .do_paste(
12907 clipboard_string.text(),
12908 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12909 true,
12910 window,
12911 cx,
12912 ),
12913 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12914 }
12915 }
12916 }
12917
12918 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12919 if self.read_only(cx) {
12920 return;
12921 }
12922
12923 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12924
12925 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12926 if let Some((selections, _)) =
12927 self.selection_history.transaction(transaction_id).cloned()
12928 {
12929 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12930 s.select_anchors(selections.to_vec());
12931 });
12932 } else {
12933 log::error!(
12934 "No entry in selection_history found for undo. \
12935 This may correspond to a bug where undo does not update the selection. \
12936 If this is occurring, please add details to \
12937 https://github.com/zed-industries/zed/issues/22692"
12938 );
12939 }
12940 self.request_autoscroll(Autoscroll::fit(), cx);
12941 self.unmark_text(window, cx);
12942 self.refresh_edit_prediction(true, false, window, cx);
12943 cx.emit(EditorEvent::Edited { transaction_id });
12944 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12945 }
12946 }
12947
12948 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12949 if self.read_only(cx) {
12950 return;
12951 }
12952
12953 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12954
12955 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12956 if let Some((_, Some(selections))) =
12957 self.selection_history.transaction(transaction_id).cloned()
12958 {
12959 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12960 s.select_anchors(selections.to_vec());
12961 });
12962 } else {
12963 log::error!(
12964 "No entry in selection_history found for redo. \
12965 This may correspond to a bug where undo does not update the selection. \
12966 If this is occurring, please add details to \
12967 https://github.com/zed-industries/zed/issues/22692"
12968 );
12969 }
12970 self.request_autoscroll(Autoscroll::fit(), cx);
12971 self.unmark_text(window, cx);
12972 self.refresh_edit_prediction(true, false, window, cx);
12973 cx.emit(EditorEvent::Edited { transaction_id });
12974 }
12975 }
12976
12977 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12978 self.buffer
12979 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12980 }
12981
12982 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12983 self.buffer
12984 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12985 }
12986
12987 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12988 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12989 self.change_selections(Default::default(), window, cx, |s| {
12990 s.move_with(|map, selection| {
12991 let cursor = if selection.is_empty() {
12992 movement::left(map, selection.start)
12993 } else {
12994 selection.start
12995 };
12996 selection.collapse_to(cursor, SelectionGoal::None);
12997 });
12998 })
12999 }
13000
13001 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13002 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13003 self.change_selections(Default::default(), window, cx, |s| {
13004 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13005 })
13006 }
13007
13008 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13009 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13010 self.change_selections(Default::default(), window, cx, |s| {
13011 s.move_with(|map, selection| {
13012 let cursor = if selection.is_empty() {
13013 movement::right(map, selection.end)
13014 } else {
13015 selection.end
13016 };
13017 selection.collapse_to(cursor, SelectionGoal::None)
13018 });
13019 })
13020 }
13021
13022 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13023 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13024 self.change_selections(Default::default(), window, cx, |s| {
13025 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13026 });
13027 }
13028
13029 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13030 if self.take_rename(true, window, cx).is_some() {
13031 return;
13032 }
13033
13034 if self.mode.is_single_line() {
13035 cx.propagate();
13036 return;
13037 }
13038
13039 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13040
13041 let text_layout_details = &self.text_layout_details(window);
13042 let selection_count = self.selections.count();
13043 let first_selection = self.selections.first_anchor();
13044
13045 self.change_selections(Default::default(), window, cx, |s| {
13046 s.move_with(|map, selection| {
13047 if !selection.is_empty() {
13048 selection.goal = SelectionGoal::None;
13049 }
13050 let (cursor, goal) = movement::up(
13051 map,
13052 selection.start,
13053 selection.goal,
13054 false,
13055 text_layout_details,
13056 );
13057 selection.collapse_to(cursor, goal);
13058 });
13059 });
13060
13061 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13062 {
13063 cx.propagate();
13064 }
13065 }
13066
13067 pub fn move_up_by_lines(
13068 &mut self,
13069 action: &MoveUpByLines,
13070 window: &mut Window,
13071 cx: &mut Context<Self>,
13072 ) {
13073 if self.take_rename(true, window, cx).is_some() {
13074 return;
13075 }
13076
13077 if self.mode.is_single_line() {
13078 cx.propagate();
13079 return;
13080 }
13081
13082 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13083
13084 let text_layout_details = &self.text_layout_details(window);
13085
13086 self.change_selections(Default::default(), window, cx, |s| {
13087 s.move_with(|map, selection| {
13088 if !selection.is_empty() {
13089 selection.goal = SelectionGoal::None;
13090 }
13091 let (cursor, goal) = movement::up_by_rows(
13092 map,
13093 selection.start,
13094 action.lines,
13095 selection.goal,
13096 false,
13097 text_layout_details,
13098 );
13099 selection.collapse_to(cursor, goal);
13100 });
13101 })
13102 }
13103
13104 pub fn move_down_by_lines(
13105 &mut self,
13106 action: &MoveDownByLines,
13107 window: &mut Window,
13108 cx: &mut Context<Self>,
13109 ) {
13110 if self.take_rename(true, window, cx).is_some() {
13111 return;
13112 }
13113
13114 if self.mode.is_single_line() {
13115 cx.propagate();
13116 return;
13117 }
13118
13119 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13120
13121 let text_layout_details = &self.text_layout_details(window);
13122
13123 self.change_selections(Default::default(), window, cx, |s| {
13124 s.move_with(|map, selection| {
13125 if !selection.is_empty() {
13126 selection.goal = SelectionGoal::None;
13127 }
13128 let (cursor, goal) = movement::down_by_rows(
13129 map,
13130 selection.start,
13131 action.lines,
13132 selection.goal,
13133 false,
13134 text_layout_details,
13135 );
13136 selection.collapse_to(cursor, goal);
13137 });
13138 })
13139 }
13140
13141 pub fn select_down_by_lines(
13142 &mut self,
13143 action: &SelectDownByLines,
13144 window: &mut Window,
13145 cx: &mut Context<Self>,
13146 ) {
13147 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13148 let text_layout_details = &self.text_layout_details(window);
13149 self.change_selections(Default::default(), window, cx, |s| {
13150 s.move_heads_with(|map, head, goal| {
13151 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13152 })
13153 })
13154 }
13155
13156 pub fn select_up_by_lines(
13157 &mut self,
13158 action: &SelectUpByLines,
13159 window: &mut Window,
13160 cx: &mut Context<Self>,
13161 ) {
13162 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13163 let text_layout_details = &self.text_layout_details(window);
13164 self.change_selections(Default::default(), window, cx, |s| {
13165 s.move_heads_with(|map, head, goal| {
13166 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13167 })
13168 })
13169 }
13170
13171 pub fn select_page_up(
13172 &mut self,
13173 _: &SelectPageUp,
13174 window: &mut Window,
13175 cx: &mut Context<Self>,
13176 ) {
13177 let Some(row_count) = self.visible_row_count() else {
13178 return;
13179 };
13180
13181 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13182
13183 let text_layout_details = &self.text_layout_details(window);
13184
13185 self.change_selections(Default::default(), window, cx, |s| {
13186 s.move_heads_with(|map, head, goal| {
13187 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13188 })
13189 })
13190 }
13191
13192 pub fn move_page_up(
13193 &mut self,
13194 action: &MovePageUp,
13195 window: &mut Window,
13196 cx: &mut Context<Self>,
13197 ) {
13198 if self.take_rename(true, window, cx).is_some() {
13199 return;
13200 }
13201
13202 if self
13203 .context_menu
13204 .borrow_mut()
13205 .as_mut()
13206 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13207 .unwrap_or(false)
13208 {
13209 return;
13210 }
13211
13212 if matches!(self.mode, EditorMode::SingleLine) {
13213 cx.propagate();
13214 return;
13215 }
13216
13217 let Some(row_count) = self.visible_row_count() else {
13218 return;
13219 };
13220
13221 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13222
13223 let effects = if action.center_cursor {
13224 SelectionEffects::scroll(Autoscroll::center())
13225 } else {
13226 SelectionEffects::default()
13227 };
13228
13229 let text_layout_details = &self.text_layout_details(window);
13230
13231 self.change_selections(effects, window, cx, |s| {
13232 s.move_with(|map, selection| {
13233 if !selection.is_empty() {
13234 selection.goal = SelectionGoal::None;
13235 }
13236 let (cursor, goal) = movement::up_by_rows(
13237 map,
13238 selection.end,
13239 row_count,
13240 selection.goal,
13241 false,
13242 text_layout_details,
13243 );
13244 selection.collapse_to(cursor, goal);
13245 });
13246 });
13247 }
13248
13249 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13250 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13251 let text_layout_details = &self.text_layout_details(window);
13252 self.change_selections(Default::default(), window, cx, |s| {
13253 s.move_heads_with(|map, head, goal| {
13254 movement::up(map, head, goal, false, text_layout_details)
13255 })
13256 })
13257 }
13258
13259 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13260 self.take_rename(true, window, cx);
13261
13262 if self.mode.is_single_line() {
13263 cx.propagate();
13264 return;
13265 }
13266
13267 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13268
13269 let text_layout_details = &self.text_layout_details(window);
13270 let selection_count = self.selections.count();
13271 let first_selection = self.selections.first_anchor();
13272
13273 self.change_selections(Default::default(), window, cx, |s| {
13274 s.move_with(|map, selection| {
13275 if !selection.is_empty() {
13276 selection.goal = SelectionGoal::None;
13277 }
13278 let (cursor, goal) = movement::down(
13279 map,
13280 selection.end,
13281 selection.goal,
13282 false,
13283 text_layout_details,
13284 );
13285 selection.collapse_to(cursor, goal);
13286 });
13287 });
13288
13289 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13290 {
13291 cx.propagate();
13292 }
13293 }
13294
13295 pub fn select_page_down(
13296 &mut self,
13297 _: &SelectPageDown,
13298 window: &mut Window,
13299 cx: &mut Context<Self>,
13300 ) {
13301 let Some(row_count) = self.visible_row_count() else {
13302 return;
13303 };
13304
13305 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13306
13307 let text_layout_details = &self.text_layout_details(window);
13308
13309 self.change_selections(Default::default(), window, cx, |s| {
13310 s.move_heads_with(|map, head, goal| {
13311 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13312 })
13313 })
13314 }
13315
13316 pub fn move_page_down(
13317 &mut self,
13318 action: &MovePageDown,
13319 window: &mut Window,
13320 cx: &mut Context<Self>,
13321 ) {
13322 if self.take_rename(true, window, cx).is_some() {
13323 return;
13324 }
13325
13326 if self
13327 .context_menu
13328 .borrow_mut()
13329 .as_mut()
13330 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13331 .unwrap_or(false)
13332 {
13333 return;
13334 }
13335
13336 if matches!(self.mode, EditorMode::SingleLine) {
13337 cx.propagate();
13338 return;
13339 }
13340
13341 let Some(row_count) = self.visible_row_count() else {
13342 return;
13343 };
13344
13345 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13346
13347 let effects = if action.center_cursor {
13348 SelectionEffects::scroll(Autoscroll::center())
13349 } else {
13350 SelectionEffects::default()
13351 };
13352
13353 let text_layout_details = &self.text_layout_details(window);
13354 self.change_selections(effects, window, cx, |s| {
13355 s.move_with(|map, selection| {
13356 if !selection.is_empty() {
13357 selection.goal = SelectionGoal::None;
13358 }
13359 let (cursor, goal) = movement::down_by_rows(
13360 map,
13361 selection.end,
13362 row_count,
13363 selection.goal,
13364 false,
13365 text_layout_details,
13366 );
13367 selection.collapse_to(cursor, goal);
13368 });
13369 });
13370 }
13371
13372 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13373 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13374 let text_layout_details = &self.text_layout_details(window);
13375 self.change_selections(Default::default(), window, cx, |s| {
13376 s.move_heads_with(|map, head, goal| {
13377 movement::down(map, head, goal, false, text_layout_details)
13378 })
13379 });
13380 }
13381
13382 pub fn context_menu_first(
13383 &mut self,
13384 _: &ContextMenuFirst,
13385 window: &mut Window,
13386 cx: &mut Context<Self>,
13387 ) {
13388 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13389 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13390 }
13391 }
13392
13393 pub fn context_menu_prev(
13394 &mut self,
13395 _: &ContextMenuPrevious,
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_prev(self.completion_provider.as_deref(), window, cx);
13401 }
13402 }
13403
13404 pub fn context_menu_next(
13405 &mut self,
13406 _: &ContextMenuNext,
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_next(self.completion_provider.as_deref(), window, cx);
13412 }
13413 }
13414
13415 pub fn context_menu_last(
13416 &mut self,
13417 _: &ContextMenuLast,
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_last(self.completion_provider.as_deref(), window, cx);
13423 }
13424 }
13425
13426 pub fn signature_help_prev(
13427 &mut self,
13428 _: &SignatureHelpPrevious,
13429 _: &mut Window,
13430 cx: &mut Context<Self>,
13431 ) {
13432 if let Some(popover) = self.signature_help_state.popover_mut() {
13433 if popover.current_signature == 0 {
13434 popover.current_signature = popover.signatures.len() - 1;
13435 } else {
13436 popover.current_signature -= 1;
13437 }
13438 cx.notify();
13439 }
13440 }
13441
13442 pub fn signature_help_next(
13443 &mut self,
13444 _: &SignatureHelpNext,
13445 _: &mut Window,
13446 cx: &mut Context<Self>,
13447 ) {
13448 if let Some(popover) = self.signature_help_state.popover_mut() {
13449 if popover.current_signature + 1 == popover.signatures.len() {
13450 popover.current_signature = 0;
13451 } else {
13452 popover.current_signature += 1;
13453 }
13454 cx.notify();
13455 }
13456 }
13457
13458 pub fn move_to_previous_word_start(
13459 &mut self,
13460 _: &MoveToPreviousWordStart,
13461 window: &mut Window,
13462 cx: &mut Context<Self>,
13463 ) {
13464 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13465 self.change_selections(Default::default(), window, cx, |s| {
13466 s.move_cursors_with(|map, head, _| {
13467 (
13468 movement::previous_word_start(map, head),
13469 SelectionGoal::None,
13470 )
13471 });
13472 })
13473 }
13474
13475 pub fn move_to_previous_subword_start(
13476 &mut self,
13477 _: &MoveToPreviousSubwordStart,
13478 window: &mut Window,
13479 cx: &mut Context<Self>,
13480 ) {
13481 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13482 self.change_selections(Default::default(), window, cx, |s| {
13483 s.move_cursors_with(|map, head, _| {
13484 (
13485 movement::previous_subword_start(map, head),
13486 SelectionGoal::None,
13487 )
13488 });
13489 })
13490 }
13491
13492 pub fn select_to_previous_word_start(
13493 &mut self,
13494 _: &SelectToPreviousWordStart,
13495 window: &mut Window,
13496 cx: &mut Context<Self>,
13497 ) {
13498 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13499 self.change_selections(Default::default(), window, cx, |s| {
13500 s.move_heads_with(|map, head, _| {
13501 (
13502 movement::previous_word_start(map, head),
13503 SelectionGoal::None,
13504 )
13505 });
13506 })
13507 }
13508
13509 pub fn select_to_previous_subword_start(
13510 &mut self,
13511 _: &SelectToPreviousSubwordStart,
13512 window: &mut Window,
13513 cx: &mut Context<Self>,
13514 ) {
13515 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13516 self.change_selections(Default::default(), window, cx, |s| {
13517 s.move_heads_with(|map, head, _| {
13518 (
13519 movement::previous_subword_start(map, head),
13520 SelectionGoal::None,
13521 )
13522 });
13523 })
13524 }
13525
13526 pub fn delete_to_previous_word_start(
13527 &mut self,
13528 action: &DeleteToPreviousWordStart,
13529 window: &mut Window,
13530 cx: &mut Context<Self>,
13531 ) {
13532 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13533 self.transact(window, cx, |this, window, cx| {
13534 this.select_autoclose_pair(window, cx);
13535 this.change_selections(Default::default(), window, cx, |s| {
13536 s.move_with(|map, selection| {
13537 if selection.is_empty() {
13538 let mut cursor = if action.ignore_newlines {
13539 movement::previous_word_start(map, selection.head())
13540 } else {
13541 movement::previous_word_start_or_newline(map, selection.head())
13542 };
13543 cursor = movement::adjust_greedy_deletion(
13544 map,
13545 selection.head(),
13546 cursor,
13547 action.ignore_brackets,
13548 );
13549 selection.set_head(cursor, SelectionGoal::None);
13550 }
13551 });
13552 });
13553 this.insert("", window, cx);
13554 });
13555 }
13556
13557 pub fn delete_to_previous_subword_start(
13558 &mut self,
13559 _: &DeleteToPreviousSubwordStart,
13560 window: &mut Window,
13561 cx: &mut Context<Self>,
13562 ) {
13563 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13564 self.transact(window, cx, |this, window, cx| {
13565 this.select_autoclose_pair(window, cx);
13566 this.change_selections(Default::default(), window, cx, |s| {
13567 s.move_with(|map, selection| {
13568 if selection.is_empty() {
13569 let mut cursor = movement::previous_subword_start(map, selection.head());
13570 cursor =
13571 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13572 selection.set_head(cursor, SelectionGoal::None);
13573 }
13574 });
13575 });
13576 this.insert("", window, cx);
13577 });
13578 }
13579
13580 pub fn move_to_next_word_end(
13581 &mut self,
13582 _: &MoveToNextWordEnd,
13583 window: &mut Window,
13584 cx: &mut Context<Self>,
13585 ) {
13586 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13587 self.change_selections(Default::default(), window, cx, |s| {
13588 s.move_cursors_with(|map, head, _| {
13589 (movement::next_word_end(map, head), SelectionGoal::None)
13590 });
13591 })
13592 }
13593
13594 pub fn move_to_next_subword_end(
13595 &mut self,
13596 _: &MoveToNextSubwordEnd,
13597 window: &mut Window,
13598 cx: &mut Context<Self>,
13599 ) {
13600 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13601 self.change_selections(Default::default(), window, cx, |s| {
13602 s.move_cursors_with(|map, head, _| {
13603 (movement::next_subword_end(map, head), SelectionGoal::None)
13604 });
13605 })
13606 }
13607
13608 pub fn select_to_next_word_end(
13609 &mut self,
13610 _: &SelectToNextWordEnd,
13611 window: &mut Window,
13612 cx: &mut Context<Self>,
13613 ) {
13614 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13615 self.change_selections(Default::default(), window, cx, |s| {
13616 s.move_heads_with(|map, head, _| {
13617 (movement::next_word_end(map, head), SelectionGoal::None)
13618 });
13619 })
13620 }
13621
13622 pub fn select_to_next_subword_end(
13623 &mut self,
13624 _: &SelectToNextSubwordEnd,
13625 window: &mut Window,
13626 cx: &mut Context<Self>,
13627 ) {
13628 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13629 self.change_selections(Default::default(), window, cx, |s| {
13630 s.move_heads_with(|map, head, _| {
13631 (movement::next_subword_end(map, head), SelectionGoal::None)
13632 });
13633 })
13634 }
13635
13636 pub fn delete_to_next_word_end(
13637 &mut self,
13638 action: &DeleteToNextWordEnd,
13639 window: &mut Window,
13640 cx: &mut Context<Self>,
13641 ) {
13642 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13643 self.transact(window, cx, |this, window, cx| {
13644 this.change_selections(Default::default(), window, cx, |s| {
13645 s.move_with(|map, selection| {
13646 if selection.is_empty() {
13647 let mut cursor = if action.ignore_newlines {
13648 movement::next_word_end(map, selection.head())
13649 } else {
13650 movement::next_word_end_or_newline(map, selection.head())
13651 };
13652 cursor = movement::adjust_greedy_deletion(
13653 map,
13654 selection.head(),
13655 cursor,
13656 action.ignore_brackets,
13657 );
13658 selection.set_head(cursor, SelectionGoal::None);
13659 }
13660 });
13661 });
13662 this.insert("", window, cx);
13663 });
13664 }
13665
13666 pub fn delete_to_next_subword_end(
13667 &mut self,
13668 _: &DeleteToNextSubwordEnd,
13669 window: &mut Window,
13670 cx: &mut Context<Self>,
13671 ) {
13672 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13673 self.transact(window, cx, |this, window, cx| {
13674 this.change_selections(Default::default(), window, cx, |s| {
13675 s.move_with(|map, selection| {
13676 if selection.is_empty() {
13677 let mut cursor = movement::next_subword_end(map, selection.head());
13678 cursor =
13679 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13680 selection.set_head(cursor, SelectionGoal::None);
13681 }
13682 });
13683 });
13684 this.insert("", window, cx);
13685 });
13686 }
13687
13688 pub fn move_to_beginning_of_line(
13689 &mut self,
13690 action: &MoveToBeginningOfLine,
13691 window: &mut Window,
13692 cx: &mut Context<Self>,
13693 ) {
13694 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13695 self.change_selections(Default::default(), window, cx, |s| {
13696 s.move_cursors_with(|map, head, _| {
13697 (
13698 movement::indented_line_beginning(
13699 map,
13700 head,
13701 action.stop_at_soft_wraps,
13702 action.stop_at_indent,
13703 ),
13704 SelectionGoal::None,
13705 )
13706 });
13707 })
13708 }
13709
13710 pub fn select_to_beginning_of_line(
13711 &mut self,
13712 action: &SelectToBeginningOfLine,
13713 window: &mut Window,
13714 cx: &mut Context<Self>,
13715 ) {
13716 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13717 self.change_selections(Default::default(), window, cx, |s| {
13718 s.move_heads_with(|map, head, _| {
13719 (
13720 movement::indented_line_beginning(
13721 map,
13722 head,
13723 action.stop_at_soft_wraps,
13724 action.stop_at_indent,
13725 ),
13726 SelectionGoal::None,
13727 )
13728 });
13729 });
13730 }
13731
13732 pub fn delete_to_beginning_of_line(
13733 &mut self,
13734 action: &DeleteToBeginningOfLine,
13735 window: &mut Window,
13736 cx: &mut Context<Self>,
13737 ) {
13738 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13739 self.transact(window, cx, |this, window, cx| {
13740 this.change_selections(Default::default(), window, cx, |s| {
13741 s.move_with(|_, selection| {
13742 selection.reversed = true;
13743 });
13744 });
13745
13746 this.select_to_beginning_of_line(
13747 &SelectToBeginningOfLine {
13748 stop_at_soft_wraps: false,
13749 stop_at_indent: action.stop_at_indent,
13750 },
13751 window,
13752 cx,
13753 );
13754 this.backspace(&Backspace, window, cx);
13755 });
13756 }
13757
13758 pub fn move_to_end_of_line(
13759 &mut self,
13760 action: &MoveToEndOfLine,
13761 window: &mut Window,
13762 cx: &mut Context<Self>,
13763 ) {
13764 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13765 self.change_selections(Default::default(), window, cx, |s| {
13766 s.move_cursors_with(|map, head, _| {
13767 (
13768 movement::line_end(map, head, action.stop_at_soft_wraps),
13769 SelectionGoal::None,
13770 )
13771 });
13772 })
13773 }
13774
13775 pub fn select_to_end_of_line(
13776 &mut self,
13777 action: &SelectToEndOfLine,
13778 window: &mut Window,
13779 cx: &mut Context<Self>,
13780 ) {
13781 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13782 self.change_selections(Default::default(), window, cx, |s| {
13783 s.move_heads_with(|map, head, _| {
13784 (
13785 movement::line_end(map, head, action.stop_at_soft_wraps),
13786 SelectionGoal::None,
13787 )
13788 });
13789 })
13790 }
13791
13792 pub fn delete_to_end_of_line(
13793 &mut self,
13794 _: &DeleteToEndOfLine,
13795 window: &mut Window,
13796 cx: &mut Context<Self>,
13797 ) {
13798 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13799 self.transact(window, cx, |this, window, cx| {
13800 this.select_to_end_of_line(
13801 &SelectToEndOfLine {
13802 stop_at_soft_wraps: false,
13803 },
13804 window,
13805 cx,
13806 );
13807 this.delete(&Delete, window, cx);
13808 });
13809 }
13810
13811 pub fn cut_to_end_of_line(
13812 &mut self,
13813 action: &CutToEndOfLine,
13814 window: &mut Window,
13815 cx: &mut Context<Self>,
13816 ) {
13817 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13818 self.transact(window, cx, |this, window, cx| {
13819 this.select_to_end_of_line(
13820 &SelectToEndOfLine {
13821 stop_at_soft_wraps: false,
13822 },
13823 window,
13824 cx,
13825 );
13826 if !action.stop_at_newlines {
13827 this.change_selections(Default::default(), window, cx, |s| {
13828 s.move_with(|_, sel| {
13829 if sel.is_empty() {
13830 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13831 }
13832 });
13833 });
13834 }
13835 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13836 let item = this.cut_common(false, window, cx);
13837 cx.write_to_clipboard(item);
13838 });
13839 }
13840
13841 pub fn move_to_start_of_paragraph(
13842 &mut self,
13843 _: &MoveToStartOfParagraph,
13844 window: &mut Window,
13845 cx: &mut Context<Self>,
13846 ) {
13847 if matches!(self.mode, EditorMode::SingleLine) {
13848 cx.propagate();
13849 return;
13850 }
13851 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13852 self.change_selections(Default::default(), window, cx, |s| {
13853 s.move_with(|map, selection| {
13854 selection.collapse_to(
13855 movement::start_of_paragraph(map, selection.head(), 1),
13856 SelectionGoal::None,
13857 )
13858 });
13859 })
13860 }
13861
13862 pub fn move_to_end_of_paragraph(
13863 &mut self,
13864 _: &MoveToEndOfParagraph,
13865 window: &mut Window,
13866 cx: &mut Context<Self>,
13867 ) {
13868 if matches!(self.mode, EditorMode::SingleLine) {
13869 cx.propagate();
13870 return;
13871 }
13872 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13873 self.change_selections(Default::default(), window, cx, |s| {
13874 s.move_with(|map, selection| {
13875 selection.collapse_to(
13876 movement::end_of_paragraph(map, selection.head(), 1),
13877 SelectionGoal::None,
13878 )
13879 });
13880 })
13881 }
13882
13883 pub fn select_to_start_of_paragraph(
13884 &mut self,
13885 _: &SelectToStartOfParagraph,
13886 window: &mut Window,
13887 cx: &mut Context<Self>,
13888 ) {
13889 if matches!(self.mode, EditorMode::SingleLine) {
13890 cx.propagate();
13891 return;
13892 }
13893 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13894 self.change_selections(Default::default(), window, cx, |s| {
13895 s.move_heads_with(|map, head, _| {
13896 (
13897 movement::start_of_paragraph(map, head, 1),
13898 SelectionGoal::None,
13899 )
13900 });
13901 })
13902 }
13903
13904 pub fn select_to_end_of_paragraph(
13905 &mut self,
13906 _: &SelectToEndOfParagraph,
13907 window: &mut Window,
13908 cx: &mut Context<Self>,
13909 ) {
13910 if matches!(self.mode, EditorMode::SingleLine) {
13911 cx.propagate();
13912 return;
13913 }
13914 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13915 self.change_selections(Default::default(), window, cx, |s| {
13916 s.move_heads_with(|map, head, _| {
13917 (
13918 movement::end_of_paragraph(map, head, 1),
13919 SelectionGoal::None,
13920 )
13921 });
13922 })
13923 }
13924
13925 pub fn move_to_start_of_excerpt(
13926 &mut self,
13927 _: &MoveToStartOfExcerpt,
13928 window: &mut Window,
13929 cx: &mut Context<Self>,
13930 ) {
13931 if matches!(self.mode, EditorMode::SingleLine) {
13932 cx.propagate();
13933 return;
13934 }
13935 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13936 self.change_selections(Default::default(), window, cx, |s| {
13937 s.move_with(|map, selection| {
13938 selection.collapse_to(
13939 movement::start_of_excerpt(
13940 map,
13941 selection.head(),
13942 workspace::searchable::Direction::Prev,
13943 ),
13944 SelectionGoal::None,
13945 )
13946 });
13947 })
13948 }
13949
13950 pub fn move_to_start_of_next_excerpt(
13951 &mut self,
13952 _: &MoveToStartOfNextExcerpt,
13953 window: &mut Window,
13954 cx: &mut Context<Self>,
13955 ) {
13956 if matches!(self.mode, EditorMode::SingleLine) {
13957 cx.propagate();
13958 return;
13959 }
13960
13961 self.change_selections(Default::default(), window, cx, |s| {
13962 s.move_with(|map, selection| {
13963 selection.collapse_to(
13964 movement::start_of_excerpt(
13965 map,
13966 selection.head(),
13967 workspace::searchable::Direction::Next,
13968 ),
13969 SelectionGoal::None,
13970 )
13971 });
13972 })
13973 }
13974
13975 pub fn move_to_end_of_excerpt(
13976 &mut self,
13977 _: &MoveToEndOfExcerpt,
13978 window: &mut Window,
13979 cx: &mut Context<Self>,
13980 ) {
13981 if matches!(self.mode, EditorMode::SingleLine) {
13982 cx.propagate();
13983 return;
13984 }
13985 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13986 self.change_selections(Default::default(), window, cx, |s| {
13987 s.move_with(|map, selection| {
13988 selection.collapse_to(
13989 movement::end_of_excerpt(
13990 map,
13991 selection.head(),
13992 workspace::searchable::Direction::Next,
13993 ),
13994 SelectionGoal::None,
13995 )
13996 });
13997 })
13998 }
13999
14000 pub fn move_to_end_of_previous_excerpt(
14001 &mut self,
14002 _: &MoveToEndOfPreviousExcerpt,
14003 window: &mut Window,
14004 cx: &mut Context<Self>,
14005 ) {
14006 if matches!(self.mode, EditorMode::SingleLine) {
14007 cx.propagate();
14008 return;
14009 }
14010 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14011 self.change_selections(Default::default(), window, cx, |s| {
14012 s.move_with(|map, selection| {
14013 selection.collapse_to(
14014 movement::end_of_excerpt(
14015 map,
14016 selection.head(),
14017 workspace::searchable::Direction::Prev,
14018 ),
14019 SelectionGoal::None,
14020 )
14021 });
14022 })
14023 }
14024
14025 pub fn select_to_start_of_excerpt(
14026 &mut self,
14027 _: &SelectToStartOfExcerpt,
14028 window: &mut Window,
14029 cx: &mut Context<Self>,
14030 ) {
14031 if matches!(self.mode, EditorMode::SingleLine) {
14032 cx.propagate();
14033 return;
14034 }
14035 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14036 self.change_selections(Default::default(), window, cx, |s| {
14037 s.move_heads_with(|map, head, _| {
14038 (
14039 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14040 SelectionGoal::None,
14041 )
14042 });
14043 })
14044 }
14045
14046 pub fn select_to_start_of_next_excerpt(
14047 &mut self,
14048 _: &SelectToStartOfNextExcerpt,
14049 window: &mut Window,
14050 cx: &mut Context<Self>,
14051 ) {
14052 if matches!(self.mode, EditorMode::SingleLine) {
14053 cx.propagate();
14054 return;
14055 }
14056 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14057 self.change_selections(Default::default(), window, cx, |s| {
14058 s.move_heads_with(|map, head, _| {
14059 (
14060 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14061 SelectionGoal::None,
14062 )
14063 });
14064 })
14065 }
14066
14067 pub fn select_to_end_of_excerpt(
14068 &mut self,
14069 _: &SelectToEndOfExcerpt,
14070 window: &mut Window,
14071 cx: &mut Context<Self>,
14072 ) {
14073 if matches!(self.mode, EditorMode::SingleLine) {
14074 cx.propagate();
14075 return;
14076 }
14077 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14078 self.change_selections(Default::default(), window, cx, |s| {
14079 s.move_heads_with(|map, head, _| {
14080 (
14081 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14082 SelectionGoal::None,
14083 )
14084 });
14085 })
14086 }
14087
14088 pub fn select_to_end_of_previous_excerpt(
14089 &mut self,
14090 _: &SelectToEndOfPreviousExcerpt,
14091 window: &mut Window,
14092 cx: &mut Context<Self>,
14093 ) {
14094 if matches!(self.mode, EditorMode::SingleLine) {
14095 cx.propagate();
14096 return;
14097 }
14098 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14099 self.change_selections(Default::default(), window, cx, |s| {
14100 s.move_heads_with(|map, head, _| {
14101 (
14102 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14103 SelectionGoal::None,
14104 )
14105 });
14106 })
14107 }
14108
14109 pub fn move_to_beginning(
14110 &mut self,
14111 _: &MoveToBeginning,
14112 window: &mut Window,
14113 cx: &mut Context<Self>,
14114 ) {
14115 if matches!(self.mode, EditorMode::SingleLine) {
14116 cx.propagate();
14117 return;
14118 }
14119 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14120 self.change_selections(Default::default(), window, cx, |s| {
14121 s.select_ranges(vec![0..0]);
14122 });
14123 }
14124
14125 pub fn select_to_beginning(
14126 &mut self,
14127 _: &SelectToBeginning,
14128 window: &mut Window,
14129 cx: &mut Context<Self>,
14130 ) {
14131 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14132 selection.set_head(Point::zero(), SelectionGoal::None);
14133 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14134 self.change_selections(Default::default(), window, cx, |s| {
14135 s.select(vec![selection]);
14136 });
14137 }
14138
14139 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14140 if matches!(self.mode, EditorMode::SingleLine) {
14141 cx.propagate();
14142 return;
14143 }
14144 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14145 let cursor = self.buffer.read(cx).read(cx).len();
14146 self.change_selections(Default::default(), window, cx, |s| {
14147 s.select_ranges(vec![cursor..cursor])
14148 });
14149 }
14150
14151 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14152 self.nav_history = nav_history;
14153 }
14154
14155 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14156 self.nav_history.as_ref()
14157 }
14158
14159 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14160 self.push_to_nav_history(
14161 self.selections.newest_anchor().head(),
14162 None,
14163 false,
14164 true,
14165 cx,
14166 );
14167 }
14168
14169 fn push_to_nav_history(
14170 &mut self,
14171 cursor_anchor: Anchor,
14172 new_position: Option<Point>,
14173 is_deactivate: bool,
14174 always: bool,
14175 cx: &mut Context<Self>,
14176 ) {
14177 if let Some(nav_history) = self.nav_history.as_mut() {
14178 let buffer = self.buffer.read(cx).read(cx);
14179 let cursor_position = cursor_anchor.to_point(&buffer);
14180 let scroll_state = self.scroll_manager.anchor();
14181 let scroll_top_row = scroll_state.top_row(&buffer);
14182 drop(buffer);
14183
14184 if let Some(new_position) = new_position {
14185 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14186 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14187 return;
14188 }
14189 }
14190
14191 nav_history.push(
14192 Some(NavigationData {
14193 cursor_anchor,
14194 cursor_position,
14195 scroll_anchor: scroll_state,
14196 scroll_top_row,
14197 }),
14198 cx,
14199 );
14200 cx.emit(EditorEvent::PushedToNavHistory {
14201 anchor: cursor_anchor,
14202 is_deactivate,
14203 })
14204 }
14205 }
14206
14207 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14208 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14209 let buffer = self.buffer.read(cx).snapshot(cx);
14210 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14211 selection.set_head(buffer.len(), SelectionGoal::None);
14212 self.change_selections(Default::default(), window, cx, |s| {
14213 s.select(vec![selection]);
14214 });
14215 }
14216
14217 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14218 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14219 let end = self.buffer.read(cx).read(cx).len();
14220 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14221 s.select_ranges(vec![0..end]);
14222 });
14223 }
14224
14225 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14226 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14227 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14228 let mut selections = self.selections.all::<Point>(&display_map);
14229 let max_point = display_map.buffer_snapshot().max_point();
14230 for selection in &mut selections {
14231 let rows = selection.spanned_rows(true, &display_map);
14232 selection.start = Point::new(rows.start.0, 0);
14233 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14234 selection.reversed = false;
14235 }
14236 self.change_selections(Default::default(), window, cx, |s| {
14237 s.select(selections);
14238 });
14239 }
14240
14241 pub fn split_selection_into_lines(
14242 &mut self,
14243 action: &SplitSelectionIntoLines,
14244 window: &mut Window,
14245 cx: &mut Context<Self>,
14246 ) {
14247 let selections = self
14248 .selections
14249 .all::<Point>(&self.display_snapshot(cx))
14250 .into_iter()
14251 .map(|selection| selection.start..selection.end)
14252 .collect::<Vec<_>>();
14253 self.unfold_ranges(&selections, true, true, cx);
14254
14255 let mut new_selection_ranges = Vec::new();
14256 {
14257 let buffer = self.buffer.read(cx).read(cx);
14258 for selection in selections {
14259 for row in selection.start.row..selection.end.row {
14260 let line_start = Point::new(row, 0);
14261 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14262
14263 if action.keep_selections {
14264 // Keep the selection range for each line
14265 let selection_start = if row == selection.start.row {
14266 selection.start
14267 } else {
14268 line_start
14269 };
14270 new_selection_ranges.push(selection_start..line_end);
14271 } else {
14272 // Collapse to cursor at end of line
14273 new_selection_ranges.push(line_end..line_end);
14274 }
14275 }
14276
14277 let is_multiline_selection = selection.start.row != selection.end.row;
14278 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14279 // so this action feels more ergonomic when paired with other selection operations
14280 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14281 if !should_skip_last {
14282 if action.keep_selections {
14283 if is_multiline_selection {
14284 let line_start = Point::new(selection.end.row, 0);
14285 new_selection_ranges.push(line_start..selection.end);
14286 } else {
14287 new_selection_ranges.push(selection.start..selection.end);
14288 }
14289 } else {
14290 new_selection_ranges.push(selection.end..selection.end);
14291 }
14292 }
14293 }
14294 }
14295 self.change_selections(Default::default(), window, cx, |s| {
14296 s.select_ranges(new_selection_ranges);
14297 });
14298 }
14299
14300 pub fn add_selection_above(
14301 &mut self,
14302 action: &AddSelectionAbove,
14303 window: &mut Window,
14304 cx: &mut Context<Self>,
14305 ) {
14306 self.add_selection(true, action.skip_soft_wrap, window, cx);
14307 }
14308
14309 pub fn add_selection_below(
14310 &mut self,
14311 action: &AddSelectionBelow,
14312 window: &mut Window,
14313 cx: &mut Context<Self>,
14314 ) {
14315 self.add_selection(false, action.skip_soft_wrap, window, cx);
14316 }
14317
14318 fn add_selection(
14319 &mut self,
14320 above: bool,
14321 skip_soft_wrap: bool,
14322 window: &mut Window,
14323 cx: &mut Context<Self>,
14324 ) {
14325 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14326
14327 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14328 let all_selections = self.selections.all::<Point>(&display_map);
14329 let text_layout_details = self.text_layout_details(window);
14330
14331 let (mut columnar_selections, new_selections_to_columnarize) = {
14332 if let Some(state) = self.add_selections_state.as_ref() {
14333 let columnar_selection_ids: HashSet<_> = state
14334 .groups
14335 .iter()
14336 .flat_map(|group| group.stack.iter())
14337 .copied()
14338 .collect();
14339
14340 all_selections
14341 .into_iter()
14342 .partition(|s| columnar_selection_ids.contains(&s.id))
14343 } else {
14344 (Vec::new(), all_selections)
14345 }
14346 };
14347
14348 let mut state = self
14349 .add_selections_state
14350 .take()
14351 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14352
14353 for selection in new_selections_to_columnarize {
14354 let range = selection.display_range(&display_map).sorted();
14355 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14356 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14357 let positions = start_x.min(end_x)..start_x.max(end_x);
14358 let mut stack = Vec::new();
14359 for row in range.start.row().0..=range.end.row().0 {
14360 if let Some(selection) = self.selections.build_columnar_selection(
14361 &display_map,
14362 DisplayRow(row),
14363 &positions,
14364 selection.reversed,
14365 &text_layout_details,
14366 ) {
14367 stack.push(selection.id);
14368 columnar_selections.push(selection);
14369 }
14370 }
14371 if !stack.is_empty() {
14372 if above {
14373 stack.reverse();
14374 }
14375 state.groups.push(AddSelectionsGroup { above, stack });
14376 }
14377 }
14378
14379 let mut final_selections = Vec::new();
14380 let end_row = if above {
14381 DisplayRow(0)
14382 } else {
14383 display_map.max_point().row()
14384 };
14385
14386 let mut last_added_item_per_group = HashMap::default();
14387 for group in state.groups.iter_mut() {
14388 if let Some(last_id) = group.stack.last() {
14389 last_added_item_per_group.insert(*last_id, group);
14390 }
14391 }
14392
14393 for selection in columnar_selections {
14394 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14395 if above == group.above {
14396 let range = selection.display_range(&display_map).sorted();
14397 debug_assert_eq!(range.start.row(), range.end.row());
14398 let mut row = range.start.row();
14399 let positions =
14400 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14401 Pixels::from(start)..Pixels::from(end)
14402 } else {
14403 let start_x =
14404 display_map.x_for_display_point(range.start, &text_layout_details);
14405 let end_x =
14406 display_map.x_for_display_point(range.end, &text_layout_details);
14407 start_x.min(end_x)..start_x.max(end_x)
14408 };
14409
14410 let mut maybe_new_selection = None;
14411 let direction = if above { -1 } else { 1 };
14412
14413 while row != end_row {
14414 if skip_soft_wrap {
14415 row = display_map
14416 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14417 .row();
14418 } else if above {
14419 row.0 -= 1;
14420 } else {
14421 row.0 += 1;
14422 }
14423
14424 if let Some(new_selection) = self.selections.build_columnar_selection(
14425 &display_map,
14426 row,
14427 &positions,
14428 selection.reversed,
14429 &text_layout_details,
14430 ) {
14431 maybe_new_selection = Some(new_selection);
14432 break;
14433 }
14434 }
14435
14436 if let Some(new_selection) = maybe_new_selection {
14437 group.stack.push(new_selection.id);
14438 if above {
14439 final_selections.push(new_selection);
14440 final_selections.push(selection);
14441 } else {
14442 final_selections.push(selection);
14443 final_selections.push(new_selection);
14444 }
14445 } else {
14446 final_selections.push(selection);
14447 }
14448 } else {
14449 group.stack.pop();
14450 }
14451 } else {
14452 final_selections.push(selection);
14453 }
14454 }
14455
14456 self.change_selections(Default::default(), window, cx, |s| {
14457 s.select(final_selections);
14458 });
14459
14460 let final_selection_ids: HashSet<_> = self
14461 .selections
14462 .all::<Point>(&display_map)
14463 .iter()
14464 .map(|s| s.id)
14465 .collect();
14466 state.groups.retain_mut(|group| {
14467 // selections might get merged above so we remove invalid items from stacks
14468 group.stack.retain(|id| final_selection_ids.contains(id));
14469
14470 // single selection in stack can be treated as initial state
14471 group.stack.len() > 1
14472 });
14473
14474 if !state.groups.is_empty() {
14475 self.add_selections_state = Some(state);
14476 }
14477 }
14478
14479 fn select_match_ranges(
14480 &mut self,
14481 range: Range<usize>,
14482 reversed: bool,
14483 replace_newest: bool,
14484 auto_scroll: Option<Autoscroll>,
14485 window: &mut Window,
14486 cx: &mut Context<Editor>,
14487 ) {
14488 self.unfold_ranges(
14489 std::slice::from_ref(&range),
14490 false,
14491 auto_scroll.is_some(),
14492 cx,
14493 );
14494 let effects = if let Some(scroll) = auto_scroll {
14495 SelectionEffects::scroll(scroll)
14496 } else {
14497 SelectionEffects::no_scroll()
14498 };
14499 self.change_selections(effects, window, cx, |s| {
14500 if replace_newest {
14501 s.delete(s.newest_anchor().id);
14502 }
14503 if reversed {
14504 s.insert_range(range.end..range.start);
14505 } else {
14506 s.insert_range(range);
14507 }
14508 });
14509 }
14510
14511 pub fn select_next_match_internal(
14512 &mut self,
14513 display_map: &DisplaySnapshot,
14514 replace_newest: bool,
14515 autoscroll: Option<Autoscroll>,
14516 window: &mut Window,
14517 cx: &mut Context<Self>,
14518 ) -> Result<()> {
14519 let buffer = display_map.buffer_snapshot();
14520 let mut selections = self.selections.all::<usize>(&display_map);
14521 if let Some(mut select_next_state) = self.select_next_state.take() {
14522 let query = &select_next_state.query;
14523 if !select_next_state.done {
14524 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14525 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14526 let mut next_selected_range = None;
14527
14528 let bytes_after_last_selection =
14529 buffer.bytes_in_range(last_selection.end..buffer.len());
14530 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14531 let query_matches = query
14532 .stream_find_iter(bytes_after_last_selection)
14533 .map(|result| (last_selection.end, result))
14534 .chain(
14535 query
14536 .stream_find_iter(bytes_before_first_selection)
14537 .map(|result| (0, result)),
14538 );
14539
14540 for (start_offset, query_match) in query_matches {
14541 let query_match = query_match.unwrap(); // can only fail due to I/O
14542 let offset_range =
14543 start_offset + query_match.start()..start_offset + query_match.end();
14544
14545 if !select_next_state.wordwise
14546 || (!buffer.is_inside_word(offset_range.start, None)
14547 && !buffer.is_inside_word(offset_range.end, None))
14548 {
14549 let idx = selections
14550 .partition_point(|selection| selection.end <= offset_range.start);
14551 let overlaps = selections
14552 .get(idx)
14553 .map_or(false, |selection| selection.start < offset_range.end);
14554
14555 if !overlaps {
14556 next_selected_range = Some(offset_range);
14557 break;
14558 }
14559 }
14560 }
14561
14562 if let Some(next_selected_range) = next_selected_range {
14563 self.select_match_ranges(
14564 next_selected_range,
14565 last_selection.reversed,
14566 replace_newest,
14567 autoscroll,
14568 window,
14569 cx,
14570 );
14571 } else {
14572 select_next_state.done = true;
14573 }
14574 }
14575
14576 self.select_next_state = Some(select_next_state);
14577 } else {
14578 let mut only_carets = true;
14579 let mut same_text_selected = true;
14580 let mut selected_text = None;
14581
14582 let mut selections_iter = selections.iter().peekable();
14583 while let Some(selection) = selections_iter.next() {
14584 if selection.start != selection.end {
14585 only_carets = false;
14586 }
14587
14588 if same_text_selected {
14589 if selected_text.is_none() {
14590 selected_text =
14591 Some(buffer.text_for_range(selection.range()).collect::<String>());
14592 }
14593
14594 if let Some(next_selection) = selections_iter.peek() {
14595 if next_selection.range().len() == selection.range().len() {
14596 let next_selected_text = buffer
14597 .text_for_range(next_selection.range())
14598 .collect::<String>();
14599 if Some(next_selected_text) != selected_text {
14600 same_text_selected = false;
14601 selected_text = None;
14602 }
14603 } else {
14604 same_text_selected = false;
14605 selected_text = None;
14606 }
14607 }
14608 }
14609 }
14610
14611 if only_carets {
14612 for selection in &mut selections {
14613 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14614 selection.start = word_range.start;
14615 selection.end = word_range.end;
14616 selection.goal = SelectionGoal::None;
14617 selection.reversed = false;
14618 self.select_match_ranges(
14619 selection.start..selection.end,
14620 selection.reversed,
14621 replace_newest,
14622 autoscroll,
14623 window,
14624 cx,
14625 );
14626 }
14627
14628 if selections.len() == 1 {
14629 let selection = selections
14630 .last()
14631 .expect("ensured that there's only one selection");
14632 let query = buffer
14633 .text_for_range(selection.start..selection.end)
14634 .collect::<String>();
14635 let is_empty = query.is_empty();
14636 let select_state = SelectNextState {
14637 query: AhoCorasick::new(&[query])?,
14638 wordwise: true,
14639 done: is_empty,
14640 };
14641 self.select_next_state = Some(select_state);
14642 } else {
14643 self.select_next_state = None;
14644 }
14645 } else if let Some(selected_text) = selected_text {
14646 self.select_next_state = Some(SelectNextState {
14647 query: AhoCorasick::new(&[selected_text])?,
14648 wordwise: false,
14649 done: false,
14650 });
14651 self.select_next_match_internal(
14652 display_map,
14653 replace_newest,
14654 autoscroll,
14655 window,
14656 cx,
14657 )?;
14658 }
14659 }
14660 Ok(())
14661 }
14662
14663 pub fn select_all_matches(
14664 &mut self,
14665 _action: &SelectAllMatches,
14666 window: &mut Window,
14667 cx: &mut Context<Self>,
14668 ) -> Result<()> {
14669 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14670
14671 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14672
14673 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14674 let Some(select_next_state) = self.select_next_state.as_mut() else {
14675 return Ok(());
14676 };
14677 if select_next_state.done {
14678 return Ok(());
14679 }
14680
14681 let mut new_selections = Vec::new();
14682
14683 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14684 let buffer = display_map.buffer_snapshot();
14685 let query_matches = select_next_state
14686 .query
14687 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14688
14689 for query_match in query_matches.into_iter() {
14690 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14691 let offset_range = if reversed {
14692 query_match.end()..query_match.start()
14693 } else {
14694 query_match.start()..query_match.end()
14695 };
14696
14697 if !select_next_state.wordwise
14698 || (!buffer.is_inside_word(offset_range.start, None)
14699 && !buffer.is_inside_word(offset_range.end, None))
14700 {
14701 new_selections.push(offset_range.start..offset_range.end);
14702 }
14703 }
14704
14705 select_next_state.done = true;
14706
14707 if new_selections.is_empty() {
14708 log::error!("bug: new_selections is empty in select_all_matches");
14709 return Ok(());
14710 }
14711
14712 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14713 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14714 selections.select_ranges(new_selections)
14715 });
14716
14717 Ok(())
14718 }
14719
14720 pub fn select_next(
14721 &mut self,
14722 action: &SelectNext,
14723 window: &mut Window,
14724 cx: &mut Context<Self>,
14725 ) -> Result<()> {
14726 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14727 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14728 self.select_next_match_internal(
14729 &display_map,
14730 action.replace_newest,
14731 Some(Autoscroll::newest()),
14732 window,
14733 cx,
14734 )?;
14735 Ok(())
14736 }
14737
14738 pub fn select_previous(
14739 &mut self,
14740 action: &SelectPrevious,
14741 window: &mut Window,
14742 cx: &mut Context<Self>,
14743 ) -> Result<()> {
14744 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14745 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14746 let buffer = display_map.buffer_snapshot();
14747 let mut selections = self.selections.all::<usize>(&display_map);
14748 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14749 let query = &select_prev_state.query;
14750 if !select_prev_state.done {
14751 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14752 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14753 let mut next_selected_range = None;
14754 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14755 let bytes_before_last_selection =
14756 buffer.reversed_bytes_in_range(0..last_selection.start);
14757 let bytes_after_first_selection =
14758 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14759 let query_matches = query
14760 .stream_find_iter(bytes_before_last_selection)
14761 .map(|result| (last_selection.start, result))
14762 .chain(
14763 query
14764 .stream_find_iter(bytes_after_first_selection)
14765 .map(|result| (buffer.len(), result)),
14766 );
14767 for (end_offset, query_match) in query_matches {
14768 let query_match = query_match.unwrap(); // can only fail due to I/O
14769 let offset_range =
14770 end_offset - query_match.end()..end_offset - query_match.start();
14771
14772 if !select_prev_state.wordwise
14773 || (!buffer.is_inside_word(offset_range.start, None)
14774 && !buffer.is_inside_word(offset_range.end, None))
14775 {
14776 next_selected_range = Some(offset_range);
14777 break;
14778 }
14779 }
14780
14781 if let Some(next_selected_range) = next_selected_range {
14782 self.select_match_ranges(
14783 next_selected_range,
14784 last_selection.reversed,
14785 action.replace_newest,
14786 Some(Autoscroll::newest()),
14787 window,
14788 cx,
14789 );
14790 } else {
14791 select_prev_state.done = true;
14792 }
14793 }
14794
14795 self.select_prev_state = Some(select_prev_state);
14796 } else {
14797 let mut only_carets = true;
14798 let mut same_text_selected = true;
14799 let mut selected_text = None;
14800
14801 let mut selections_iter = selections.iter().peekable();
14802 while let Some(selection) = selections_iter.next() {
14803 if selection.start != selection.end {
14804 only_carets = false;
14805 }
14806
14807 if same_text_selected {
14808 if selected_text.is_none() {
14809 selected_text =
14810 Some(buffer.text_for_range(selection.range()).collect::<String>());
14811 }
14812
14813 if let Some(next_selection) = selections_iter.peek() {
14814 if next_selection.range().len() == selection.range().len() {
14815 let next_selected_text = buffer
14816 .text_for_range(next_selection.range())
14817 .collect::<String>();
14818 if Some(next_selected_text) != selected_text {
14819 same_text_selected = false;
14820 selected_text = None;
14821 }
14822 } else {
14823 same_text_selected = false;
14824 selected_text = None;
14825 }
14826 }
14827 }
14828 }
14829
14830 if only_carets {
14831 for selection in &mut selections {
14832 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14833 selection.start = word_range.start;
14834 selection.end = word_range.end;
14835 selection.goal = SelectionGoal::None;
14836 selection.reversed = false;
14837 self.select_match_ranges(
14838 selection.start..selection.end,
14839 selection.reversed,
14840 action.replace_newest,
14841 Some(Autoscroll::newest()),
14842 window,
14843 cx,
14844 );
14845 }
14846 if selections.len() == 1 {
14847 let selection = selections
14848 .last()
14849 .expect("ensured that there's only one selection");
14850 let query = buffer
14851 .text_for_range(selection.start..selection.end)
14852 .collect::<String>();
14853 let is_empty = query.is_empty();
14854 let select_state = SelectNextState {
14855 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14856 wordwise: true,
14857 done: is_empty,
14858 };
14859 self.select_prev_state = Some(select_state);
14860 } else {
14861 self.select_prev_state = None;
14862 }
14863 } else if let Some(selected_text) = selected_text {
14864 self.select_prev_state = Some(SelectNextState {
14865 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14866 wordwise: false,
14867 done: false,
14868 });
14869 self.select_previous(action, window, cx)?;
14870 }
14871 }
14872 Ok(())
14873 }
14874
14875 pub fn find_next_match(
14876 &mut self,
14877 _: &FindNextMatch,
14878 window: &mut Window,
14879 cx: &mut Context<Self>,
14880 ) -> Result<()> {
14881 let selections = self.selections.disjoint_anchors_arc();
14882 match selections.first() {
14883 Some(first) if selections.len() >= 2 => {
14884 self.change_selections(Default::default(), window, cx, |s| {
14885 s.select_ranges([first.range()]);
14886 });
14887 }
14888 _ => self.select_next(
14889 &SelectNext {
14890 replace_newest: true,
14891 },
14892 window,
14893 cx,
14894 )?,
14895 }
14896 Ok(())
14897 }
14898
14899 pub fn find_previous_match(
14900 &mut self,
14901 _: &FindPreviousMatch,
14902 window: &mut Window,
14903 cx: &mut Context<Self>,
14904 ) -> Result<()> {
14905 let selections = self.selections.disjoint_anchors_arc();
14906 match selections.last() {
14907 Some(last) if selections.len() >= 2 => {
14908 self.change_selections(Default::default(), window, cx, |s| {
14909 s.select_ranges([last.range()]);
14910 });
14911 }
14912 _ => self.select_previous(
14913 &SelectPrevious {
14914 replace_newest: true,
14915 },
14916 window,
14917 cx,
14918 )?,
14919 }
14920 Ok(())
14921 }
14922
14923 pub fn toggle_comments(
14924 &mut self,
14925 action: &ToggleComments,
14926 window: &mut Window,
14927 cx: &mut Context<Self>,
14928 ) {
14929 if self.read_only(cx) {
14930 return;
14931 }
14932 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14933 let text_layout_details = &self.text_layout_details(window);
14934 self.transact(window, cx, |this, window, cx| {
14935 let mut selections = this
14936 .selections
14937 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14938 let mut edits = Vec::new();
14939 let mut selection_edit_ranges = Vec::new();
14940 let mut last_toggled_row = None;
14941 let snapshot = this.buffer.read(cx).read(cx);
14942 let empty_str: Arc<str> = Arc::default();
14943 let mut suffixes_inserted = Vec::new();
14944 let ignore_indent = action.ignore_indent;
14945
14946 fn comment_prefix_range(
14947 snapshot: &MultiBufferSnapshot,
14948 row: MultiBufferRow,
14949 comment_prefix: &str,
14950 comment_prefix_whitespace: &str,
14951 ignore_indent: bool,
14952 ) -> Range<Point> {
14953 let indent_size = if ignore_indent {
14954 0
14955 } else {
14956 snapshot.indent_size_for_line(row).len
14957 };
14958
14959 let start = Point::new(row.0, indent_size);
14960
14961 let mut line_bytes = snapshot
14962 .bytes_in_range(start..snapshot.max_point())
14963 .flatten()
14964 .copied();
14965
14966 // If this line currently begins with the line comment prefix, then record
14967 // the range containing the prefix.
14968 if line_bytes
14969 .by_ref()
14970 .take(comment_prefix.len())
14971 .eq(comment_prefix.bytes())
14972 {
14973 // Include any whitespace that matches the comment prefix.
14974 let matching_whitespace_len = line_bytes
14975 .zip(comment_prefix_whitespace.bytes())
14976 .take_while(|(a, b)| a == b)
14977 .count() as u32;
14978 let end = Point::new(
14979 start.row,
14980 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14981 );
14982 start..end
14983 } else {
14984 start..start
14985 }
14986 }
14987
14988 fn comment_suffix_range(
14989 snapshot: &MultiBufferSnapshot,
14990 row: MultiBufferRow,
14991 comment_suffix: &str,
14992 comment_suffix_has_leading_space: bool,
14993 ) -> Range<Point> {
14994 let end = Point::new(row.0, snapshot.line_len(row));
14995 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14996
14997 let mut line_end_bytes = snapshot
14998 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14999 .flatten()
15000 .copied();
15001
15002 let leading_space_len = if suffix_start_column > 0
15003 && line_end_bytes.next() == Some(b' ')
15004 && comment_suffix_has_leading_space
15005 {
15006 1
15007 } else {
15008 0
15009 };
15010
15011 // If this line currently begins with the line comment prefix, then record
15012 // the range containing the prefix.
15013 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15014 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15015 start..end
15016 } else {
15017 end..end
15018 }
15019 }
15020
15021 // TODO: Handle selections that cross excerpts
15022 for selection in &mut selections {
15023 let start_column = snapshot
15024 .indent_size_for_line(MultiBufferRow(selection.start.row))
15025 .len;
15026 let language = if let Some(language) =
15027 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15028 {
15029 language
15030 } else {
15031 continue;
15032 };
15033
15034 selection_edit_ranges.clear();
15035
15036 // If multiple selections contain a given row, avoid processing that
15037 // row more than once.
15038 let mut start_row = MultiBufferRow(selection.start.row);
15039 if last_toggled_row == Some(start_row) {
15040 start_row = start_row.next_row();
15041 }
15042 let end_row =
15043 if selection.end.row > selection.start.row && selection.end.column == 0 {
15044 MultiBufferRow(selection.end.row - 1)
15045 } else {
15046 MultiBufferRow(selection.end.row)
15047 };
15048 last_toggled_row = Some(end_row);
15049
15050 if start_row > end_row {
15051 continue;
15052 }
15053
15054 // If the language has line comments, toggle those.
15055 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15056
15057 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15058 if ignore_indent {
15059 full_comment_prefixes = full_comment_prefixes
15060 .into_iter()
15061 .map(|s| Arc::from(s.trim_end()))
15062 .collect();
15063 }
15064
15065 if !full_comment_prefixes.is_empty() {
15066 let first_prefix = full_comment_prefixes
15067 .first()
15068 .expect("prefixes is non-empty");
15069 let prefix_trimmed_lengths = full_comment_prefixes
15070 .iter()
15071 .map(|p| p.trim_end_matches(' ').len())
15072 .collect::<SmallVec<[usize; 4]>>();
15073
15074 let mut all_selection_lines_are_comments = true;
15075
15076 for row in start_row.0..=end_row.0 {
15077 let row = MultiBufferRow(row);
15078 if start_row < end_row && snapshot.is_line_blank(row) {
15079 continue;
15080 }
15081
15082 let prefix_range = full_comment_prefixes
15083 .iter()
15084 .zip(prefix_trimmed_lengths.iter().copied())
15085 .map(|(prefix, trimmed_prefix_len)| {
15086 comment_prefix_range(
15087 snapshot.deref(),
15088 row,
15089 &prefix[..trimmed_prefix_len],
15090 &prefix[trimmed_prefix_len..],
15091 ignore_indent,
15092 )
15093 })
15094 .max_by_key(|range| range.end.column - range.start.column)
15095 .expect("prefixes is non-empty");
15096
15097 if prefix_range.is_empty() {
15098 all_selection_lines_are_comments = false;
15099 }
15100
15101 selection_edit_ranges.push(prefix_range);
15102 }
15103
15104 if all_selection_lines_are_comments {
15105 edits.extend(
15106 selection_edit_ranges
15107 .iter()
15108 .cloned()
15109 .map(|range| (range, empty_str.clone())),
15110 );
15111 } else {
15112 let min_column = selection_edit_ranges
15113 .iter()
15114 .map(|range| range.start.column)
15115 .min()
15116 .unwrap_or(0);
15117 edits.extend(selection_edit_ranges.iter().map(|range| {
15118 let position = Point::new(range.start.row, min_column);
15119 (position..position, first_prefix.clone())
15120 }));
15121 }
15122 } else if let Some(BlockCommentConfig {
15123 start: full_comment_prefix,
15124 end: comment_suffix,
15125 ..
15126 }) = language.block_comment()
15127 {
15128 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15129 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15130 let prefix_range = comment_prefix_range(
15131 snapshot.deref(),
15132 start_row,
15133 comment_prefix,
15134 comment_prefix_whitespace,
15135 ignore_indent,
15136 );
15137 let suffix_range = comment_suffix_range(
15138 snapshot.deref(),
15139 end_row,
15140 comment_suffix.trim_start_matches(' '),
15141 comment_suffix.starts_with(' '),
15142 );
15143
15144 if prefix_range.is_empty() || suffix_range.is_empty() {
15145 edits.push((
15146 prefix_range.start..prefix_range.start,
15147 full_comment_prefix.clone(),
15148 ));
15149 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15150 suffixes_inserted.push((end_row, comment_suffix.len()));
15151 } else {
15152 edits.push((prefix_range, empty_str.clone()));
15153 edits.push((suffix_range, empty_str.clone()));
15154 }
15155 } else {
15156 continue;
15157 }
15158 }
15159
15160 drop(snapshot);
15161 this.buffer.update(cx, |buffer, cx| {
15162 buffer.edit(edits, None, cx);
15163 });
15164
15165 // Adjust selections so that they end before any comment suffixes that
15166 // were inserted.
15167 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15168 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15169 let snapshot = this.buffer.read(cx).read(cx);
15170 for selection in &mut selections {
15171 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15172 match row.cmp(&MultiBufferRow(selection.end.row)) {
15173 Ordering::Less => {
15174 suffixes_inserted.next();
15175 continue;
15176 }
15177 Ordering::Greater => break,
15178 Ordering::Equal => {
15179 if selection.end.column == snapshot.line_len(row) {
15180 if selection.is_empty() {
15181 selection.start.column -= suffix_len as u32;
15182 }
15183 selection.end.column -= suffix_len as u32;
15184 }
15185 break;
15186 }
15187 }
15188 }
15189 }
15190
15191 drop(snapshot);
15192 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15193
15194 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15195 let selections_on_single_row = selections.windows(2).all(|selections| {
15196 selections[0].start.row == selections[1].start.row
15197 && selections[0].end.row == selections[1].end.row
15198 && selections[0].start.row == selections[0].end.row
15199 });
15200 let selections_selecting = selections
15201 .iter()
15202 .any(|selection| selection.start != selection.end);
15203 let advance_downwards = action.advance_downwards
15204 && selections_on_single_row
15205 && !selections_selecting
15206 && !matches!(this.mode, EditorMode::SingleLine);
15207
15208 if advance_downwards {
15209 let snapshot = this.buffer.read(cx).snapshot(cx);
15210
15211 this.change_selections(Default::default(), window, cx, |s| {
15212 s.move_cursors_with(|display_snapshot, display_point, _| {
15213 let mut point = display_point.to_point(display_snapshot);
15214 point.row += 1;
15215 point = snapshot.clip_point(point, Bias::Left);
15216 let display_point = point.to_display_point(display_snapshot);
15217 let goal = SelectionGoal::HorizontalPosition(
15218 display_snapshot
15219 .x_for_display_point(display_point, text_layout_details)
15220 .into(),
15221 );
15222 (display_point, goal)
15223 })
15224 });
15225 }
15226 });
15227 }
15228
15229 pub fn select_enclosing_symbol(
15230 &mut self,
15231 _: &SelectEnclosingSymbol,
15232 window: &mut Window,
15233 cx: &mut Context<Self>,
15234 ) {
15235 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15236
15237 let buffer = self.buffer.read(cx).snapshot(cx);
15238 let old_selections = self
15239 .selections
15240 .all::<usize>(&self.display_snapshot(cx))
15241 .into_boxed_slice();
15242
15243 fn update_selection(
15244 selection: &Selection<usize>,
15245 buffer_snap: &MultiBufferSnapshot,
15246 ) -> Option<Selection<usize>> {
15247 let cursor = selection.head();
15248 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15249 for symbol in symbols.iter().rev() {
15250 let start = symbol.range.start.to_offset(buffer_snap);
15251 let end = symbol.range.end.to_offset(buffer_snap);
15252 let new_range = start..end;
15253 if start < selection.start || end > selection.end {
15254 return Some(Selection {
15255 id: selection.id,
15256 start: new_range.start,
15257 end: new_range.end,
15258 goal: SelectionGoal::None,
15259 reversed: selection.reversed,
15260 });
15261 }
15262 }
15263 None
15264 }
15265
15266 let mut selected_larger_symbol = false;
15267 let new_selections = old_selections
15268 .iter()
15269 .map(|selection| match update_selection(selection, &buffer) {
15270 Some(new_selection) => {
15271 if new_selection.range() != selection.range() {
15272 selected_larger_symbol = true;
15273 }
15274 new_selection
15275 }
15276 None => selection.clone(),
15277 })
15278 .collect::<Vec<_>>();
15279
15280 if selected_larger_symbol {
15281 self.change_selections(Default::default(), window, cx, |s| {
15282 s.select(new_selections);
15283 });
15284 }
15285 }
15286
15287 pub fn select_larger_syntax_node(
15288 &mut self,
15289 _: &SelectLargerSyntaxNode,
15290 window: &mut Window,
15291 cx: &mut Context<Self>,
15292 ) {
15293 let Some(visible_row_count) = self.visible_row_count() else {
15294 return;
15295 };
15296 let old_selections: Box<[_]> = self
15297 .selections
15298 .all::<usize>(&self.display_snapshot(cx))
15299 .into();
15300 if old_selections.is_empty() {
15301 return;
15302 }
15303
15304 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15305
15306 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15307 let buffer = self.buffer.read(cx).snapshot(cx);
15308
15309 let mut selected_larger_node = false;
15310 let mut new_selections = old_selections
15311 .iter()
15312 .map(|selection| {
15313 let old_range = selection.start..selection.end;
15314
15315 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15316 // manually select word at selection
15317 if ["string_content", "inline"].contains(&node.kind()) {
15318 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15319 // ignore if word is already selected
15320 if !word_range.is_empty() && old_range != word_range {
15321 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15322 // only select word if start and end point belongs to same word
15323 if word_range == last_word_range {
15324 selected_larger_node = true;
15325 return Selection {
15326 id: selection.id,
15327 start: word_range.start,
15328 end: word_range.end,
15329 goal: SelectionGoal::None,
15330 reversed: selection.reversed,
15331 };
15332 }
15333 }
15334 }
15335 }
15336
15337 let mut new_range = old_range.clone();
15338 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15339 new_range = range;
15340 if !node.is_named() {
15341 continue;
15342 }
15343 if !display_map.intersects_fold(new_range.start)
15344 && !display_map.intersects_fold(new_range.end)
15345 {
15346 break;
15347 }
15348 }
15349
15350 selected_larger_node |= new_range != old_range;
15351 Selection {
15352 id: selection.id,
15353 start: new_range.start,
15354 end: new_range.end,
15355 goal: SelectionGoal::None,
15356 reversed: selection.reversed,
15357 }
15358 })
15359 .collect::<Vec<_>>();
15360
15361 if !selected_larger_node {
15362 return; // don't put this call in the history
15363 }
15364
15365 // scroll based on transformation done to the last selection created by the user
15366 let (last_old, last_new) = old_selections
15367 .last()
15368 .zip(new_selections.last().cloned())
15369 .expect("old_selections isn't empty");
15370
15371 // revert selection
15372 let is_selection_reversed = {
15373 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15374 new_selections.last_mut().expect("checked above").reversed =
15375 should_newest_selection_be_reversed;
15376 should_newest_selection_be_reversed
15377 };
15378
15379 if selected_larger_node {
15380 self.select_syntax_node_history.disable_clearing = true;
15381 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15382 s.select(new_selections.clone());
15383 });
15384 self.select_syntax_node_history.disable_clearing = false;
15385 }
15386
15387 let start_row = last_new.start.to_display_point(&display_map).row().0;
15388 let end_row = last_new.end.to_display_point(&display_map).row().0;
15389 let selection_height = end_row - start_row + 1;
15390 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15391
15392 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15393 let scroll_behavior = if fits_on_the_screen {
15394 self.request_autoscroll(Autoscroll::fit(), cx);
15395 SelectSyntaxNodeScrollBehavior::FitSelection
15396 } else if is_selection_reversed {
15397 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15398 SelectSyntaxNodeScrollBehavior::CursorTop
15399 } else {
15400 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15401 SelectSyntaxNodeScrollBehavior::CursorBottom
15402 };
15403
15404 self.select_syntax_node_history.push((
15405 old_selections,
15406 scroll_behavior,
15407 is_selection_reversed,
15408 ));
15409 }
15410
15411 pub fn select_smaller_syntax_node(
15412 &mut self,
15413 _: &SelectSmallerSyntaxNode,
15414 window: &mut Window,
15415 cx: &mut Context<Self>,
15416 ) {
15417 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15418
15419 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15420 self.select_syntax_node_history.pop()
15421 {
15422 if let Some(selection) = selections.last_mut() {
15423 selection.reversed = is_selection_reversed;
15424 }
15425
15426 self.select_syntax_node_history.disable_clearing = true;
15427 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15428 s.select(selections.to_vec());
15429 });
15430 self.select_syntax_node_history.disable_clearing = false;
15431
15432 match scroll_behavior {
15433 SelectSyntaxNodeScrollBehavior::CursorTop => {
15434 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15435 }
15436 SelectSyntaxNodeScrollBehavior::FitSelection => {
15437 self.request_autoscroll(Autoscroll::fit(), cx);
15438 }
15439 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15440 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15441 }
15442 }
15443 }
15444 }
15445
15446 pub fn unwrap_syntax_node(
15447 &mut self,
15448 _: &UnwrapSyntaxNode,
15449 window: &mut Window,
15450 cx: &mut Context<Self>,
15451 ) {
15452 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15453
15454 let buffer = self.buffer.read(cx).snapshot(cx);
15455 let selections = self
15456 .selections
15457 .all::<usize>(&self.display_snapshot(cx))
15458 .into_iter()
15459 // subtracting the offset requires sorting
15460 .sorted_by_key(|i| i.start);
15461
15462 let full_edits = selections
15463 .into_iter()
15464 .filter_map(|selection| {
15465 let child = if selection.is_empty()
15466 && let Some((_, ancestor_range)) =
15467 buffer.syntax_ancestor(selection.start..selection.end)
15468 {
15469 ancestor_range
15470 } else {
15471 selection.range()
15472 };
15473
15474 let mut parent = child.clone();
15475 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15476 parent = ancestor_range;
15477 if parent.start < child.start || parent.end > child.end {
15478 break;
15479 }
15480 }
15481
15482 if parent == child {
15483 return None;
15484 }
15485 let text = buffer.text_for_range(child).collect::<String>();
15486 Some((selection.id, parent, text))
15487 })
15488 .collect::<Vec<_>>();
15489 if full_edits.is_empty() {
15490 return;
15491 }
15492
15493 self.transact(window, cx, |this, window, cx| {
15494 this.buffer.update(cx, |buffer, cx| {
15495 buffer.edit(
15496 full_edits
15497 .iter()
15498 .map(|(_, p, t)| (p.clone(), t.clone()))
15499 .collect::<Vec<_>>(),
15500 None,
15501 cx,
15502 );
15503 });
15504 this.change_selections(Default::default(), window, cx, |s| {
15505 let mut offset = 0;
15506 let mut selections = vec![];
15507 for (id, parent, text) in full_edits {
15508 let start = parent.start - offset;
15509 offset += parent.len() - text.len();
15510 selections.push(Selection {
15511 id,
15512 start,
15513 end: start + text.len(),
15514 reversed: false,
15515 goal: Default::default(),
15516 });
15517 }
15518 s.select(selections);
15519 });
15520 });
15521 }
15522
15523 pub fn select_next_syntax_node(
15524 &mut self,
15525 _: &SelectNextSyntaxNode,
15526 window: &mut Window,
15527 cx: &mut Context<Self>,
15528 ) {
15529 let old_selections: Box<[_]> = self
15530 .selections
15531 .all::<usize>(&self.display_snapshot(cx))
15532 .into();
15533 if old_selections.is_empty() {
15534 return;
15535 }
15536
15537 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15538
15539 let buffer = self.buffer.read(cx).snapshot(cx);
15540 let mut selected_sibling = false;
15541
15542 let new_selections = old_selections
15543 .iter()
15544 .map(|selection| {
15545 let old_range = selection.start..selection.end;
15546
15547 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15548 let new_range = node.byte_range();
15549 selected_sibling = true;
15550 Selection {
15551 id: selection.id,
15552 start: new_range.start,
15553 end: new_range.end,
15554 goal: SelectionGoal::None,
15555 reversed: selection.reversed,
15556 }
15557 } else {
15558 selection.clone()
15559 }
15560 })
15561 .collect::<Vec<_>>();
15562
15563 if selected_sibling {
15564 self.change_selections(
15565 SelectionEffects::scroll(Autoscroll::fit()),
15566 window,
15567 cx,
15568 |s| {
15569 s.select(new_selections);
15570 },
15571 );
15572 }
15573 }
15574
15575 pub fn select_prev_syntax_node(
15576 &mut self,
15577 _: &SelectPreviousSyntaxNode,
15578 window: &mut Window,
15579 cx: &mut Context<Self>,
15580 ) {
15581 let old_selections: Box<[_]> = self
15582 .selections
15583 .all::<usize>(&self.display_snapshot(cx))
15584 .into();
15585 if old_selections.is_empty() {
15586 return;
15587 }
15588
15589 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15590
15591 let buffer = self.buffer.read(cx).snapshot(cx);
15592 let mut selected_sibling = false;
15593
15594 let new_selections = old_selections
15595 .iter()
15596 .map(|selection| {
15597 let old_range = selection.start..selection.end;
15598
15599 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15600 let new_range = node.byte_range();
15601 selected_sibling = true;
15602 Selection {
15603 id: selection.id,
15604 start: new_range.start,
15605 end: new_range.end,
15606 goal: SelectionGoal::None,
15607 reversed: selection.reversed,
15608 }
15609 } else {
15610 selection.clone()
15611 }
15612 })
15613 .collect::<Vec<_>>();
15614
15615 if selected_sibling {
15616 self.change_selections(
15617 SelectionEffects::scroll(Autoscroll::fit()),
15618 window,
15619 cx,
15620 |s| {
15621 s.select(new_selections);
15622 },
15623 );
15624 }
15625 }
15626
15627 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15628 if !EditorSettings::get_global(cx).gutter.runnables {
15629 self.clear_tasks();
15630 return Task::ready(());
15631 }
15632 let project = self.project().map(Entity::downgrade);
15633 let task_sources = self.lsp_task_sources(cx);
15634 let multi_buffer = self.buffer.downgrade();
15635 cx.spawn_in(window, async move |editor, cx| {
15636 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15637 let Some(project) = project.and_then(|p| p.upgrade()) else {
15638 return;
15639 };
15640 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15641 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15642 }) else {
15643 return;
15644 };
15645
15646 let hide_runnables = project
15647 .update(cx, |project, _| project.is_via_collab())
15648 .unwrap_or(true);
15649 if hide_runnables {
15650 return;
15651 }
15652 let new_rows =
15653 cx.background_spawn({
15654 let snapshot = display_snapshot.clone();
15655 async move {
15656 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15657 }
15658 })
15659 .await;
15660 let Ok(lsp_tasks) =
15661 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15662 else {
15663 return;
15664 };
15665 let lsp_tasks = lsp_tasks.await;
15666
15667 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15668 lsp_tasks
15669 .into_iter()
15670 .flat_map(|(kind, tasks)| {
15671 tasks.into_iter().filter_map(move |(location, task)| {
15672 Some((kind.clone(), location?, task))
15673 })
15674 })
15675 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15676 let buffer = location.target.buffer;
15677 let buffer_snapshot = buffer.read(cx).snapshot();
15678 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15679 |(excerpt_id, snapshot, _)| {
15680 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15681 display_snapshot
15682 .buffer_snapshot()
15683 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15684 } else {
15685 None
15686 }
15687 },
15688 );
15689 if let Some(offset) = offset {
15690 let task_buffer_range =
15691 location.target.range.to_point(&buffer_snapshot);
15692 let context_buffer_range =
15693 task_buffer_range.to_offset(&buffer_snapshot);
15694 let context_range = BufferOffset(context_buffer_range.start)
15695 ..BufferOffset(context_buffer_range.end);
15696
15697 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15698 .or_insert_with(|| RunnableTasks {
15699 templates: Vec::new(),
15700 offset,
15701 column: task_buffer_range.start.column,
15702 extra_variables: HashMap::default(),
15703 context_range,
15704 })
15705 .templates
15706 .push((kind, task.original_task().clone()));
15707 }
15708
15709 acc
15710 })
15711 }) else {
15712 return;
15713 };
15714
15715 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15716 buffer.language_settings(cx).tasks.prefer_lsp
15717 }) else {
15718 return;
15719 };
15720
15721 let rows = Self::runnable_rows(
15722 project,
15723 display_snapshot,
15724 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15725 new_rows,
15726 cx.clone(),
15727 )
15728 .await;
15729 editor
15730 .update(cx, |editor, _| {
15731 editor.clear_tasks();
15732 for (key, mut value) in rows {
15733 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15734 value.templates.extend(lsp_tasks.templates);
15735 }
15736
15737 editor.insert_tasks(key, value);
15738 }
15739 for (key, value) in lsp_tasks_by_rows {
15740 editor.insert_tasks(key, value);
15741 }
15742 })
15743 .ok();
15744 })
15745 }
15746 fn fetch_runnable_ranges(
15747 snapshot: &DisplaySnapshot,
15748 range: Range<Anchor>,
15749 ) -> Vec<language::RunnableRange> {
15750 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15751 }
15752
15753 fn runnable_rows(
15754 project: Entity<Project>,
15755 snapshot: DisplaySnapshot,
15756 prefer_lsp: bool,
15757 runnable_ranges: Vec<RunnableRange>,
15758 cx: AsyncWindowContext,
15759 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15760 cx.spawn(async move |cx| {
15761 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15762 for mut runnable in runnable_ranges {
15763 let Some(tasks) = cx
15764 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15765 .ok()
15766 else {
15767 continue;
15768 };
15769 let mut tasks = tasks.await;
15770
15771 if prefer_lsp {
15772 tasks.retain(|(task_kind, _)| {
15773 !matches!(task_kind, TaskSourceKind::Language { .. })
15774 });
15775 }
15776 if tasks.is_empty() {
15777 continue;
15778 }
15779
15780 let point = runnable
15781 .run_range
15782 .start
15783 .to_point(&snapshot.buffer_snapshot());
15784 let Some(row) = snapshot
15785 .buffer_snapshot()
15786 .buffer_line_for_row(MultiBufferRow(point.row))
15787 .map(|(_, range)| range.start.row)
15788 else {
15789 continue;
15790 };
15791
15792 let context_range =
15793 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15794 runnable_rows.push((
15795 (runnable.buffer_id, row),
15796 RunnableTasks {
15797 templates: tasks,
15798 offset: snapshot
15799 .buffer_snapshot()
15800 .anchor_before(runnable.run_range.start),
15801 context_range,
15802 column: point.column,
15803 extra_variables: runnable.extra_captures,
15804 },
15805 ));
15806 }
15807 runnable_rows
15808 })
15809 }
15810
15811 fn templates_with_tags(
15812 project: &Entity<Project>,
15813 runnable: &mut Runnable,
15814 cx: &mut App,
15815 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15816 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15817 let (worktree_id, file) = project
15818 .buffer_for_id(runnable.buffer, cx)
15819 .and_then(|buffer| buffer.read(cx).file())
15820 .map(|file| (file.worktree_id(cx), file.clone()))
15821 .unzip();
15822
15823 (
15824 project.task_store().read(cx).task_inventory().cloned(),
15825 worktree_id,
15826 file,
15827 )
15828 });
15829
15830 let tags = mem::take(&mut runnable.tags);
15831 let language = runnable.language.clone();
15832 cx.spawn(async move |cx| {
15833 let mut templates_with_tags = Vec::new();
15834 if let Some(inventory) = inventory {
15835 for RunnableTag(tag) in tags {
15836 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15837 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15838 }) else {
15839 return templates_with_tags;
15840 };
15841 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15842 move |(_, template)| {
15843 template.tags.iter().any(|source_tag| source_tag == &tag)
15844 },
15845 ));
15846 }
15847 }
15848 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15849
15850 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15851 // Strongest source wins; if we have worktree tag binding, prefer that to
15852 // global and language bindings;
15853 // if we have a global binding, prefer that to language binding.
15854 let first_mismatch = templates_with_tags
15855 .iter()
15856 .position(|(tag_source, _)| tag_source != leading_tag_source);
15857 if let Some(index) = first_mismatch {
15858 templates_with_tags.truncate(index);
15859 }
15860 }
15861
15862 templates_with_tags
15863 })
15864 }
15865
15866 pub fn move_to_enclosing_bracket(
15867 &mut self,
15868 _: &MoveToEnclosingBracket,
15869 window: &mut Window,
15870 cx: &mut Context<Self>,
15871 ) {
15872 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15873 self.change_selections(Default::default(), window, cx, |s| {
15874 s.move_offsets_with(|snapshot, selection| {
15875 let Some(enclosing_bracket_ranges) =
15876 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15877 else {
15878 return;
15879 };
15880
15881 let mut best_length = usize::MAX;
15882 let mut best_inside = false;
15883 let mut best_in_bracket_range = false;
15884 let mut best_destination = None;
15885 for (open, close) in enclosing_bracket_ranges {
15886 let close = close.to_inclusive();
15887 let length = close.end() - open.start;
15888 let inside = selection.start >= open.end && selection.end <= *close.start();
15889 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15890 || close.contains(&selection.head());
15891
15892 // If best is next to a bracket and current isn't, skip
15893 if !in_bracket_range && best_in_bracket_range {
15894 continue;
15895 }
15896
15897 // Prefer smaller lengths unless best is inside and current isn't
15898 if length > best_length && (best_inside || !inside) {
15899 continue;
15900 }
15901
15902 best_length = length;
15903 best_inside = inside;
15904 best_in_bracket_range = in_bracket_range;
15905 best_destination = Some(
15906 if close.contains(&selection.start) && close.contains(&selection.end) {
15907 if inside { open.end } else { open.start }
15908 } else if inside {
15909 *close.start()
15910 } else {
15911 *close.end()
15912 },
15913 );
15914 }
15915
15916 if let Some(destination) = best_destination {
15917 selection.collapse_to(destination, SelectionGoal::None);
15918 }
15919 })
15920 });
15921 }
15922
15923 pub fn undo_selection(
15924 &mut self,
15925 _: &UndoSelection,
15926 window: &mut Window,
15927 cx: &mut Context<Self>,
15928 ) {
15929 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15930 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15931 self.selection_history.mode = SelectionHistoryMode::Undoing;
15932 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15933 this.end_selection(window, cx);
15934 this.change_selections(
15935 SelectionEffects::scroll(Autoscroll::newest()),
15936 window,
15937 cx,
15938 |s| s.select_anchors(entry.selections.to_vec()),
15939 );
15940 });
15941 self.selection_history.mode = SelectionHistoryMode::Normal;
15942
15943 self.select_next_state = entry.select_next_state;
15944 self.select_prev_state = entry.select_prev_state;
15945 self.add_selections_state = entry.add_selections_state;
15946 }
15947 }
15948
15949 pub fn redo_selection(
15950 &mut self,
15951 _: &RedoSelection,
15952 window: &mut Window,
15953 cx: &mut Context<Self>,
15954 ) {
15955 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15956 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15957 self.selection_history.mode = SelectionHistoryMode::Redoing;
15958 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15959 this.end_selection(window, cx);
15960 this.change_selections(
15961 SelectionEffects::scroll(Autoscroll::newest()),
15962 window,
15963 cx,
15964 |s| s.select_anchors(entry.selections.to_vec()),
15965 );
15966 });
15967 self.selection_history.mode = SelectionHistoryMode::Normal;
15968
15969 self.select_next_state = entry.select_next_state;
15970 self.select_prev_state = entry.select_prev_state;
15971 self.add_selections_state = entry.add_selections_state;
15972 }
15973 }
15974
15975 pub fn expand_excerpts(
15976 &mut self,
15977 action: &ExpandExcerpts,
15978 _: &mut Window,
15979 cx: &mut Context<Self>,
15980 ) {
15981 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15982 }
15983
15984 pub fn expand_excerpts_down(
15985 &mut self,
15986 action: &ExpandExcerptsDown,
15987 _: &mut Window,
15988 cx: &mut Context<Self>,
15989 ) {
15990 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15991 }
15992
15993 pub fn expand_excerpts_up(
15994 &mut self,
15995 action: &ExpandExcerptsUp,
15996 _: &mut Window,
15997 cx: &mut Context<Self>,
15998 ) {
15999 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16000 }
16001
16002 pub fn expand_excerpts_for_direction(
16003 &mut self,
16004 lines: u32,
16005 direction: ExpandExcerptDirection,
16006
16007 cx: &mut Context<Self>,
16008 ) {
16009 let selections = self.selections.disjoint_anchors_arc();
16010
16011 let lines = if lines == 0 {
16012 EditorSettings::get_global(cx).expand_excerpt_lines
16013 } else {
16014 lines
16015 };
16016
16017 self.buffer.update(cx, |buffer, cx| {
16018 let snapshot = buffer.snapshot(cx);
16019 let mut excerpt_ids = selections
16020 .iter()
16021 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16022 .collect::<Vec<_>>();
16023 excerpt_ids.sort();
16024 excerpt_ids.dedup();
16025 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16026 })
16027 }
16028
16029 pub fn expand_excerpt(
16030 &mut self,
16031 excerpt: ExcerptId,
16032 direction: ExpandExcerptDirection,
16033 window: &mut Window,
16034 cx: &mut Context<Self>,
16035 ) {
16036 let current_scroll_position = self.scroll_position(cx);
16037 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16038 let mut scroll = None;
16039
16040 if direction == ExpandExcerptDirection::Down {
16041 let multi_buffer = self.buffer.read(cx);
16042 let snapshot = multi_buffer.snapshot(cx);
16043 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16044 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16045 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16046 {
16047 let buffer_snapshot = buffer.read(cx).snapshot();
16048 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16049 let last_row = buffer_snapshot.max_point().row;
16050 let lines_below = last_row.saturating_sub(excerpt_end_row);
16051 if lines_below >= lines_to_expand {
16052 scroll = Some(
16053 current_scroll_position
16054 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16055 );
16056 }
16057 }
16058 }
16059 if direction == ExpandExcerptDirection::Up
16060 && self
16061 .buffer
16062 .read(cx)
16063 .snapshot(cx)
16064 .excerpt_before(excerpt)
16065 .is_none()
16066 {
16067 scroll = Some(current_scroll_position);
16068 }
16069
16070 self.buffer.update(cx, |buffer, cx| {
16071 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16072 });
16073
16074 if let Some(new_scroll_position) = scroll {
16075 self.set_scroll_position(new_scroll_position, window, cx);
16076 }
16077 }
16078
16079 pub fn go_to_singleton_buffer_point(
16080 &mut self,
16081 point: Point,
16082 window: &mut Window,
16083 cx: &mut Context<Self>,
16084 ) {
16085 self.go_to_singleton_buffer_range(point..point, window, cx);
16086 }
16087
16088 pub fn go_to_singleton_buffer_range(
16089 &mut self,
16090 range: Range<Point>,
16091 window: &mut Window,
16092 cx: &mut Context<Self>,
16093 ) {
16094 let multibuffer = self.buffer().read(cx);
16095 let Some(buffer) = multibuffer.as_singleton() else {
16096 return;
16097 };
16098 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16099 return;
16100 };
16101 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16102 return;
16103 };
16104 self.change_selections(
16105 SelectionEffects::default().nav_history(true),
16106 window,
16107 cx,
16108 |s| s.select_anchor_ranges([start..end]),
16109 );
16110 }
16111
16112 pub fn go_to_diagnostic(
16113 &mut self,
16114 action: &GoToDiagnostic,
16115 window: &mut Window,
16116 cx: &mut Context<Self>,
16117 ) {
16118 if !self.diagnostics_enabled() {
16119 return;
16120 }
16121 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16122 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16123 }
16124
16125 pub fn go_to_prev_diagnostic(
16126 &mut self,
16127 action: &GoToPreviousDiagnostic,
16128 window: &mut Window,
16129 cx: &mut Context<Self>,
16130 ) {
16131 if !self.diagnostics_enabled() {
16132 return;
16133 }
16134 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16135 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16136 }
16137
16138 pub fn go_to_diagnostic_impl(
16139 &mut self,
16140 direction: Direction,
16141 severity: GoToDiagnosticSeverityFilter,
16142 window: &mut Window,
16143 cx: &mut Context<Self>,
16144 ) {
16145 let buffer = self.buffer.read(cx).snapshot(cx);
16146 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16147
16148 let mut active_group_id = None;
16149 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16150 && active_group.active_range.start.to_offset(&buffer) == selection.start
16151 {
16152 active_group_id = Some(active_group.group_id);
16153 }
16154
16155 fn filtered<'a>(
16156 severity: GoToDiagnosticSeverityFilter,
16157 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16158 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16159 diagnostics
16160 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16161 .filter(|entry| entry.range.start != entry.range.end)
16162 .filter(|entry| !entry.diagnostic.is_unnecessary)
16163 }
16164
16165 let before = filtered(
16166 severity,
16167 buffer
16168 .diagnostics_in_range(0..selection.start)
16169 .filter(|entry| entry.range.start <= selection.start),
16170 );
16171 let after = filtered(
16172 severity,
16173 buffer
16174 .diagnostics_in_range(selection.start..buffer.len())
16175 .filter(|entry| entry.range.start >= selection.start),
16176 );
16177
16178 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16179 if direction == Direction::Prev {
16180 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16181 {
16182 for diagnostic in prev_diagnostics.into_iter().rev() {
16183 if diagnostic.range.start != selection.start
16184 || active_group_id
16185 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16186 {
16187 found = Some(diagnostic);
16188 break 'outer;
16189 }
16190 }
16191 }
16192 } else {
16193 for diagnostic in after.chain(before) {
16194 if diagnostic.range.start != selection.start
16195 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16196 {
16197 found = Some(diagnostic);
16198 break;
16199 }
16200 }
16201 }
16202 let Some(next_diagnostic) = found else {
16203 return;
16204 };
16205
16206 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16207 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16208 return;
16209 };
16210 let snapshot = self.snapshot(window, cx);
16211 if snapshot.intersects_fold(next_diagnostic.range.start) {
16212 self.unfold_ranges(
16213 std::slice::from_ref(&next_diagnostic.range),
16214 true,
16215 false,
16216 cx,
16217 );
16218 }
16219 self.change_selections(Default::default(), window, cx, |s| {
16220 s.select_ranges(vec![
16221 next_diagnostic.range.start..next_diagnostic.range.start,
16222 ])
16223 });
16224 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16225 self.refresh_edit_prediction(false, true, window, cx);
16226 }
16227
16228 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16229 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16230 let snapshot = self.snapshot(window, cx);
16231 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16232 self.go_to_hunk_before_or_after_position(
16233 &snapshot,
16234 selection.head(),
16235 Direction::Next,
16236 window,
16237 cx,
16238 );
16239 }
16240
16241 pub fn go_to_hunk_before_or_after_position(
16242 &mut self,
16243 snapshot: &EditorSnapshot,
16244 position: Point,
16245 direction: Direction,
16246 window: &mut Window,
16247 cx: &mut Context<Editor>,
16248 ) {
16249 let row = if direction == Direction::Next {
16250 self.hunk_after_position(snapshot, position)
16251 .map(|hunk| hunk.row_range.start)
16252 } else {
16253 self.hunk_before_position(snapshot, position)
16254 };
16255
16256 if let Some(row) = row {
16257 let destination = Point::new(row.0, 0);
16258 let autoscroll = Autoscroll::center();
16259
16260 self.unfold_ranges(&[destination..destination], false, false, cx);
16261 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16262 s.select_ranges([destination..destination]);
16263 });
16264 }
16265 }
16266
16267 fn hunk_after_position(
16268 &mut self,
16269 snapshot: &EditorSnapshot,
16270 position: Point,
16271 ) -> Option<MultiBufferDiffHunk> {
16272 snapshot
16273 .buffer_snapshot()
16274 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16275 .find(|hunk| hunk.row_range.start.0 > position.row)
16276 .or_else(|| {
16277 snapshot
16278 .buffer_snapshot()
16279 .diff_hunks_in_range(Point::zero()..position)
16280 .find(|hunk| hunk.row_range.end.0 < position.row)
16281 })
16282 }
16283
16284 fn go_to_prev_hunk(
16285 &mut self,
16286 _: &GoToPreviousHunk,
16287 window: &mut Window,
16288 cx: &mut Context<Self>,
16289 ) {
16290 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16291 let snapshot = self.snapshot(window, cx);
16292 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16293 self.go_to_hunk_before_or_after_position(
16294 &snapshot,
16295 selection.head(),
16296 Direction::Prev,
16297 window,
16298 cx,
16299 );
16300 }
16301
16302 fn hunk_before_position(
16303 &mut self,
16304 snapshot: &EditorSnapshot,
16305 position: Point,
16306 ) -> Option<MultiBufferRow> {
16307 snapshot
16308 .buffer_snapshot()
16309 .diff_hunk_before(position)
16310 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16311 }
16312
16313 fn go_to_next_change(
16314 &mut self,
16315 _: &GoToNextChange,
16316 window: &mut Window,
16317 cx: &mut Context<Self>,
16318 ) {
16319 if let Some(selections) = self
16320 .change_list
16321 .next_change(1, Direction::Next)
16322 .map(|s| s.to_vec())
16323 {
16324 self.change_selections(Default::default(), window, cx, |s| {
16325 let map = s.display_snapshot();
16326 s.select_display_ranges(selections.iter().map(|a| {
16327 let point = a.to_display_point(&map);
16328 point..point
16329 }))
16330 })
16331 }
16332 }
16333
16334 fn go_to_previous_change(
16335 &mut self,
16336 _: &GoToPreviousChange,
16337 window: &mut Window,
16338 cx: &mut Context<Self>,
16339 ) {
16340 if let Some(selections) = self
16341 .change_list
16342 .next_change(1, Direction::Prev)
16343 .map(|s| s.to_vec())
16344 {
16345 self.change_selections(Default::default(), window, cx, |s| {
16346 let map = s.display_snapshot();
16347 s.select_display_ranges(selections.iter().map(|a| {
16348 let point = a.to_display_point(&map);
16349 point..point
16350 }))
16351 })
16352 }
16353 }
16354
16355 pub fn go_to_next_document_highlight(
16356 &mut self,
16357 _: &GoToNextDocumentHighlight,
16358 window: &mut Window,
16359 cx: &mut Context<Self>,
16360 ) {
16361 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16362 }
16363
16364 pub fn go_to_prev_document_highlight(
16365 &mut self,
16366 _: &GoToPreviousDocumentHighlight,
16367 window: &mut Window,
16368 cx: &mut Context<Self>,
16369 ) {
16370 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16371 }
16372
16373 pub fn go_to_document_highlight_before_or_after_position(
16374 &mut self,
16375 direction: Direction,
16376 window: &mut Window,
16377 cx: &mut Context<Editor>,
16378 ) {
16379 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16380 let snapshot = self.snapshot(window, cx);
16381 let buffer = &snapshot.buffer_snapshot();
16382 let position = self
16383 .selections
16384 .newest::<Point>(&snapshot.display_snapshot)
16385 .head();
16386 let anchor_position = buffer.anchor_after(position);
16387
16388 // Get all document highlights (both read and write)
16389 let mut all_highlights = Vec::new();
16390
16391 if let Some((_, read_highlights)) = self
16392 .background_highlights
16393 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16394 {
16395 all_highlights.extend(read_highlights.iter());
16396 }
16397
16398 if let Some((_, write_highlights)) = self
16399 .background_highlights
16400 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16401 {
16402 all_highlights.extend(write_highlights.iter());
16403 }
16404
16405 if all_highlights.is_empty() {
16406 return;
16407 }
16408
16409 // Sort highlights by position
16410 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16411
16412 let target_highlight = match direction {
16413 Direction::Next => {
16414 // Find the first highlight after the current position
16415 all_highlights
16416 .iter()
16417 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16418 }
16419 Direction::Prev => {
16420 // Find the last highlight before the current position
16421 all_highlights
16422 .iter()
16423 .rev()
16424 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16425 }
16426 };
16427
16428 if let Some(highlight) = target_highlight {
16429 let destination = highlight.start.to_point(buffer);
16430 let autoscroll = Autoscroll::center();
16431
16432 self.unfold_ranges(&[destination..destination], false, false, cx);
16433 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16434 s.select_ranges([destination..destination]);
16435 });
16436 }
16437 }
16438
16439 fn go_to_line<T: 'static>(
16440 &mut self,
16441 position: Anchor,
16442 highlight_color: Option<Hsla>,
16443 window: &mut Window,
16444 cx: &mut Context<Self>,
16445 ) {
16446 let snapshot = self.snapshot(window, cx).display_snapshot;
16447 let position = position.to_point(&snapshot.buffer_snapshot());
16448 let start = snapshot
16449 .buffer_snapshot()
16450 .clip_point(Point::new(position.row, 0), Bias::Left);
16451 let end = start + Point::new(1, 0);
16452 let start = snapshot.buffer_snapshot().anchor_before(start);
16453 let end = snapshot.buffer_snapshot().anchor_before(end);
16454
16455 self.highlight_rows::<T>(
16456 start..end,
16457 highlight_color
16458 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16459 Default::default(),
16460 cx,
16461 );
16462
16463 if self.buffer.read(cx).is_singleton() {
16464 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16465 }
16466 }
16467
16468 pub fn go_to_definition(
16469 &mut self,
16470 _: &GoToDefinition,
16471 window: &mut Window,
16472 cx: &mut Context<Self>,
16473 ) -> Task<Result<Navigated>> {
16474 let definition =
16475 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16476 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16477 cx.spawn_in(window, async move |editor, cx| {
16478 if definition.await? == Navigated::Yes {
16479 return Ok(Navigated::Yes);
16480 }
16481 match fallback_strategy {
16482 GoToDefinitionFallback::None => Ok(Navigated::No),
16483 GoToDefinitionFallback::FindAllReferences => {
16484 match editor.update_in(cx, |editor, window, cx| {
16485 editor.find_all_references(&FindAllReferences, window, cx)
16486 })? {
16487 Some(references) => references.await,
16488 None => Ok(Navigated::No),
16489 }
16490 }
16491 }
16492 })
16493 }
16494
16495 pub fn go_to_declaration(
16496 &mut self,
16497 _: &GoToDeclaration,
16498 window: &mut Window,
16499 cx: &mut Context<Self>,
16500 ) -> Task<Result<Navigated>> {
16501 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16502 }
16503
16504 pub fn go_to_declaration_split(
16505 &mut self,
16506 _: &GoToDeclaration,
16507 window: &mut Window,
16508 cx: &mut Context<Self>,
16509 ) -> Task<Result<Navigated>> {
16510 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16511 }
16512
16513 pub fn go_to_implementation(
16514 &mut self,
16515 _: &GoToImplementation,
16516 window: &mut Window,
16517 cx: &mut Context<Self>,
16518 ) -> Task<Result<Navigated>> {
16519 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16520 }
16521
16522 pub fn go_to_implementation_split(
16523 &mut self,
16524 _: &GoToImplementationSplit,
16525 window: &mut Window,
16526 cx: &mut Context<Self>,
16527 ) -> Task<Result<Navigated>> {
16528 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16529 }
16530
16531 pub fn go_to_type_definition(
16532 &mut self,
16533 _: &GoToTypeDefinition,
16534 window: &mut Window,
16535 cx: &mut Context<Self>,
16536 ) -> Task<Result<Navigated>> {
16537 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16538 }
16539
16540 pub fn go_to_definition_split(
16541 &mut self,
16542 _: &GoToDefinitionSplit,
16543 window: &mut Window,
16544 cx: &mut Context<Self>,
16545 ) -> Task<Result<Navigated>> {
16546 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16547 }
16548
16549 pub fn go_to_type_definition_split(
16550 &mut self,
16551 _: &GoToTypeDefinitionSplit,
16552 window: &mut Window,
16553 cx: &mut Context<Self>,
16554 ) -> Task<Result<Navigated>> {
16555 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16556 }
16557
16558 fn go_to_definition_of_kind(
16559 &mut self,
16560 kind: GotoDefinitionKind,
16561 split: bool,
16562 window: &mut Window,
16563 cx: &mut Context<Self>,
16564 ) -> Task<Result<Navigated>> {
16565 let Some(provider) = self.semantics_provider.clone() else {
16566 return Task::ready(Ok(Navigated::No));
16567 };
16568 let head = self
16569 .selections
16570 .newest::<usize>(&self.display_snapshot(cx))
16571 .head();
16572 let buffer = self.buffer.read(cx);
16573 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16574 return Task::ready(Ok(Navigated::No));
16575 };
16576 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16577 return Task::ready(Ok(Navigated::No));
16578 };
16579
16580 cx.spawn_in(window, async move |editor, cx| {
16581 let Some(definitions) = definitions.await? else {
16582 return Ok(Navigated::No);
16583 };
16584 let navigated = editor
16585 .update_in(cx, |editor, window, cx| {
16586 editor.navigate_to_hover_links(
16587 Some(kind),
16588 definitions
16589 .into_iter()
16590 .filter(|location| {
16591 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16592 })
16593 .map(HoverLink::Text)
16594 .collect::<Vec<_>>(),
16595 split,
16596 window,
16597 cx,
16598 )
16599 })?
16600 .await?;
16601 anyhow::Ok(navigated)
16602 })
16603 }
16604
16605 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16606 let selection = self.selections.newest_anchor();
16607 let head = selection.head();
16608 let tail = selection.tail();
16609
16610 let Some((buffer, start_position)) =
16611 self.buffer.read(cx).text_anchor_for_position(head, cx)
16612 else {
16613 return;
16614 };
16615
16616 let end_position = if head != tail {
16617 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16618 return;
16619 };
16620 Some(pos)
16621 } else {
16622 None
16623 };
16624
16625 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16626 let url = if let Some(end_pos) = end_position {
16627 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16628 } else {
16629 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16630 };
16631
16632 if let Some(url) = url {
16633 cx.update(|window, cx| {
16634 if parse_zed_link(&url, cx).is_some() {
16635 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16636 } else {
16637 cx.open_url(&url);
16638 }
16639 })?;
16640 }
16641
16642 anyhow::Ok(())
16643 });
16644
16645 url_finder.detach();
16646 }
16647
16648 pub fn open_selected_filename(
16649 &mut self,
16650 _: &OpenSelectedFilename,
16651 window: &mut Window,
16652 cx: &mut Context<Self>,
16653 ) {
16654 let Some(workspace) = self.workspace() else {
16655 return;
16656 };
16657
16658 let position = self.selections.newest_anchor().head();
16659
16660 let Some((buffer, buffer_position)) =
16661 self.buffer.read(cx).text_anchor_for_position(position, cx)
16662 else {
16663 return;
16664 };
16665
16666 let project = self.project.clone();
16667
16668 cx.spawn_in(window, async move |_, cx| {
16669 let result = find_file(&buffer, project, buffer_position, cx).await;
16670
16671 if let Some((_, path)) = result {
16672 workspace
16673 .update_in(cx, |workspace, window, cx| {
16674 workspace.open_resolved_path(path, window, cx)
16675 })?
16676 .await?;
16677 }
16678 anyhow::Ok(())
16679 })
16680 .detach();
16681 }
16682
16683 pub(crate) fn navigate_to_hover_links(
16684 &mut self,
16685 kind: Option<GotoDefinitionKind>,
16686 definitions: Vec<HoverLink>,
16687 split: bool,
16688 window: &mut Window,
16689 cx: &mut Context<Editor>,
16690 ) -> Task<Result<Navigated>> {
16691 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16692 let mut first_url_or_file = None;
16693 let definitions: Vec<_> = definitions
16694 .into_iter()
16695 .filter_map(|def| match def {
16696 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16697 HoverLink::InlayHint(lsp_location, server_id) => {
16698 let computation =
16699 self.compute_target_location(lsp_location, server_id, window, cx);
16700 Some(cx.background_spawn(computation))
16701 }
16702 HoverLink::Url(url) => {
16703 first_url_or_file = Some(Either::Left(url));
16704 None
16705 }
16706 HoverLink::File(path) => {
16707 first_url_or_file = Some(Either::Right(path));
16708 None
16709 }
16710 })
16711 .collect();
16712
16713 let workspace = self.workspace();
16714
16715 cx.spawn_in(window, async move |editor, cx| {
16716 let locations: Vec<Location> = future::join_all(definitions)
16717 .await
16718 .into_iter()
16719 .filter_map(|location| location.transpose())
16720 .collect::<Result<_>>()
16721 .context("location tasks")?;
16722 let mut locations = cx.update(|_, cx| {
16723 locations
16724 .into_iter()
16725 .map(|location| {
16726 let buffer = location.buffer.read(cx);
16727 (location.buffer, location.range.to_point(buffer))
16728 })
16729 .into_group_map()
16730 })?;
16731 let mut num_locations = 0;
16732 for ranges in locations.values_mut() {
16733 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16734 ranges.dedup();
16735 num_locations += ranges.len();
16736 }
16737
16738 if num_locations > 1 {
16739 let Some(workspace) = workspace else {
16740 return Ok(Navigated::No);
16741 };
16742
16743 let tab_kind = match kind {
16744 Some(GotoDefinitionKind::Implementation) => "Implementations",
16745 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16746 Some(GotoDefinitionKind::Declaration) => "Declarations",
16747 Some(GotoDefinitionKind::Type) => "Types",
16748 };
16749 let title = editor
16750 .update_in(cx, |_, _, cx| {
16751 let target = locations
16752 .iter()
16753 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16754 .map(|(buffer, location)| {
16755 buffer
16756 .read(cx)
16757 .text_for_range(location.clone())
16758 .collect::<String>()
16759 })
16760 .filter(|text| !text.contains('\n'))
16761 .unique()
16762 .take(3)
16763 .join(", ");
16764 if target.is_empty() {
16765 tab_kind.to_owned()
16766 } else {
16767 format!("{tab_kind} for {target}")
16768 }
16769 })
16770 .context("buffer title")?;
16771
16772 let opened = workspace
16773 .update_in(cx, |workspace, window, cx| {
16774 Self::open_locations_in_multibuffer(
16775 workspace,
16776 locations,
16777 title,
16778 split,
16779 MultibufferSelectionMode::First,
16780 window,
16781 cx,
16782 )
16783 })
16784 .is_ok();
16785
16786 anyhow::Ok(Navigated::from_bool(opened))
16787 } else if num_locations == 0 {
16788 // If there is one url or file, open it directly
16789 match first_url_or_file {
16790 Some(Either::Left(url)) => {
16791 cx.update(|_, cx| cx.open_url(&url))?;
16792 Ok(Navigated::Yes)
16793 }
16794 Some(Either::Right(path)) => {
16795 let Some(workspace) = workspace else {
16796 return Ok(Navigated::No);
16797 };
16798
16799 workspace
16800 .update_in(cx, |workspace, window, cx| {
16801 workspace.open_resolved_path(path, window, cx)
16802 })?
16803 .await?;
16804 Ok(Navigated::Yes)
16805 }
16806 None => Ok(Navigated::No),
16807 }
16808 } else {
16809 let Some(workspace) = workspace else {
16810 return Ok(Navigated::No);
16811 };
16812
16813 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16814 let target_range = target_ranges.first().unwrap().clone();
16815
16816 editor.update_in(cx, |editor, window, cx| {
16817 let range = target_range.to_point(target_buffer.read(cx));
16818 let range = editor.range_for_match(&range, false);
16819 let range = collapse_multiline_range(range);
16820
16821 if !split
16822 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16823 {
16824 editor.go_to_singleton_buffer_range(range, window, cx);
16825 } else {
16826 let pane = workspace.read(cx).active_pane().clone();
16827 window.defer(cx, move |window, cx| {
16828 let target_editor: Entity<Self> =
16829 workspace.update(cx, |workspace, cx| {
16830 let pane = if split {
16831 workspace.adjacent_pane(window, cx)
16832 } else {
16833 workspace.active_pane().clone()
16834 };
16835
16836 workspace.open_project_item(
16837 pane,
16838 target_buffer.clone(),
16839 true,
16840 true,
16841 window,
16842 cx,
16843 )
16844 });
16845 target_editor.update(cx, |target_editor, cx| {
16846 // When selecting a definition in a different buffer, disable the nav history
16847 // to avoid creating a history entry at the previous cursor location.
16848 pane.update(cx, |pane, _| pane.disable_history());
16849 target_editor.go_to_singleton_buffer_range(range, window, cx);
16850 pane.update(cx, |pane, _| pane.enable_history());
16851 });
16852 });
16853 }
16854 Navigated::Yes
16855 })
16856 }
16857 })
16858 }
16859
16860 fn compute_target_location(
16861 &self,
16862 lsp_location: lsp::Location,
16863 server_id: LanguageServerId,
16864 window: &mut Window,
16865 cx: &mut Context<Self>,
16866 ) -> Task<anyhow::Result<Option<Location>>> {
16867 let Some(project) = self.project.clone() else {
16868 return Task::ready(Ok(None));
16869 };
16870
16871 cx.spawn_in(window, async move |editor, cx| {
16872 let location_task = editor.update(cx, |_, cx| {
16873 project.update(cx, |project, cx| {
16874 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16875 })
16876 })?;
16877 let location = Some({
16878 let target_buffer_handle = location_task.await.context("open local buffer")?;
16879 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16880 let target_start = target_buffer
16881 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16882 let target_end = target_buffer
16883 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16884 target_buffer.anchor_after(target_start)
16885 ..target_buffer.anchor_before(target_end)
16886 })?;
16887 Location {
16888 buffer: target_buffer_handle,
16889 range,
16890 }
16891 });
16892 Ok(location)
16893 })
16894 }
16895
16896 fn go_to_next_reference(
16897 &mut self,
16898 _: &GoToNextReference,
16899 window: &mut Window,
16900 cx: &mut Context<Self>,
16901 ) {
16902 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
16903 if let Some(task) = task {
16904 task.detach();
16905 };
16906 }
16907
16908 fn go_to_prev_reference(
16909 &mut self,
16910 _: &GoToPreviousReference,
16911 window: &mut Window,
16912 cx: &mut Context<Self>,
16913 ) {
16914 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
16915 if let Some(task) = task {
16916 task.detach();
16917 };
16918 }
16919
16920 pub fn go_to_reference_before_or_after_position(
16921 &mut self,
16922 direction: Direction,
16923 count: usize,
16924 window: &mut Window,
16925 cx: &mut Context<Self>,
16926 ) -> Option<Task<Result<()>>> {
16927 let selection = self.selections.newest_anchor();
16928 let head = selection.head();
16929
16930 let multi_buffer = self.buffer.read(cx);
16931
16932 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
16933 let workspace = self.workspace()?;
16934 let project = workspace.read(cx).project().clone();
16935 let references =
16936 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
16937 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
16938 let Some(locations) = references.await? else {
16939 return Ok(());
16940 };
16941
16942 if locations.is_empty() {
16943 // totally normal - the cursor may be on something which is not
16944 // a symbol (e.g. a keyword)
16945 log::info!("no references found under cursor");
16946 return Ok(());
16947 }
16948
16949 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
16950
16951 let multi_buffer_snapshot =
16952 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
16953
16954 let (locations, current_location_index) =
16955 multi_buffer.update(cx, |multi_buffer, cx| {
16956 let mut locations = locations
16957 .into_iter()
16958 .filter_map(|loc| {
16959 let start = multi_buffer.buffer_anchor_to_anchor(
16960 &loc.buffer,
16961 loc.range.start,
16962 cx,
16963 )?;
16964 let end = multi_buffer.buffer_anchor_to_anchor(
16965 &loc.buffer,
16966 loc.range.end,
16967 cx,
16968 )?;
16969 Some(start..end)
16970 })
16971 .collect::<Vec<_>>();
16972
16973 // There is an O(n) implementation, but given this list will be
16974 // small (usually <100 items), the extra O(log(n)) factor isn't
16975 // worth the (surprisingly large amount of) extra complexity.
16976 locations
16977 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
16978
16979 let head_offset = head.to_offset(&multi_buffer_snapshot);
16980
16981 let current_location_index = locations.iter().position(|loc| {
16982 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
16983 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
16984 });
16985
16986 (locations, current_location_index)
16987 })?;
16988
16989 let Some(current_location_index) = current_location_index else {
16990 // This indicates something has gone wrong, because we already
16991 // handle the "no references" case above
16992 log::error!(
16993 "failed to find current reference under cursor. Total references: {}",
16994 locations.len()
16995 );
16996 return Ok(());
16997 };
16998
16999 let destination_location_index = match direction {
17000 Direction::Next => (current_location_index + count) % locations.len(),
17001 Direction::Prev => {
17002 (current_location_index + locations.len() - count % locations.len())
17003 % locations.len()
17004 }
17005 };
17006
17007 // TODO(cameron): is this needed?
17008 // the thinking is to avoid "jumping to the current location" (avoid
17009 // polluting "jumplist" in vim terms)
17010 if current_location_index == destination_location_index {
17011 return Ok(());
17012 }
17013
17014 let Range { start, end } = locations[destination_location_index];
17015
17016 editor.update_in(cx, |editor, window, cx| {
17017 let effects = SelectionEffects::default();
17018
17019 editor.unfold_ranges(&[start..end], false, false, cx);
17020 editor.change_selections(effects, window, cx, |s| {
17021 s.select_ranges([start..start]);
17022 });
17023 })?;
17024
17025 Ok(())
17026 }))
17027 }
17028
17029 pub fn find_all_references(
17030 &mut self,
17031 _: &FindAllReferences,
17032 window: &mut Window,
17033 cx: &mut Context<Self>,
17034 ) -> Option<Task<Result<Navigated>>> {
17035 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
17036 let multi_buffer = self.buffer.read(cx);
17037 let head = selection.head();
17038
17039 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17040 let head_anchor = multi_buffer_snapshot.anchor_at(
17041 head,
17042 if head < selection.tail() {
17043 Bias::Right
17044 } else {
17045 Bias::Left
17046 },
17047 );
17048
17049 match self
17050 .find_all_references_task_sources
17051 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17052 {
17053 Ok(_) => {
17054 log::info!(
17055 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17056 );
17057 return None;
17058 }
17059 Err(i) => {
17060 self.find_all_references_task_sources.insert(i, head_anchor);
17061 }
17062 }
17063
17064 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17065 let workspace = self.workspace()?;
17066 let project = workspace.read(cx).project().clone();
17067 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17068 Some(cx.spawn_in(window, async move |editor, cx| {
17069 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17070 if let Ok(i) = editor
17071 .find_all_references_task_sources
17072 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17073 {
17074 editor.find_all_references_task_sources.remove(i);
17075 }
17076 });
17077
17078 let Some(locations) = references.await? else {
17079 return anyhow::Ok(Navigated::No);
17080 };
17081 let mut locations = cx.update(|_, cx| {
17082 locations
17083 .into_iter()
17084 .map(|location| {
17085 let buffer = location.buffer.read(cx);
17086 (location.buffer, location.range.to_point(buffer))
17087 })
17088 .into_group_map()
17089 })?;
17090 if locations.is_empty() {
17091 return anyhow::Ok(Navigated::No);
17092 }
17093 for ranges in locations.values_mut() {
17094 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17095 ranges.dedup();
17096 }
17097
17098 workspace.update_in(cx, |workspace, window, cx| {
17099 let target = locations
17100 .iter()
17101 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17102 .map(|(buffer, location)| {
17103 buffer
17104 .read(cx)
17105 .text_for_range(location.clone())
17106 .collect::<String>()
17107 })
17108 .filter(|text| !text.contains('\n'))
17109 .unique()
17110 .take(3)
17111 .join(", ");
17112 let title = if target.is_empty() {
17113 "References".to_owned()
17114 } else {
17115 format!("References to {target}")
17116 };
17117 Self::open_locations_in_multibuffer(
17118 workspace,
17119 locations,
17120 title,
17121 false,
17122 MultibufferSelectionMode::First,
17123 window,
17124 cx,
17125 );
17126 Navigated::Yes
17127 })
17128 }))
17129 }
17130
17131 /// Opens a multibuffer with the given project locations in it
17132 pub fn open_locations_in_multibuffer(
17133 workspace: &mut Workspace,
17134 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17135 title: String,
17136 split: bool,
17137 multibuffer_selection_mode: MultibufferSelectionMode,
17138 window: &mut Window,
17139 cx: &mut Context<Workspace>,
17140 ) {
17141 if locations.is_empty() {
17142 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17143 return;
17144 }
17145
17146 let capability = workspace.project().read(cx).capability();
17147 let mut ranges = <Vec<Range<Anchor>>>::new();
17148
17149 // a key to find existing multibuffer editors with the same set of locations
17150 // to prevent us from opening more and more multibuffer tabs for searches and the like
17151 let mut key = (title.clone(), vec![]);
17152 let excerpt_buffer = cx.new(|cx| {
17153 let key = &mut key.1;
17154 let mut multibuffer = MultiBuffer::new(capability);
17155 for (buffer, mut ranges_for_buffer) in locations {
17156 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17157 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17158 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17159 PathKey::for_buffer(&buffer, cx),
17160 buffer.clone(),
17161 ranges_for_buffer,
17162 multibuffer_context_lines(cx),
17163 cx,
17164 );
17165 ranges.extend(new_ranges)
17166 }
17167
17168 multibuffer.with_title(title)
17169 });
17170 let existing = workspace.active_pane().update(cx, |pane, cx| {
17171 pane.items()
17172 .filter_map(|item| item.downcast::<Editor>())
17173 .find(|editor| {
17174 editor
17175 .read(cx)
17176 .lookup_key
17177 .as_ref()
17178 .and_then(|it| {
17179 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17180 })
17181 .is_some_and(|it| *it == key)
17182 })
17183 });
17184 let editor = existing.unwrap_or_else(|| {
17185 cx.new(|cx| {
17186 let mut editor = Editor::for_multibuffer(
17187 excerpt_buffer,
17188 Some(workspace.project().clone()),
17189 window,
17190 cx,
17191 );
17192 editor.lookup_key = Some(Box::new(key));
17193 editor
17194 })
17195 });
17196 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17197 MultibufferSelectionMode::First => {
17198 if let Some(first_range) = ranges.first() {
17199 editor.change_selections(
17200 SelectionEffects::no_scroll(),
17201 window,
17202 cx,
17203 |selections| {
17204 selections.clear_disjoint();
17205 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17206 },
17207 );
17208 }
17209 editor.highlight_background::<Self>(
17210 &ranges,
17211 |theme| theme.colors().editor_highlighted_line_background,
17212 cx,
17213 );
17214 }
17215 MultibufferSelectionMode::All => {
17216 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17217 selections.clear_disjoint();
17218 selections.select_anchor_ranges(ranges);
17219 });
17220 }
17221 });
17222
17223 let item = Box::new(editor);
17224 let item_id = item.item_id();
17225
17226 if split {
17227 let pane = workspace.adjacent_pane(window, cx);
17228 workspace.add_item(pane, item, None, true, true, window, cx);
17229 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17230 let (preview_item_id, preview_item_idx) =
17231 workspace.active_pane().read_with(cx, |pane, _| {
17232 (pane.preview_item_id(), pane.preview_item_idx())
17233 });
17234
17235 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17236
17237 if let Some(preview_item_id) = preview_item_id {
17238 workspace.active_pane().update(cx, |pane, cx| {
17239 pane.remove_item(preview_item_id, false, false, window, cx);
17240 });
17241 }
17242 } else {
17243 workspace.add_item_to_active_pane(item, None, true, window, cx);
17244 }
17245 workspace.active_pane().update(cx, |pane, cx| {
17246 pane.set_preview_item_id(Some(item_id), cx);
17247 });
17248 }
17249
17250 pub fn rename(
17251 &mut self,
17252 _: &Rename,
17253 window: &mut Window,
17254 cx: &mut Context<Self>,
17255 ) -> Option<Task<Result<()>>> {
17256 use language::ToOffset as _;
17257
17258 let provider = self.semantics_provider.clone()?;
17259 let selection = self.selections.newest_anchor().clone();
17260 let (cursor_buffer, cursor_buffer_position) = self
17261 .buffer
17262 .read(cx)
17263 .text_anchor_for_position(selection.head(), cx)?;
17264 let (tail_buffer, cursor_buffer_position_end) = self
17265 .buffer
17266 .read(cx)
17267 .text_anchor_for_position(selection.tail(), cx)?;
17268 if tail_buffer != cursor_buffer {
17269 return None;
17270 }
17271
17272 let snapshot = cursor_buffer.read(cx).snapshot();
17273 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17274 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17275 let prepare_rename = provider
17276 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17277 .unwrap_or_else(|| Task::ready(Ok(None)));
17278 drop(snapshot);
17279
17280 Some(cx.spawn_in(window, async move |this, cx| {
17281 let rename_range = if let Some(range) = prepare_rename.await? {
17282 Some(range)
17283 } else {
17284 this.update(cx, |this, cx| {
17285 let buffer = this.buffer.read(cx).snapshot(cx);
17286 let mut buffer_highlights = this
17287 .document_highlights_for_position(selection.head(), &buffer)
17288 .filter(|highlight| {
17289 highlight.start.excerpt_id == selection.head().excerpt_id
17290 && highlight.end.excerpt_id == selection.head().excerpt_id
17291 });
17292 buffer_highlights
17293 .next()
17294 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17295 })?
17296 };
17297 if let Some(rename_range) = rename_range {
17298 this.update_in(cx, |this, window, cx| {
17299 let snapshot = cursor_buffer.read(cx).snapshot();
17300 let rename_buffer_range = rename_range.to_offset(&snapshot);
17301 let cursor_offset_in_rename_range =
17302 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17303 let cursor_offset_in_rename_range_end =
17304 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17305
17306 this.take_rename(false, window, cx);
17307 let buffer = this.buffer.read(cx).read(cx);
17308 let cursor_offset = selection.head().to_offset(&buffer);
17309 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17310 let rename_end = rename_start + rename_buffer_range.len();
17311 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17312 let mut old_highlight_id = None;
17313 let old_name: Arc<str> = buffer
17314 .chunks(rename_start..rename_end, true)
17315 .map(|chunk| {
17316 if old_highlight_id.is_none() {
17317 old_highlight_id = chunk.syntax_highlight_id;
17318 }
17319 chunk.text
17320 })
17321 .collect::<String>()
17322 .into();
17323
17324 drop(buffer);
17325
17326 // Position the selection in the rename editor so that it matches the current selection.
17327 this.show_local_selections = false;
17328 let rename_editor = cx.new(|cx| {
17329 let mut editor = Editor::single_line(window, cx);
17330 editor.buffer.update(cx, |buffer, cx| {
17331 buffer.edit([(0..0, old_name.clone())], None, cx)
17332 });
17333 let rename_selection_range = match cursor_offset_in_rename_range
17334 .cmp(&cursor_offset_in_rename_range_end)
17335 {
17336 Ordering::Equal => {
17337 editor.select_all(&SelectAll, window, cx);
17338 return editor;
17339 }
17340 Ordering::Less => {
17341 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17342 }
17343 Ordering::Greater => {
17344 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17345 }
17346 };
17347 if rename_selection_range.end > old_name.len() {
17348 editor.select_all(&SelectAll, window, cx);
17349 } else {
17350 editor.change_selections(Default::default(), window, cx, |s| {
17351 s.select_ranges([rename_selection_range]);
17352 });
17353 }
17354 editor
17355 });
17356 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17357 if e == &EditorEvent::Focused {
17358 cx.emit(EditorEvent::FocusedIn)
17359 }
17360 })
17361 .detach();
17362
17363 let write_highlights =
17364 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17365 let read_highlights =
17366 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17367 let ranges = write_highlights
17368 .iter()
17369 .flat_map(|(_, ranges)| ranges.iter())
17370 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17371 .cloned()
17372 .collect();
17373
17374 this.highlight_text::<Rename>(
17375 ranges,
17376 HighlightStyle {
17377 fade_out: Some(0.6),
17378 ..Default::default()
17379 },
17380 cx,
17381 );
17382 let rename_focus_handle = rename_editor.focus_handle(cx);
17383 window.focus(&rename_focus_handle);
17384 let block_id = this.insert_blocks(
17385 [BlockProperties {
17386 style: BlockStyle::Flex,
17387 placement: BlockPlacement::Below(range.start),
17388 height: Some(1),
17389 render: Arc::new({
17390 let rename_editor = rename_editor.clone();
17391 move |cx: &mut BlockContext| {
17392 let mut text_style = cx.editor_style.text.clone();
17393 if let Some(highlight_style) = old_highlight_id
17394 .and_then(|h| h.style(&cx.editor_style.syntax))
17395 {
17396 text_style = text_style.highlight(highlight_style);
17397 }
17398 div()
17399 .block_mouse_except_scroll()
17400 .pl(cx.anchor_x)
17401 .child(EditorElement::new(
17402 &rename_editor,
17403 EditorStyle {
17404 background: cx.theme().system().transparent,
17405 local_player: cx.editor_style.local_player,
17406 text: text_style,
17407 scrollbar_width: cx.editor_style.scrollbar_width,
17408 syntax: cx.editor_style.syntax.clone(),
17409 status: cx.editor_style.status.clone(),
17410 inlay_hints_style: HighlightStyle {
17411 font_weight: Some(FontWeight::BOLD),
17412 ..make_inlay_hints_style(cx.app)
17413 },
17414 edit_prediction_styles: make_suggestion_styles(
17415 cx.app,
17416 ),
17417 ..EditorStyle::default()
17418 },
17419 ))
17420 .into_any_element()
17421 }
17422 }),
17423 priority: 0,
17424 }],
17425 Some(Autoscroll::fit()),
17426 cx,
17427 )[0];
17428 this.pending_rename = Some(RenameState {
17429 range,
17430 old_name,
17431 editor: rename_editor,
17432 block_id,
17433 });
17434 })?;
17435 }
17436
17437 Ok(())
17438 }))
17439 }
17440
17441 pub fn confirm_rename(
17442 &mut self,
17443 _: &ConfirmRename,
17444 window: &mut Window,
17445 cx: &mut Context<Self>,
17446 ) -> Option<Task<Result<()>>> {
17447 let rename = self.take_rename(false, window, cx)?;
17448 let workspace = self.workspace()?.downgrade();
17449 let (buffer, start) = self
17450 .buffer
17451 .read(cx)
17452 .text_anchor_for_position(rename.range.start, cx)?;
17453 let (end_buffer, _) = self
17454 .buffer
17455 .read(cx)
17456 .text_anchor_for_position(rename.range.end, cx)?;
17457 if buffer != end_buffer {
17458 return None;
17459 }
17460
17461 let old_name = rename.old_name;
17462 let new_name = rename.editor.read(cx).text(cx);
17463
17464 let rename = self.semantics_provider.as_ref()?.perform_rename(
17465 &buffer,
17466 start,
17467 new_name.clone(),
17468 cx,
17469 )?;
17470
17471 Some(cx.spawn_in(window, async move |editor, cx| {
17472 let project_transaction = rename.await?;
17473 Self::open_project_transaction(
17474 &editor,
17475 workspace,
17476 project_transaction,
17477 format!("Rename: {} → {}", old_name, new_name),
17478 cx,
17479 )
17480 .await?;
17481
17482 editor.update(cx, |editor, cx| {
17483 editor.refresh_document_highlights(cx);
17484 })?;
17485 Ok(())
17486 }))
17487 }
17488
17489 fn take_rename(
17490 &mut self,
17491 moving_cursor: bool,
17492 window: &mut Window,
17493 cx: &mut Context<Self>,
17494 ) -> Option<RenameState> {
17495 let rename = self.pending_rename.take()?;
17496 if rename.editor.focus_handle(cx).is_focused(window) {
17497 window.focus(&self.focus_handle);
17498 }
17499
17500 self.remove_blocks(
17501 [rename.block_id].into_iter().collect(),
17502 Some(Autoscroll::fit()),
17503 cx,
17504 );
17505 self.clear_highlights::<Rename>(cx);
17506 self.show_local_selections = true;
17507
17508 if moving_cursor {
17509 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17510 editor
17511 .selections
17512 .newest::<usize>(&editor.display_snapshot(cx))
17513 .head()
17514 });
17515
17516 // Update the selection to match the position of the selection inside
17517 // the rename editor.
17518 let snapshot = self.buffer.read(cx).read(cx);
17519 let rename_range = rename.range.to_offset(&snapshot);
17520 let cursor_in_editor = snapshot
17521 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17522 .min(rename_range.end);
17523 drop(snapshot);
17524
17525 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17526 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17527 });
17528 } else {
17529 self.refresh_document_highlights(cx);
17530 }
17531
17532 Some(rename)
17533 }
17534
17535 pub fn pending_rename(&self) -> Option<&RenameState> {
17536 self.pending_rename.as_ref()
17537 }
17538
17539 fn format(
17540 &mut self,
17541 _: &Format,
17542 window: &mut Window,
17543 cx: &mut Context<Self>,
17544 ) -> Option<Task<Result<()>>> {
17545 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17546
17547 let project = match &self.project {
17548 Some(project) => project.clone(),
17549 None => return None,
17550 };
17551
17552 Some(self.perform_format(
17553 project,
17554 FormatTrigger::Manual,
17555 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17556 window,
17557 cx,
17558 ))
17559 }
17560
17561 fn format_selections(
17562 &mut self,
17563 _: &FormatSelections,
17564 window: &mut Window,
17565 cx: &mut Context<Self>,
17566 ) -> Option<Task<Result<()>>> {
17567 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17568
17569 let project = match &self.project {
17570 Some(project) => project.clone(),
17571 None => return None,
17572 };
17573
17574 let ranges = self
17575 .selections
17576 .all_adjusted(&self.display_snapshot(cx))
17577 .into_iter()
17578 .map(|selection| selection.range())
17579 .collect_vec();
17580
17581 Some(self.perform_format(
17582 project,
17583 FormatTrigger::Manual,
17584 FormatTarget::Ranges(ranges),
17585 window,
17586 cx,
17587 ))
17588 }
17589
17590 fn perform_format(
17591 &mut self,
17592 project: Entity<Project>,
17593 trigger: FormatTrigger,
17594 target: FormatTarget,
17595 window: &mut Window,
17596 cx: &mut Context<Self>,
17597 ) -> Task<Result<()>> {
17598 let buffer = self.buffer.clone();
17599 let (buffers, target) = match target {
17600 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17601 FormatTarget::Ranges(selection_ranges) => {
17602 let multi_buffer = buffer.read(cx);
17603 let snapshot = multi_buffer.read(cx);
17604 let mut buffers = HashSet::default();
17605 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17606 BTreeMap::new();
17607 for selection_range in selection_ranges {
17608 for (buffer, buffer_range, _) in
17609 snapshot.range_to_buffer_ranges(selection_range)
17610 {
17611 let buffer_id = buffer.remote_id();
17612 let start = buffer.anchor_before(buffer_range.start);
17613 let end = buffer.anchor_after(buffer_range.end);
17614 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17615 buffer_id_to_ranges
17616 .entry(buffer_id)
17617 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17618 .or_insert_with(|| vec![start..end]);
17619 }
17620 }
17621 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17622 }
17623 };
17624
17625 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17626 let selections_prev = transaction_id_prev
17627 .and_then(|transaction_id_prev| {
17628 // default to selections as they were after the last edit, if we have them,
17629 // instead of how they are now.
17630 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17631 // will take you back to where you made the last edit, instead of staying where you scrolled
17632 self.selection_history
17633 .transaction(transaction_id_prev)
17634 .map(|t| t.0.clone())
17635 })
17636 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17637
17638 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17639 let format = project.update(cx, |project, cx| {
17640 project.format(buffers, target, true, trigger, cx)
17641 });
17642
17643 cx.spawn_in(window, async move |editor, cx| {
17644 let transaction = futures::select_biased! {
17645 transaction = format.log_err().fuse() => transaction,
17646 () = timeout => {
17647 log::warn!("timed out waiting for formatting");
17648 None
17649 }
17650 };
17651
17652 buffer
17653 .update(cx, |buffer, cx| {
17654 if let Some(transaction) = transaction
17655 && !buffer.is_singleton()
17656 {
17657 buffer.push_transaction(&transaction.0, cx);
17658 }
17659 cx.notify();
17660 })
17661 .ok();
17662
17663 if let Some(transaction_id_now) =
17664 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17665 {
17666 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17667 if has_new_transaction {
17668 _ = editor.update(cx, |editor, _| {
17669 editor
17670 .selection_history
17671 .insert_transaction(transaction_id_now, selections_prev);
17672 });
17673 }
17674 }
17675
17676 Ok(())
17677 })
17678 }
17679
17680 fn organize_imports(
17681 &mut self,
17682 _: &OrganizeImports,
17683 window: &mut Window,
17684 cx: &mut Context<Self>,
17685 ) -> Option<Task<Result<()>>> {
17686 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17687 let project = match &self.project {
17688 Some(project) => project.clone(),
17689 None => return None,
17690 };
17691 Some(self.perform_code_action_kind(
17692 project,
17693 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17694 window,
17695 cx,
17696 ))
17697 }
17698
17699 fn perform_code_action_kind(
17700 &mut self,
17701 project: Entity<Project>,
17702 kind: CodeActionKind,
17703 window: &mut Window,
17704 cx: &mut Context<Self>,
17705 ) -> Task<Result<()>> {
17706 let buffer = self.buffer.clone();
17707 let buffers = buffer.read(cx).all_buffers();
17708 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17709 let apply_action = project.update(cx, |project, cx| {
17710 project.apply_code_action_kind(buffers, kind, true, cx)
17711 });
17712 cx.spawn_in(window, async move |_, cx| {
17713 let transaction = futures::select_biased! {
17714 () = timeout => {
17715 log::warn!("timed out waiting for executing code action");
17716 None
17717 }
17718 transaction = apply_action.log_err().fuse() => transaction,
17719 };
17720 buffer
17721 .update(cx, |buffer, cx| {
17722 // check if we need this
17723 if let Some(transaction) = transaction
17724 && !buffer.is_singleton()
17725 {
17726 buffer.push_transaction(&transaction.0, cx);
17727 }
17728 cx.notify();
17729 })
17730 .ok();
17731 Ok(())
17732 })
17733 }
17734
17735 pub fn restart_language_server(
17736 &mut self,
17737 _: &RestartLanguageServer,
17738 _: &mut Window,
17739 cx: &mut Context<Self>,
17740 ) {
17741 if let Some(project) = self.project.clone() {
17742 self.buffer.update(cx, |multi_buffer, cx| {
17743 project.update(cx, |project, cx| {
17744 project.restart_language_servers_for_buffers(
17745 multi_buffer.all_buffers().into_iter().collect(),
17746 HashSet::default(),
17747 cx,
17748 );
17749 });
17750 })
17751 }
17752 }
17753
17754 pub fn stop_language_server(
17755 &mut self,
17756 _: &StopLanguageServer,
17757 _: &mut Window,
17758 cx: &mut Context<Self>,
17759 ) {
17760 if let Some(project) = self.project.clone() {
17761 self.buffer.update(cx, |multi_buffer, cx| {
17762 project.update(cx, |project, cx| {
17763 project.stop_language_servers_for_buffers(
17764 multi_buffer.all_buffers().into_iter().collect(),
17765 HashSet::default(),
17766 cx,
17767 );
17768 });
17769 });
17770 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17771 }
17772 }
17773
17774 fn cancel_language_server_work(
17775 workspace: &mut Workspace,
17776 _: &actions::CancelLanguageServerWork,
17777 _: &mut Window,
17778 cx: &mut Context<Workspace>,
17779 ) {
17780 let project = workspace.project();
17781 let buffers = workspace
17782 .active_item(cx)
17783 .and_then(|item| item.act_as::<Editor>(cx))
17784 .map_or(HashSet::default(), |editor| {
17785 editor.read(cx).buffer.read(cx).all_buffers()
17786 });
17787 project.update(cx, |project, cx| {
17788 project.cancel_language_server_work_for_buffers(buffers, cx);
17789 });
17790 }
17791
17792 fn show_character_palette(
17793 &mut self,
17794 _: &ShowCharacterPalette,
17795 window: &mut Window,
17796 _: &mut Context<Self>,
17797 ) {
17798 window.show_character_palette();
17799 }
17800
17801 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17802 if !self.diagnostics_enabled() {
17803 return;
17804 }
17805
17806 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17807 let buffer = self.buffer.read(cx).snapshot(cx);
17808 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17809 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17810 let is_valid = buffer
17811 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17812 .any(|entry| {
17813 entry.diagnostic.is_primary
17814 && !entry.range.is_empty()
17815 && entry.range.start == primary_range_start
17816 && entry.diagnostic.message == active_diagnostics.active_message
17817 });
17818
17819 if !is_valid {
17820 self.dismiss_diagnostics(cx);
17821 }
17822 }
17823 }
17824
17825 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17826 match &self.active_diagnostics {
17827 ActiveDiagnostic::Group(group) => Some(group),
17828 _ => None,
17829 }
17830 }
17831
17832 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17833 if !self.diagnostics_enabled() {
17834 return;
17835 }
17836 self.dismiss_diagnostics(cx);
17837 self.active_diagnostics = ActiveDiagnostic::All;
17838 }
17839
17840 fn activate_diagnostics(
17841 &mut self,
17842 buffer_id: BufferId,
17843 diagnostic: DiagnosticEntryRef<'_, usize>,
17844 window: &mut Window,
17845 cx: &mut Context<Self>,
17846 ) {
17847 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17848 return;
17849 }
17850 self.dismiss_diagnostics(cx);
17851 let snapshot = self.snapshot(window, cx);
17852 let buffer = self.buffer.read(cx).snapshot(cx);
17853 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17854 return;
17855 };
17856
17857 let diagnostic_group = buffer
17858 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17859 .collect::<Vec<_>>();
17860
17861 let blocks =
17862 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17863
17864 let blocks = self.display_map.update(cx, |display_map, cx| {
17865 display_map.insert_blocks(blocks, cx).into_iter().collect()
17866 });
17867 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17868 active_range: buffer.anchor_before(diagnostic.range.start)
17869 ..buffer.anchor_after(diagnostic.range.end),
17870 active_message: diagnostic.diagnostic.message.clone(),
17871 group_id: diagnostic.diagnostic.group_id,
17872 blocks,
17873 });
17874 cx.notify();
17875 }
17876
17877 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17878 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17879 return;
17880 };
17881
17882 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17883 if let ActiveDiagnostic::Group(group) = prev {
17884 self.display_map.update(cx, |display_map, cx| {
17885 display_map.remove_blocks(group.blocks, cx);
17886 });
17887 cx.notify();
17888 }
17889 }
17890
17891 /// Disable inline diagnostics rendering for this editor.
17892 pub fn disable_inline_diagnostics(&mut self) {
17893 self.inline_diagnostics_enabled = false;
17894 self.inline_diagnostics_update = Task::ready(());
17895 self.inline_diagnostics.clear();
17896 }
17897
17898 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17899 self.diagnostics_enabled = false;
17900 self.dismiss_diagnostics(cx);
17901 self.inline_diagnostics_update = Task::ready(());
17902 self.inline_diagnostics.clear();
17903 }
17904
17905 pub fn disable_word_completions(&mut self) {
17906 self.word_completions_enabled = false;
17907 }
17908
17909 pub fn diagnostics_enabled(&self) -> bool {
17910 self.diagnostics_enabled && self.mode.is_full()
17911 }
17912
17913 pub fn inline_diagnostics_enabled(&self) -> bool {
17914 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17915 }
17916
17917 pub fn show_inline_diagnostics(&self) -> bool {
17918 self.show_inline_diagnostics
17919 }
17920
17921 pub fn toggle_inline_diagnostics(
17922 &mut self,
17923 _: &ToggleInlineDiagnostics,
17924 window: &mut Window,
17925 cx: &mut Context<Editor>,
17926 ) {
17927 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17928 self.refresh_inline_diagnostics(false, window, cx);
17929 }
17930
17931 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17932 self.diagnostics_max_severity = severity;
17933 self.display_map.update(cx, |display_map, _| {
17934 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17935 });
17936 }
17937
17938 pub fn toggle_diagnostics(
17939 &mut self,
17940 _: &ToggleDiagnostics,
17941 window: &mut Window,
17942 cx: &mut Context<Editor>,
17943 ) {
17944 if !self.diagnostics_enabled() {
17945 return;
17946 }
17947
17948 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17949 EditorSettings::get_global(cx)
17950 .diagnostics_max_severity
17951 .filter(|severity| severity != &DiagnosticSeverity::Off)
17952 .unwrap_or(DiagnosticSeverity::Hint)
17953 } else {
17954 DiagnosticSeverity::Off
17955 };
17956 self.set_max_diagnostics_severity(new_severity, cx);
17957 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17958 self.active_diagnostics = ActiveDiagnostic::None;
17959 self.inline_diagnostics_update = Task::ready(());
17960 self.inline_diagnostics.clear();
17961 } else {
17962 self.refresh_inline_diagnostics(false, window, cx);
17963 }
17964
17965 cx.notify();
17966 }
17967
17968 pub fn toggle_minimap(
17969 &mut self,
17970 _: &ToggleMinimap,
17971 window: &mut Window,
17972 cx: &mut Context<Editor>,
17973 ) {
17974 if self.supports_minimap(cx) {
17975 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17976 }
17977 }
17978
17979 fn refresh_inline_diagnostics(
17980 &mut self,
17981 debounce: bool,
17982 window: &mut Window,
17983 cx: &mut Context<Self>,
17984 ) {
17985 let max_severity = ProjectSettings::get_global(cx)
17986 .diagnostics
17987 .inline
17988 .max_severity
17989 .unwrap_or(self.diagnostics_max_severity);
17990
17991 if !self.inline_diagnostics_enabled()
17992 || !self.diagnostics_enabled()
17993 || !self.show_inline_diagnostics
17994 || max_severity == DiagnosticSeverity::Off
17995 {
17996 self.inline_diagnostics_update = Task::ready(());
17997 self.inline_diagnostics.clear();
17998 return;
17999 }
18000
18001 let debounce_ms = ProjectSettings::get_global(cx)
18002 .diagnostics
18003 .inline
18004 .update_debounce_ms;
18005 let debounce = if debounce && debounce_ms > 0 {
18006 Some(Duration::from_millis(debounce_ms))
18007 } else {
18008 None
18009 };
18010 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18011 if let Some(debounce) = debounce {
18012 cx.background_executor().timer(debounce).await;
18013 }
18014 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18015 editor
18016 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18017 .ok()
18018 }) else {
18019 return;
18020 };
18021
18022 let new_inline_diagnostics = cx
18023 .background_spawn(async move {
18024 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18025 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
18026 let message = diagnostic_entry
18027 .diagnostic
18028 .message
18029 .split_once('\n')
18030 .map(|(line, _)| line)
18031 .map(SharedString::new)
18032 .unwrap_or_else(|| {
18033 SharedString::new(&*diagnostic_entry.diagnostic.message)
18034 });
18035 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18036 let (Ok(i) | Err(i)) = inline_diagnostics
18037 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18038 inline_diagnostics.insert(
18039 i,
18040 (
18041 start_anchor,
18042 InlineDiagnostic {
18043 message,
18044 group_id: diagnostic_entry.diagnostic.group_id,
18045 start: diagnostic_entry.range.start.to_point(&snapshot),
18046 is_primary: diagnostic_entry.diagnostic.is_primary,
18047 severity: diagnostic_entry.diagnostic.severity,
18048 },
18049 ),
18050 );
18051 }
18052 inline_diagnostics
18053 })
18054 .await;
18055
18056 editor
18057 .update(cx, |editor, cx| {
18058 editor.inline_diagnostics = new_inline_diagnostics;
18059 cx.notify();
18060 })
18061 .ok();
18062 });
18063 }
18064
18065 fn pull_diagnostics(
18066 &mut self,
18067 buffer_id: Option<BufferId>,
18068 window: &Window,
18069 cx: &mut Context<Self>,
18070 ) -> Option<()> {
18071 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18072 return None;
18073 }
18074 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18075 .diagnostics
18076 .lsp_pull_diagnostics;
18077 if !pull_diagnostics_settings.enabled {
18078 return None;
18079 }
18080 let project = self.project()?.downgrade();
18081 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18082 let mut buffers = self.buffer.read(cx).all_buffers();
18083 buffers.retain(|buffer| {
18084 let buffer_id_to_retain = buffer.read(cx).remote_id();
18085 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18086 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18087 });
18088 if buffers.is_empty() {
18089 self.pull_diagnostics_task = Task::ready(());
18090 return None;
18091 }
18092
18093 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18094 cx.background_executor().timer(debounce).await;
18095
18096 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18097 buffers
18098 .into_iter()
18099 .filter_map(|buffer| {
18100 project
18101 .update(cx, |project, cx| {
18102 project.lsp_store().update(cx, |lsp_store, cx| {
18103 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18104 })
18105 })
18106 .ok()
18107 })
18108 .collect::<FuturesUnordered<_>>()
18109 }) else {
18110 return;
18111 };
18112
18113 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18114 match pull_task {
18115 Ok(()) => {
18116 if editor
18117 .update_in(cx, |editor, window, cx| {
18118 editor.update_diagnostics_state(window, cx);
18119 })
18120 .is_err()
18121 {
18122 return;
18123 }
18124 }
18125 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18126 }
18127 }
18128 });
18129
18130 Some(())
18131 }
18132
18133 pub fn set_selections_from_remote(
18134 &mut self,
18135 selections: Vec<Selection<Anchor>>,
18136 pending_selection: Option<Selection<Anchor>>,
18137 window: &mut Window,
18138 cx: &mut Context<Self>,
18139 ) {
18140 let old_cursor_position = self.selections.newest_anchor().head();
18141 self.selections
18142 .change_with(&self.display_snapshot(cx), |s| {
18143 s.select_anchors(selections);
18144 if let Some(pending_selection) = pending_selection {
18145 s.set_pending(pending_selection, SelectMode::Character);
18146 } else {
18147 s.clear_pending();
18148 }
18149 });
18150 self.selections_did_change(
18151 false,
18152 &old_cursor_position,
18153 SelectionEffects::default(),
18154 window,
18155 cx,
18156 );
18157 }
18158
18159 pub fn transact(
18160 &mut self,
18161 window: &mut Window,
18162 cx: &mut Context<Self>,
18163 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18164 ) -> Option<TransactionId> {
18165 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18166 this.start_transaction_at(Instant::now(), window, cx);
18167 update(this, window, cx);
18168 this.end_transaction_at(Instant::now(), cx)
18169 })
18170 }
18171
18172 pub fn start_transaction_at(
18173 &mut self,
18174 now: Instant,
18175 window: &mut Window,
18176 cx: &mut Context<Self>,
18177 ) -> Option<TransactionId> {
18178 self.end_selection(window, cx);
18179 if let Some(tx_id) = self
18180 .buffer
18181 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18182 {
18183 self.selection_history
18184 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18185 cx.emit(EditorEvent::TransactionBegun {
18186 transaction_id: tx_id,
18187 });
18188 Some(tx_id)
18189 } else {
18190 None
18191 }
18192 }
18193
18194 pub fn end_transaction_at(
18195 &mut self,
18196 now: Instant,
18197 cx: &mut Context<Self>,
18198 ) -> Option<TransactionId> {
18199 if let Some(transaction_id) = self
18200 .buffer
18201 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18202 {
18203 if let Some((_, end_selections)) =
18204 self.selection_history.transaction_mut(transaction_id)
18205 {
18206 *end_selections = Some(self.selections.disjoint_anchors_arc());
18207 } else {
18208 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18209 }
18210
18211 cx.emit(EditorEvent::Edited { transaction_id });
18212 Some(transaction_id)
18213 } else {
18214 None
18215 }
18216 }
18217
18218 pub fn modify_transaction_selection_history(
18219 &mut self,
18220 transaction_id: TransactionId,
18221 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18222 ) -> bool {
18223 self.selection_history
18224 .transaction_mut(transaction_id)
18225 .map(modify)
18226 .is_some()
18227 }
18228
18229 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18230 if self.selection_mark_mode {
18231 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18232 s.move_with(|_, sel| {
18233 sel.collapse_to(sel.head(), SelectionGoal::None);
18234 });
18235 })
18236 }
18237 self.selection_mark_mode = true;
18238 cx.notify();
18239 }
18240
18241 pub fn swap_selection_ends(
18242 &mut self,
18243 _: &actions::SwapSelectionEnds,
18244 window: &mut Window,
18245 cx: &mut Context<Self>,
18246 ) {
18247 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18248 s.move_with(|_, sel| {
18249 if sel.start != sel.end {
18250 sel.reversed = !sel.reversed
18251 }
18252 });
18253 });
18254 self.request_autoscroll(Autoscroll::newest(), cx);
18255 cx.notify();
18256 }
18257
18258 pub fn toggle_focus(
18259 workspace: &mut Workspace,
18260 _: &actions::ToggleFocus,
18261 window: &mut Window,
18262 cx: &mut Context<Workspace>,
18263 ) {
18264 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18265 return;
18266 };
18267 workspace.activate_item(&item, true, true, window, cx);
18268 }
18269
18270 pub fn toggle_fold(
18271 &mut self,
18272 _: &actions::ToggleFold,
18273 window: &mut Window,
18274 cx: &mut Context<Self>,
18275 ) {
18276 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18277 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18278 let selection = self.selections.newest::<Point>(&display_map);
18279
18280 let range = if selection.is_empty() {
18281 let point = selection.head().to_display_point(&display_map);
18282 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18283 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18284 .to_point(&display_map);
18285 start..end
18286 } else {
18287 selection.range()
18288 };
18289 if display_map.folds_in_range(range).next().is_some() {
18290 self.unfold_lines(&Default::default(), window, cx)
18291 } else {
18292 self.fold(&Default::default(), window, cx)
18293 }
18294 } else {
18295 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18296 let buffer_ids: HashSet<_> = self
18297 .selections
18298 .disjoint_anchor_ranges()
18299 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18300 .collect();
18301
18302 let should_unfold = buffer_ids
18303 .iter()
18304 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18305
18306 for buffer_id in buffer_ids {
18307 if should_unfold {
18308 self.unfold_buffer(buffer_id, cx);
18309 } else {
18310 self.fold_buffer(buffer_id, cx);
18311 }
18312 }
18313 }
18314 }
18315
18316 pub fn toggle_fold_recursive(
18317 &mut self,
18318 _: &actions::ToggleFoldRecursive,
18319 window: &mut Window,
18320 cx: &mut Context<Self>,
18321 ) {
18322 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18323
18324 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18325 let range = if selection.is_empty() {
18326 let point = selection.head().to_display_point(&display_map);
18327 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18328 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18329 .to_point(&display_map);
18330 start..end
18331 } else {
18332 selection.range()
18333 };
18334 if display_map.folds_in_range(range).next().is_some() {
18335 self.unfold_recursive(&Default::default(), window, cx)
18336 } else {
18337 self.fold_recursive(&Default::default(), window, cx)
18338 }
18339 }
18340
18341 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18342 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18343 let mut to_fold = Vec::new();
18344 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18345 let selections = self.selections.all_adjusted(&display_map);
18346
18347 for selection in selections {
18348 let range = selection.range().sorted();
18349 let buffer_start_row = range.start.row;
18350
18351 if range.start.row != range.end.row {
18352 let mut found = false;
18353 let mut row = range.start.row;
18354 while row <= range.end.row {
18355 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18356 {
18357 found = true;
18358 row = crease.range().end.row + 1;
18359 to_fold.push(crease);
18360 } else {
18361 row += 1
18362 }
18363 }
18364 if found {
18365 continue;
18366 }
18367 }
18368
18369 for row in (0..=range.start.row).rev() {
18370 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18371 && crease.range().end.row >= buffer_start_row
18372 {
18373 to_fold.push(crease);
18374 if row <= range.start.row {
18375 break;
18376 }
18377 }
18378 }
18379 }
18380
18381 self.fold_creases(to_fold, true, window, cx);
18382 } else {
18383 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18384 let buffer_ids = self
18385 .selections
18386 .disjoint_anchor_ranges()
18387 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18388 .collect::<HashSet<_>>();
18389 for buffer_id in buffer_ids {
18390 self.fold_buffer(buffer_id, cx);
18391 }
18392 }
18393 }
18394
18395 pub fn toggle_fold_all(
18396 &mut self,
18397 _: &actions::ToggleFoldAll,
18398 window: &mut Window,
18399 cx: &mut Context<Self>,
18400 ) {
18401 if self.buffer.read(cx).is_singleton() {
18402 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18403 let has_folds = display_map
18404 .folds_in_range(0..display_map.buffer_snapshot().len())
18405 .next()
18406 .is_some();
18407
18408 if has_folds {
18409 self.unfold_all(&actions::UnfoldAll, window, cx);
18410 } else {
18411 self.fold_all(&actions::FoldAll, window, cx);
18412 }
18413 } else {
18414 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18415 let should_unfold = buffer_ids
18416 .iter()
18417 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18418
18419 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18420 editor
18421 .update_in(cx, |editor, _, cx| {
18422 for buffer_id in buffer_ids {
18423 if should_unfold {
18424 editor.unfold_buffer(buffer_id, cx);
18425 } else {
18426 editor.fold_buffer(buffer_id, cx);
18427 }
18428 }
18429 })
18430 .ok();
18431 });
18432 }
18433 }
18434
18435 fn fold_at_level(
18436 &mut self,
18437 fold_at: &FoldAtLevel,
18438 window: &mut Window,
18439 cx: &mut Context<Self>,
18440 ) {
18441 if !self.buffer.read(cx).is_singleton() {
18442 return;
18443 }
18444
18445 let fold_at_level = fold_at.0;
18446 let snapshot = self.buffer.read(cx).snapshot(cx);
18447 let mut to_fold = Vec::new();
18448 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18449
18450 let row_ranges_to_keep: Vec<Range<u32>> = self
18451 .selections
18452 .all::<Point>(&self.display_snapshot(cx))
18453 .into_iter()
18454 .map(|sel| sel.start.row..sel.end.row)
18455 .collect();
18456
18457 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18458 while start_row < end_row {
18459 match self
18460 .snapshot(window, cx)
18461 .crease_for_buffer_row(MultiBufferRow(start_row))
18462 {
18463 Some(crease) => {
18464 let nested_start_row = crease.range().start.row + 1;
18465 let nested_end_row = crease.range().end.row;
18466
18467 if current_level < fold_at_level {
18468 stack.push((nested_start_row, nested_end_row, current_level + 1));
18469 } else if current_level == fold_at_level {
18470 // Fold iff there is no selection completely contained within the fold region
18471 if !row_ranges_to_keep.iter().any(|selection| {
18472 selection.end >= nested_start_row
18473 && selection.start <= nested_end_row
18474 }) {
18475 to_fold.push(crease);
18476 }
18477 }
18478
18479 start_row = nested_end_row + 1;
18480 }
18481 None => start_row += 1,
18482 }
18483 }
18484 }
18485
18486 self.fold_creases(to_fold, true, window, cx);
18487 }
18488
18489 pub fn fold_at_level_1(
18490 &mut self,
18491 _: &actions::FoldAtLevel1,
18492 window: &mut Window,
18493 cx: &mut Context<Self>,
18494 ) {
18495 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18496 }
18497
18498 pub fn fold_at_level_2(
18499 &mut self,
18500 _: &actions::FoldAtLevel2,
18501 window: &mut Window,
18502 cx: &mut Context<Self>,
18503 ) {
18504 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18505 }
18506
18507 pub fn fold_at_level_3(
18508 &mut self,
18509 _: &actions::FoldAtLevel3,
18510 window: &mut Window,
18511 cx: &mut Context<Self>,
18512 ) {
18513 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18514 }
18515
18516 pub fn fold_at_level_4(
18517 &mut self,
18518 _: &actions::FoldAtLevel4,
18519 window: &mut Window,
18520 cx: &mut Context<Self>,
18521 ) {
18522 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18523 }
18524
18525 pub fn fold_at_level_5(
18526 &mut self,
18527 _: &actions::FoldAtLevel5,
18528 window: &mut Window,
18529 cx: &mut Context<Self>,
18530 ) {
18531 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18532 }
18533
18534 pub fn fold_at_level_6(
18535 &mut self,
18536 _: &actions::FoldAtLevel6,
18537 window: &mut Window,
18538 cx: &mut Context<Self>,
18539 ) {
18540 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18541 }
18542
18543 pub fn fold_at_level_7(
18544 &mut self,
18545 _: &actions::FoldAtLevel7,
18546 window: &mut Window,
18547 cx: &mut Context<Self>,
18548 ) {
18549 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18550 }
18551
18552 pub fn fold_at_level_8(
18553 &mut self,
18554 _: &actions::FoldAtLevel8,
18555 window: &mut Window,
18556 cx: &mut Context<Self>,
18557 ) {
18558 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18559 }
18560
18561 pub fn fold_at_level_9(
18562 &mut self,
18563 _: &actions::FoldAtLevel9,
18564 window: &mut Window,
18565 cx: &mut Context<Self>,
18566 ) {
18567 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18568 }
18569
18570 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18571 if self.buffer.read(cx).is_singleton() {
18572 let mut fold_ranges = Vec::new();
18573 let snapshot = self.buffer.read(cx).snapshot(cx);
18574
18575 for row in 0..snapshot.max_row().0 {
18576 if let Some(foldable_range) = self
18577 .snapshot(window, cx)
18578 .crease_for_buffer_row(MultiBufferRow(row))
18579 {
18580 fold_ranges.push(foldable_range);
18581 }
18582 }
18583
18584 self.fold_creases(fold_ranges, true, window, cx);
18585 } else {
18586 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18587 editor
18588 .update_in(cx, |editor, _, cx| {
18589 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18590 editor.fold_buffer(buffer_id, cx);
18591 }
18592 })
18593 .ok();
18594 });
18595 }
18596 }
18597
18598 pub fn fold_function_bodies(
18599 &mut self,
18600 _: &actions::FoldFunctionBodies,
18601 window: &mut Window,
18602 cx: &mut Context<Self>,
18603 ) {
18604 let snapshot = self.buffer.read(cx).snapshot(cx);
18605
18606 let ranges = snapshot
18607 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18608 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18609 .collect::<Vec<_>>();
18610
18611 let creases = ranges
18612 .into_iter()
18613 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18614 .collect();
18615
18616 self.fold_creases(creases, true, window, cx);
18617 }
18618
18619 pub fn fold_recursive(
18620 &mut self,
18621 _: &actions::FoldRecursive,
18622 window: &mut Window,
18623 cx: &mut Context<Self>,
18624 ) {
18625 let mut to_fold = Vec::new();
18626 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18627 let selections = self.selections.all_adjusted(&display_map);
18628
18629 for selection in selections {
18630 let range = selection.range().sorted();
18631 let buffer_start_row = range.start.row;
18632
18633 if range.start.row != range.end.row {
18634 let mut found = false;
18635 for row in range.start.row..=range.end.row {
18636 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18637 found = true;
18638 to_fold.push(crease);
18639 }
18640 }
18641 if found {
18642 continue;
18643 }
18644 }
18645
18646 for row in (0..=range.start.row).rev() {
18647 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18648 if crease.range().end.row >= buffer_start_row {
18649 to_fold.push(crease);
18650 } else {
18651 break;
18652 }
18653 }
18654 }
18655 }
18656
18657 self.fold_creases(to_fold, true, window, cx);
18658 }
18659
18660 pub fn fold_at(
18661 &mut self,
18662 buffer_row: MultiBufferRow,
18663 window: &mut Window,
18664 cx: &mut Context<Self>,
18665 ) {
18666 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18667
18668 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18669 let autoscroll = self
18670 .selections
18671 .all::<Point>(&display_map)
18672 .iter()
18673 .any(|selection| crease.range().overlaps(&selection.range()));
18674
18675 self.fold_creases(vec![crease], autoscroll, window, cx);
18676 }
18677 }
18678
18679 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18680 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18681 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18682 let buffer = display_map.buffer_snapshot();
18683 let selections = self.selections.all::<Point>(&display_map);
18684 let ranges = selections
18685 .iter()
18686 .map(|s| {
18687 let range = s.display_range(&display_map).sorted();
18688 let mut start = range.start.to_point(&display_map);
18689 let mut end = range.end.to_point(&display_map);
18690 start.column = 0;
18691 end.column = buffer.line_len(MultiBufferRow(end.row));
18692 start..end
18693 })
18694 .collect::<Vec<_>>();
18695
18696 self.unfold_ranges(&ranges, true, true, cx);
18697 } else {
18698 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18699 let buffer_ids = self
18700 .selections
18701 .disjoint_anchor_ranges()
18702 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18703 .collect::<HashSet<_>>();
18704 for buffer_id in buffer_ids {
18705 self.unfold_buffer(buffer_id, cx);
18706 }
18707 }
18708 }
18709
18710 pub fn unfold_recursive(
18711 &mut self,
18712 _: &UnfoldRecursive,
18713 _window: &mut Window,
18714 cx: &mut Context<Self>,
18715 ) {
18716 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18717 let selections = self.selections.all::<Point>(&display_map);
18718 let ranges = selections
18719 .iter()
18720 .map(|s| {
18721 let mut range = s.display_range(&display_map).sorted();
18722 *range.start.column_mut() = 0;
18723 *range.end.column_mut() = display_map.line_len(range.end.row());
18724 let start = range.start.to_point(&display_map);
18725 let end = range.end.to_point(&display_map);
18726 start..end
18727 })
18728 .collect::<Vec<_>>();
18729
18730 self.unfold_ranges(&ranges, true, true, cx);
18731 }
18732
18733 pub fn unfold_at(
18734 &mut self,
18735 buffer_row: MultiBufferRow,
18736 _window: &mut Window,
18737 cx: &mut Context<Self>,
18738 ) {
18739 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18740
18741 let intersection_range = Point::new(buffer_row.0, 0)
18742 ..Point::new(
18743 buffer_row.0,
18744 display_map.buffer_snapshot().line_len(buffer_row),
18745 );
18746
18747 let autoscroll = self
18748 .selections
18749 .all::<Point>(&display_map)
18750 .iter()
18751 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18752
18753 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18754 }
18755
18756 pub fn unfold_all(
18757 &mut self,
18758 _: &actions::UnfoldAll,
18759 _window: &mut Window,
18760 cx: &mut Context<Self>,
18761 ) {
18762 if self.buffer.read(cx).is_singleton() {
18763 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18764 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18765 } else {
18766 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18767 editor
18768 .update(cx, |editor, cx| {
18769 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18770 editor.unfold_buffer(buffer_id, cx);
18771 }
18772 })
18773 .ok();
18774 });
18775 }
18776 }
18777
18778 pub fn fold_selected_ranges(
18779 &mut self,
18780 _: &FoldSelectedRanges,
18781 window: &mut Window,
18782 cx: &mut Context<Self>,
18783 ) {
18784 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18785 let selections = self.selections.all_adjusted(&display_map);
18786 let ranges = selections
18787 .into_iter()
18788 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18789 .collect::<Vec<_>>();
18790 self.fold_creases(ranges, true, window, cx);
18791 }
18792
18793 pub fn fold_ranges<T: ToOffset + Clone>(
18794 &mut self,
18795 ranges: Vec<Range<T>>,
18796 auto_scroll: bool,
18797 window: &mut Window,
18798 cx: &mut Context<Self>,
18799 ) {
18800 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18801 let ranges = ranges
18802 .into_iter()
18803 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18804 .collect::<Vec<_>>();
18805 self.fold_creases(ranges, auto_scroll, window, cx);
18806 }
18807
18808 pub fn fold_creases<T: ToOffset + Clone>(
18809 &mut self,
18810 creases: Vec<Crease<T>>,
18811 auto_scroll: bool,
18812 _window: &mut Window,
18813 cx: &mut Context<Self>,
18814 ) {
18815 if creases.is_empty() {
18816 return;
18817 }
18818
18819 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18820
18821 if auto_scroll {
18822 self.request_autoscroll(Autoscroll::fit(), cx);
18823 }
18824
18825 cx.notify();
18826
18827 self.scrollbar_marker_state.dirty = true;
18828 self.folds_did_change(cx);
18829 }
18830
18831 /// Removes any folds whose ranges intersect any of the given ranges.
18832 pub fn unfold_ranges<T: ToOffset + Clone>(
18833 &mut self,
18834 ranges: &[Range<T>],
18835 inclusive: bool,
18836 auto_scroll: bool,
18837 cx: &mut Context<Self>,
18838 ) {
18839 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18840 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18841 });
18842 self.folds_did_change(cx);
18843 }
18844
18845 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18846 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18847 return;
18848 }
18849 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18850 self.display_map.update(cx, |display_map, cx| {
18851 display_map.fold_buffers([buffer_id], cx)
18852 });
18853 cx.emit(EditorEvent::BufferFoldToggled {
18854 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18855 folded: true,
18856 });
18857 cx.notify();
18858 }
18859
18860 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18861 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18862 return;
18863 }
18864 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18865 self.display_map.update(cx, |display_map, cx| {
18866 display_map.unfold_buffers([buffer_id], cx);
18867 });
18868 cx.emit(EditorEvent::BufferFoldToggled {
18869 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18870 folded: false,
18871 });
18872 cx.notify();
18873 }
18874
18875 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18876 self.display_map.read(cx).is_buffer_folded(buffer)
18877 }
18878
18879 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18880 self.display_map.read(cx).folded_buffers()
18881 }
18882
18883 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18884 self.display_map.update(cx, |display_map, cx| {
18885 display_map.disable_header_for_buffer(buffer_id, cx);
18886 });
18887 cx.notify();
18888 }
18889
18890 /// Removes any folds with the given ranges.
18891 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18892 &mut self,
18893 ranges: &[Range<T>],
18894 type_id: TypeId,
18895 auto_scroll: bool,
18896 cx: &mut Context<Self>,
18897 ) {
18898 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18899 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18900 });
18901 self.folds_did_change(cx);
18902 }
18903
18904 fn remove_folds_with<T: ToOffset + Clone>(
18905 &mut self,
18906 ranges: &[Range<T>],
18907 auto_scroll: bool,
18908 cx: &mut Context<Self>,
18909 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18910 ) {
18911 if ranges.is_empty() {
18912 return;
18913 }
18914
18915 let mut buffers_affected = HashSet::default();
18916 let multi_buffer = self.buffer().read(cx);
18917 for range in ranges {
18918 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18919 buffers_affected.insert(buffer.read(cx).remote_id());
18920 };
18921 }
18922
18923 self.display_map.update(cx, update);
18924
18925 if auto_scroll {
18926 self.request_autoscroll(Autoscroll::fit(), cx);
18927 }
18928
18929 cx.notify();
18930 self.scrollbar_marker_state.dirty = true;
18931 self.active_indent_guides_state.dirty = true;
18932 }
18933
18934 pub fn update_renderer_widths(
18935 &mut self,
18936 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18937 cx: &mut Context<Self>,
18938 ) -> bool {
18939 self.display_map
18940 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18941 }
18942
18943 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18944 self.display_map.read(cx).fold_placeholder.clone()
18945 }
18946
18947 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18948 self.buffer.update(cx, |buffer, cx| {
18949 buffer.set_all_diff_hunks_expanded(cx);
18950 });
18951 }
18952
18953 pub fn expand_all_diff_hunks(
18954 &mut self,
18955 _: &ExpandAllDiffHunks,
18956 _window: &mut Window,
18957 cx: &mut Context<Self>,
18958 ) {
18959 self.buffer.update(cx, |buffer, cx| {
18960 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18961 });
18962 }
18963
18964 pub fn collapse_all_diff_hunks(
18965 &mut self,
18966 _: &CollapseAllDiffHunks,
18967 _window: &mut Window,
18968 cx: &mut Context<Self>,
18969 ) {
18970 self.buffer.update(cx, |buffer, cx| {
18971 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18972 });
18973 }
18974
18975 pub fn toggle_selected_diff_hunks(
18976 &mut self,
18977 _: &ToggleSelectedDiffHunks,
18978 _window: &mut Window,
18979 cx: &mut Context<Self>,
18980 ) {
18981 let ranges: Vec<_> = self
18982 .selections
18983 .disjoint_anchors()
18984 .iter()
18985 .map(|s| s.range())
18986 .collect();
18987 self.toggle_diff_hunks_in_ranges(ranges, cx);
18988 }
18989
18990 pub fn diff_hunks_in_ranges<'a>(
18991 &'a self,
18992 ranges: &'a [Range<Anchor>],
18993 buffer: &'a MultiBufferSnapshot,
18994 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18995 ranges.iter().flat_map(move |range| {
18996 let end_excerpt_id = range.end.excerpt_id;
18997 let range = range.to_point(buffer);
18998 let mut peek_end = range.end;
18999 if range.end.row < buffer.max_row().0 {
19000 peek_end = Point::new(range.end.row + 1, 0);
19001 }
19002 buffer
19003 .diff_hunks_in_range(range.start..peek_end)
19004 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19005 })
19006 }
19007
19008 pub fn has_stageable_diff_hunks_in_ranges(
19009 &self,
19010 ranges: &[Range<Anchor>],
19011 snapshot: &MultiBufferSnapshot,
19012 ) -> bool {
19013 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19014 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19015 }
19016
19017 pub fn toggle_staged_selected_diff_hunks(
19018 &mut self,
19019 _: &::git::ToggleStaged,
19020 _: &mut Window,
19021 cx: &mut Context<Self>,
19022 ) {
19023 let snapshot = self.buffer.read(cx).snapshot(cx);
19024 let ranges: Vec<_> = self
19025 .selections
19026 .disjoint_anchors()
19027 .iter()
19028 .map(|s| s.range())
19029 .collect();
19030 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19031 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19032 }
19033
19034 pub fn set_render_diff_hunk_controls(
19035 &mut self,
19036 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19037 cx: &mut Context<Self>,
19038 ) {
19039 self.render_diff_hunk_controls = render_diff_hunk_controls;
19040 cx.notify();
19041 }
19042
19043 pub fn stage_and_next(
19044 &mut self,
19045 _: &::git::StageAndNext,
19046 window: &mut Window,
19047 cx: &mut Context<Self>,
19048 ) {
19049 self.do_stage_or_unstage_and_next(true, window, cx);
19050 }
19051
19052 pub fn unstage_and_next(
19053 &mut self,
19054 _: &::git::UnstageAndNext,
19055 window: &mut Window,
19056 cx: &mut Context<Self>,
19057 ) {
19058 self.do_stage_or_unstage_and_next(false, window, cx);
19059 }
19060
19061 pub fn stage_or_unstage_diff_hunks(
19062 &mut self,
19063 stage: bool,
19064 ranges: Vec<Range<Anchor>>,
19065 cx: &mut Context<Self>,
19066 ) {
19067 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19068 cx.spawn(async move |this, cx| {
19069 task.await?;
19070 this.update(cx, |this, cx| {
19071 let snapshot = this.buffer.read(cx).snapshot(cx);
19072 let chunk_by = this
19073 .diff_hunks_in_ranges(&ranges, &snapshot)
19074 .chunk_by(|hunk| hunk.buffer_id);
19075 for (buffer_id, hunks) in &chunk_by {
19076 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19077 }
19078 })
19079 })
19080 .detach_and_log_err(cx);
19081 }
19082
19083 fn save_buffers_for_ranges_if_needed(
19084 &mut self,
19085 ranges: &[Range<Anchor>],
19086 cx: &mut Context<Editor>,
19087 ) -> Task<Result<()>> {
19088 let multibuffer = self.buffer.read(cx);
19089 let snapshot = multibuffer.read(cx);
19090 let buffer_ids: HashSet<_> = ranges
19091 .iter()
19092 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19093 .collect();
19094 drop(snapshot);
19095
19096 let mut buffers = HashSet::default();
19097 for buffer_id in buffer_ids {
19098 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19099 let buffer = buffer_entity.read(cx);
19100 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19101 {
19102 buffers.insert(buffer_entity);
19103 }
19104 }
19105 }
19106
19107 if let Some(project) = &self.project {
19108 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19109 } else {
19110 Task::ready(Ok(()))
19111 }
19112 }
19113
19114 fn do_stage_or_unstage_and_next(
19115 &mut self,
19116 stage: bool,
19117 window: &mut Window,
19118 cx: &mut Context<Self>,
19119 ) {
19120 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19121
19122 if ranges.iter().any(|range| range.start != range.end) {
19123 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19124 return;
19125 }
19126
19127 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19128 let snapshot = self.snapshot(window, cx);
19129 let position = self
19130 .selections
19131 .newest::<Point>(&snapshot.display_snapshot)
19132 .head();
19133 let mut row = snapshot
19134 .buffer_snapshot()
19135 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19136 .find(|hunk| hunk.row_range.start.0 > position.row)
19137 .map(|hunk| hunk.row_range.start);
19138
19139 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19140 // Outside of the project diff editor, wrap around to the beginning.
19141 if !all_diff_hunks_expanded {
19142 row = row.or_else(|| {
19143 snapshot
19144 .buffer_snapshot()
19145 .diff_hunks_in_range(Point::zero()..position)
19146 .find(|hunk| hunk.row_range.end.0 < position.row)
19147 .map(|hunk| hunk.row_range.start)
19148 });
19149 }
19150
19151 if let Some(row) = row {
19152 let destination = Point::new(row.0, 0);
19153 let autoscroll = Autoscroll::center();
19154
19155 self.unfold_ranges(&[destination..destination], false, false, cx);
19156 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19157 s.select_ranges([destination..destination]);
19158 });
19159 }
19160 }
19161
19162 fn do_stage_or_unstage(
19163 &self,
19164 stage: bool,
19165 buffer_id: BufferId,
19166 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19167 cx: &mut App,
19168 ) -> Option<()> {
19169 let project = self.project()?;
19170 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19171 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19172 let buffer_snapshot = buffer.read(cx).snapshot();
19173 let file_exists = buffer_snapshot
19174 .file()
19175 .is_some_and(|file| file.disk_state().exists());
19176 diff.update(cx, |diff, cx| {
19177 diff.stage_or_unstage_hunks(
19178 stage,
19179 &hunks
19180 .map(|hunk| buffer_diff::DiffHunk {
19181 buffer_range: hunk.buffer_range,
19182 diff_base_byte_range: hunk.diff_base_byte_range,
19183 secondary_status: hunk.secondary_status,
19184 range: Point::zero()..Point::zero(), // unused
19185 })
19186 .collect::<Vec<_>>(),
19187 &buffer_snapshot,
19188 file_exists,
19189 cx,
19190 )
19191 });
19192 None
19193 }
19194
19195 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19196 let ranges: Vec<_> = self
19197 .selections
19198 .disjoint_anchors()
19199 .iter()
19200 .map(|s| s.range())
19201 .collect();
19202 self.buffer
19203 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19204 }
19205
19206 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19207 self.buffer.update(cx, |buffer, cx| {
19208 let ranges = vec![Anchor::min()..Anchor::max()];
19209 if !buffer.all_diff_hunks_expanded()
19210 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19211 {
19212 buffer.collapse_diff_hunks(ranges, cx);
19213 true
19214 } else {
19215 false
19216 }
19217 })
19218 }
19219
19220 fn toggle_diff_hunks_in_ranges(
19221 &mut self,
19222 ranges: Vec<Range<Anchor>>,
19223 cx: &mut Context<Editor>,
19224 ) {
19225 self.buffer.update(cx, |buffer, cx| {
19226 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19227 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19228 })
19229 }
19230
19231 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19232 self.buffer.update(cx, |buffer, cx| {
19233 let snapshot = buffer.snapshot(cx);
19234 let excerpt_id = range.end.excerpt_id;
19235 let point_range = range.to_point(&snapshot);
19236 let expand = !buffer.single_hunk_is_expanded(range, cx);
19237 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19238 })
19239 }
19240
19241 pub(crate) fn apply_all_diff_hunks(
19242 &mut self,
19243 _: &ApplyAllDiffHunks,
19244 window: &mut Window,
19245 cx: &mut Context<Self>,
19246 ) {
19247 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19248
19249 let buffers = self.buffer.read(cx).all_buffers();
19250 for branch_buffer in buffers {
19251 branch_buffer.update(cx, |branch_buffer, cx| {
19252 branch_buffer.merge_into_base(Vec::new(), cx);
19253 });
19254 }
19255
19256 if let Some(project) = self.project.clone() {
19257 self.save(
19258 SaveOptions {
19259 format: true,
19260 autosave: false,
19261 },
19262 project,
19263 window,
19264 cx,
19265 )
19266 .detach_and_log_err(cx);
19267 }
19268 }
19269
19270 pub(crate) fn apply_selected_diff_hunks(
19271 &mut self,
19272 _: &ApplyDiffHunk,
19273 window: &mut Window,
19274 cx: &mut Context<Self>,
19275 ) {
19276 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19277 let snapshot = self.snapshot(window, cx);
19278 let hunks = snapshot.hunks_for_ranges(
19279 self.selections
19280 .all(&snapshot.display_snapshot)
19281 .into_iter()
19282 .map(|selection| selection.range()),
19283 );
19284 let mut ranges_by_buffer = HashMap::default();
19285 self.transact(window, cx, |editor, _window, cx| {
19286 for hunk in hunks {
19287 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19288 ranges_by_buffer
19289 .entry(buffer.clone())
19290 .or_insert_with(Vec::new)
19291 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19292 }
19293 }
19294
19295 for (buffer, ranges) in ranges_by_buffer {
19296 buffer.update(cx, |buffer, cx| {
19297 buffer.merge_into_base(ranges, cx);
19298 });
19299 }
19300 });
19301
19302 if let Some(project) = self.project.clone() {
19303 self.save(
19304 SaveOptions {
19305 format: true,
19306 autosave: false,
19307 },
19308 project,
19309 window,
19310 cx,
19311 )
19312 .detach_and_log_err(cx);
19313 }
19314 }
19315
19316 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19317 if hovered != self.gutter_hovered {
19318 self.gutter_hovered = hovered;
19319 cx.notify();
19320 }
19321 }
19322
19323 pub fn insert_blocks(
19324 &mut self,
19325 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19326 autoscroll: Option<Autoscroll>,
19327 cx: &mut Context<Self>,
19328 ) -> Vec<CustomBlockId> {
19329 let blocks = self
19330 .display_map
19331 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19332 if let Some(autoscroll) = autoscroll {
19333 self.request_autoscroll(autoscroll, cx);
19334 }
19335 cx.notify();
19336 blocks
19337 }
19338
19339 pub fn resize_blocks(
19340 &mut self,
19341 heights: HashMap<CustomBlockId, u32>,
19342 autoscroll: Option<Autoscroll>,
19343 cx: &mut Context<Self>,
19344 ) {
19345 self.display_map
19346 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19347 if let Some(autoscroll) = autoscroll {
19348 self.request_autoscroll(autoscroll, cx);
19349 }
19350 cx.notify();
19351 }
19352
19353 pub fn replace_blocks(
19354 &mut self,
19355 renderers: HashMap<CustomBlockId, RenderBlock>,
19356 autoscroll: Option<Autoscroll>,
19357 cx: &mut Context<Self>,
19358 ) {
19359 self.display_map
19360 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19361 if let Some(autoscroll) = autoscroll {
19362 self.request_autoscroll(autoscroll, cx);
19363 }
19364 cx.notify();
19365 }
19366
19367 pub fn remove_blocks(
19368 &mut self,
19369 block_ids: HashSet<CustomBlockId>,
19370 autoscroll: Option<Autoscroll>,
19371 cx: &mut Context<Self>,
19372 ) {
19373 self.display_map.update(cx, |display_map, cx| {
19374 display_map.remove_blocks(block_ids, cx)
19375 });
19376 if let Some(autoscroll) = autoscroll {
19377 self.request_autoscroll(autoscroll, cx);
19378 }
19379 cx.notify();
19380 }
19381
19382 pub fn row_for_block(
19383 &self,
19384 block_id: CustomBlockId,
19385 cx: &mut Context<Self>,
19386 ) -> Option<DisplayRow> {
19387 self.display_map
19388 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19389 }
19390
19391 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19392 self.focused_block = Some(focused_block);
19393 }
19394
19395 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19396 self.focused_block.take()
19397 }
19398
19399 pub fn insert_creases(
19400 &mut self,
19401 creases: impl IntoIterator<Item = Crease<Anchor>>,
19402 cx: &mut Context<Self>,
19403 ) -> Vec<CreaseId> {
19404 self.display_map
19405 .update(cx, |map, cx| map.insert_creases(creases, cx))
19406 }
19407
19408 pub fn remove_creases(
19409 &mut self,
19410 ids: impl IntoIterator<Item = CreaseId>,
19411 cx: &mut Context<Self>,
19412 ) -> Vec<(CreaseId, Range<Anchor>)> {
19413 self.display_map
19414 .update(cx, |map, cx| map.remove_creases(ids, cx))
19415 }
19416
19417 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19418 self.display_map
19419 .update(cx, |map, cx| map.snapshot(cx))
19420 .longest_row()
19421 }
19422
19423 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19424 self.display_map
19425 .update(cx, |map, cx| map.snapshot(cx))
19426 .max_point()
19427 }
19428
19429 pub fn text(&self, cx: &App) -> String {
19430 self.buffer.read(cx).read(cx).text()
19431 }
19432
19433 pub fn is_empty(&self, cx: &App) -> bool {
19434 self.buffer.read(cx).read(cx).is_empty()
19435 }
19436
19437 pub fn text_option(&self, cx: &App) -> Option<String> {
19438 let text = self.text(cx);
19439 let text = text.trim();
19440
19441 if text.is_empty() {
19442 return None;
19443 }
19444
19445 Some(text.to_string())
19446 }
19447
19448 pub fn set_text(
19449 &mut self,
19450 text: impl Into<Arc<str>>,
19451 window: &mut Window,
19452 cx: &mut Context<Self>,
19453 ) {
19454 self.transact(window, cx, |this, _, cx| {
19455 this.buffer
19456 .read(cx)
19457 .as_singleton()
19458 .expect("you can only call set_text on editors for singleton buffers")
19459 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19460 });
19461 }
19462
19463 pub fn display_text(&self, cx: &mut App) -> String {
19464 self.display_map
19465 .update(cx, |map, cx| map.snapshot(cx))
19466 .text()
19467 }
19468
19469 fn create_minimap(
19470 &self,
19471 minimap_settings: MinimapSettings,
19472 window: &mut Window,
19473 cx: &mut Context<Self>,
19474 ) -> Option<Entity<Self>> {
19475 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19476 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19477 }
19478
19479 fn initialize_new_minimap(
19480 &self,
19481 minimap_settings: MinimapSettings,
19482 window: &mut Window,
19483 cx: &mut Context<Self>,
19484 ) -> Entity<Self> {
19485 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19486
19487 let mut minimap = Editor::new_internal(
19488 EditorMode::Minimap {
19489 parent: cx.weak_entity(),
19490 },
19491 self.buffer.clone(),
19492 None,
19493 Some(self.display_map.clone()),
19494 window,
19495 cx,
19496 );
19497 minimap.scroll_manager.clone_state(&self.scroll_manager);
19498 minimap.set_text_style_refinement(TextStyleRefinement {
19499 font_size: Some(MINIMAP_FONT_SIZE),
19500 font_weight: Some(MINIMAP_FONT_WEIGHT),
19501 ..Default::default()
19502 });
19503 minimap.update_minimap_configuration(minimap_settings, cx);
19504 cx.new(|_| minimap)
19505 }
19506
19507 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19508 let current_line_highlight = minimap_settings
19509 .current_line_highlight
19510 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19511 self.set_current_line_highlight(Some(current_line_highlight));
19512 }
19513
19514 pub fn minimap(&self) -> Option<&Entity<Self>> {
19515 self.minimap
19516 .as_ref()
19517 .filter(|_| self.minimap_visibility.visible())
19518 }
19519
19520 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19521 let mut wrap_guides = smallvec![];
19522
19523 if self.show_wrap_guides == Some(false) {
19524 return wrap_guides;
19525 }
19526
19527 let settings = self.buffer.read(cx).language_settings(cx);
19528 if settings.show_wrap_guides {
19529 match self.soft_wrap_mode(cx) {
19530 SoftWrap::Column(soft_wrap) => {
19531 wrap_guides.push((soft_wrap as usize, true));
19532 }
19533 SoftWrap::Bounded(soft_wrap) => {
19534 wrap_guides.push((soft_wrap as usize, true));
19535 }
19536 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19537 }
19538 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19539 }
19540
19541 wrap_guides
19542 }
19543
19544 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19545 let settings = self.buffer.read(cx).language_settings(cx);
19546 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19547 match mode {
19548 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19549 SoftWrap::None
19550 }
19551 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19552 language_settings::SoftWrap::PreferredLineLength => {
19553 SoftWrap::Column(settings.preferred_line_length)
19554 }
19555 language_settings::SoftWrap::Bounded => {
19556 SoftWrap::Bounded(settings.preferred_line_length)
19557 }
19558 }
19559 }
19560
19561 pub fn set_soft_wrap_mode(
19562 &mut self,
19563 mode: language_settings::SoftWrap,
19564
19565 cx: &mut Context<Self>,
19566 ) {
19567 self.soft_wrap_mode_override = Some(mode);
19568 cx.notify();
19569 }
19570
19571 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19572 self.hard_wrap = hard_wrap;
19573 cx.notify();
19574 }
19575
19576 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19577 self.text_style_refinement = Some(style);
19578 }
19579
19580 /// called by the Element so we know what style we were most recently rendered with.
19581 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19582 // We intentionally do not inform the display map about the minimap style
19583 // so that wrapping is not recalculated and stays consistent for the editor
19584 // and its linked minimap.
19585 if !self.mode.is_minimap() {
19586 let font = style.text.font();
19587 let font_size = style.text.font_size.to_pixels(window.rem_size());
19588 let display_map = self
19589 .placeholder_display_map
19590 .as_ref()
19591 .filter(|_| self.is_empty(cx))
19592 .unwrap_or(&self.display_map);
19593
19594 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19595 }
19596 self.style = Some(style);
19597 }
19598
19599 pub fn style(&self) -> Option<&EditorStyle> {
19600 self.style.as_ref()
19601 }
19602
19603 // Called by the element. This method is not designed to be called outside of the editor
19604 // element's layout code because it does not notify when rewrapping is computed synchronously.
19605 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19606 if self.is_empty(cx) {
19607 self.placeholder_display_map
19608 .as_ref()
19609 .map_or(false, |display_map| {
19610 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19611 })
19612 } else {
19613 self.display_map
19614 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19615 }
19616 }
19617
19618 pub fn set_soft_wrap(&mut self) {
19619 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19620 }
19621
19622 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19623 if self.soft_wrap_mode_override.is_some() {
19624 self.soft_wrap_mode_override.take();
19625 } else {
19626 let soft_wrap = match self.soft_wrap_mode(cx) {
19627 SoftWrap::GitDiff => return,
19628 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19629 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19630 language_settings::SoftWrap::None
19631 }
19632 };
19633 self.soft_wrap_mode_override = Some(soft_wrap);
19634 }
19635 cx.notify();
19636 }
19637
19638 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19639 let Some(workspace) = self.workspace() else {
19640 return;
19641 };
19642 let fs = workspace.read(cx).app_state().fs.clone();
19643 let current_show = TabBarSettings::get_global(cx).show;
19644 update_settings_file(fs, cx, move |setting, _| {
19645 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19646 });
19647 }
19648
19649 pub fn toggle_indent_guides(
19650 &mut self,
19651 _: &ToggleIndentGuides,
19652 _: &mut Window,
19653 cx: &mut Context<Self>,
19654 ) {
19655 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19656 self.buffer
19657 .read(cx)
19658 .language_settings(cx)
19659 .indent_guides
19660 .enabled
19661 });
19662 self.show_indent_guides = Some(!currently_enabled);
19663 cx.notify();
19664 }
19665
19666 fn should_show_indent_guides(&self) -> Option<bool> {
19667 self.show_indent_guides
19668 }
19669
19670 pub fn toggle_line_numbers(
19671 &mut self,
19672 _: &ToggleLineNumbers,
19673 _: &mut Window,
19674 cx: &mut Context<Self>,
19675 ) {
19676 let mut editor_settings = EditorSettings::get_global(cx).clone();
19677 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19678 EditorSettings::override_global(editor_settings, cx);
19679 }
19680
19681 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19682 if let Some(show_line_numbers) = self.show_line_numbers {
19683 return show_line_numbers;
19684 }
19685 EditorSettings::get_global(cx).gutter.line_numbers
19686 }
19687
19688 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19689 match (
19690 self.use_relative_line_numbers,
19691 EditorSettings::get_global(cx).relative_line_numbers,
19692 ) {
19693 (None, setting) => setting,
19694 (Some(false), _) => RelativeLineNumbers::Disabled,
19695 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19696 (Some(true), _) => RelativeLineNumbers::Enabled,
19697 }
19698 }
19699
19700 pub fn toggle_relative_line_numbers(
19701 &mut self,
19702 _: &ToggleRelativeLineNumbers,
19703 _: &mut Window,
19704 cx: &mut Context<Self>,
19705 ) {
19706 let is_relative = self.relative_line_numbers(cx);
19707 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19708 }
19709
19710 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19711 self.use_relative_line_numbers = is_relative;
19712 cx.notify();
19713 }
19714
19715 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19716 self.show_gutter = show_gutter;
19717 cx.notify();
19718 }
19719
19720 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19721 self.show_scrollbars = ScrollbarAxes {
19722 horizontal: show,
19723 vertical: show,
19724 };
19725 cx.notify();
19726 }
19727
19728 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19729 self.show_scrollbars.vertical = show;
19730 cx.notify();
19731 }
19732
19733 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19734 self.show_scrollbars.horizontal = show;
19735 cx.notify();
19736 }
19737
19738 pub fn set_minimap_visibility(
19739 &mut self,
19740 minimap_visibility: MinimapVisibility,
19741 window: &mut Window,
19742 cx: &mut Context<Self>,
19743 ) {
19744 if self.minimap_visibility != minimap_visibility {
19745 if minimap_visibility.visible() && self.minimap.is_none() {
19746 let minimap_settings = EditorSettings::get_global(cx).minimap;
19747 self.minimap =
19748 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19749 }
19750 self.minimap_visibility = minimap_visibility;
19751 cx.notify();
19752 }
19753 }
19754
19755 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19756 self.set_show_scrollbars(false, cx);
19757 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19758 }
19759
19760 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19761 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19762 }
19763
19764 /// Normally the text in full mode and auto height editors is padded on the
19765 /// left side by roughly half a character width for improved hit testing.
19766 ///
19767 /// Use this method to disable this for cases where this is not wanted (e.g.
19768 /// if you want to align the editor text with some other text above or below)
19769 /// or if you want to add this padding to single-line editors.
19770 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19771 self.offset_content = offset_content;
19772 cx.notify();
19773 }
19774
19775 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19776 self.show_line_numbers = Some(show_line_numbers);
19777 cx.notify();
19778 }
19779
19780 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19781 self.disable_expand_excerpt_buttons = true;
19782 cx.notify();
19783 }
19784
19785 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19786 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19787 cx.notify();
19788 }
19789
19790 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19791 self.show_code_actions = Some(show_code_actions);
19792 cx.notify();
19793 }
19794
19795 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19796 self.show_runnables = Some(show_runnables);
19797 cx.notify();
19798 }
19799
19800 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19801 self.show_breakpoints = Some(show_breakpoints);
19802 cx.notify();
19803 }
19804
19805 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19806 if self.display_map.read(cx).masked != masked {
19807 self.display_map.update(cx, |map, _| map.masked = masked);
19808 }
19809 cx.notify()
19810 }
19811
19812 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19813 self.show_wrap_guides = Some(show_wrap_guides);
19814 cx.notify();
19815 }
19816
19817 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19818 self.show_indent_guides = Some(show_indent_guides);
19819 cx.notify();
19820 }
19821
19822 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19823 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19824 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19825 && let Some(dir) = file.abs_path(cx).parent()
19826 {
19827 return Some(dir.to_owned());
19828 }
19829 }
19830
19831 None
19832 }
19833
19834 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19835 self.active_excerpt(cx)?
19836 .1
19837 .read(cx)
19838 .file()
19839 .and_then(|f| f.as_local())
19840 }
19841
19842 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19843 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19844 let buffer = buffer.read(cx);
19845 if let Some(project_path) = buffer.project_path(cx) {
19846 let project = self.project()?.read(cx);
19847 project.absolute_path(&project_path, cx)
19848 } else {
19849 buffer
19850 .file()
19851 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19852 }
19853 })
19854 }
19855
19856 pub fn reveal_in_finder(
19857 &mut self,
19858 _: &RevealInFileManager,
19859 _window: &mut Window,
19860 cx: &mut Context<Self>,
19861 ) {
19862 if let Some(target) = self.target_file(cx) {
19863 cx.reveal_path(&target.abs_path(cx));
19864 }
19865 }
19866
19867 pub fn copy_path(
19868 &mut self,
19869 _: &zed_actions::workspace::CopyPath,
19870 _window: &mut Window,
19871 cx: &mut Context<Self>,
19872 ) {
19873 if let Some(path) = self.target_file_abs_path(cx)
19874 && let Some(path) = path.to_str()
19875 {
19876 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19877 } else {
19878 cx.propagate();
19879 }
19880 }
19881
19882 pub fn copy_relative_path(
19883 &mut self,
19884 _: &zed_actions::workspace::CopyRelativePath,
19885 _window: &mut Window,
19886 cx: &mut Context<Self>,
19887 ) {
19888 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19889 let project = self.project()?.read(cx);
19890 let path = buffer.read(cx).file()?.path();
19891 let path = path.display(project.path_style(cx));
19892 Some(path)
19893 }) {
19894 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19895 } else {
19896 cx.propagate();
19897 }
19898 }
19899
19900 /// Returns the project path for the editor's buffer, if any buffer is
19901 /// opened in the editor.
19902 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19903 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19904 buffer.read(cx).project_path(cx)
19905 } else {
19906 None
19907 }
19908 }
19909
19910 // Returns true if the editor handled a go-to-line request
19911 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19912 maybe!({
19913 let breakpoint_store = self.breakpoint_store.as_ref()?;
19914
19915 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19916 else {
19917 self.clear_row_highlights::<ActiveDebugLine>();
19918 return None;
19919 };
19920
19921 let position = active_stack_frame.position;
19922 let buffer_id = position.buffer_id?;
19923 let snapshot = self
19924 .project
19925 .as_ref()?
19926 .read(cx)
19927 .buffer_for_id(buffer_id, cx)?
19928 .read(cx)
19929 .snapshot();
19930
19931 let mut handled = false;
19932 for (id, ExcerptRange { context, .. }) in
19933 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19934 {
19935 if context.start.cmp(&position, &snapshot).is_ge()
19936 || context.end.cmp(&position, &snapshot).is_lt()
19937 {
19938 continue;
19939 }
19940 let snapshot = self.buffer.read(cx).snapshot(cx);
19941 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19942
19943 handled = true;
19944 self.clear_row_highlights::<ActiveDebugLine>();
19945
19946 self.go_to_line::<ActiveDebugLine>(
19947 multibuffer_anchor,
19948 Some(cx.theme().colors().editor_debugger_active_line_background),
19949 window,
19950 cx,
19951 );
19952
19953 cx.notify();
19954 }
19955
19956 handled.then_some(())
19957 })
19958 .is_some()
19959 }
19960
19961 pub fn copy_file_name_without_extension(
19962 &mut self,
19963 _: &CopyFileNameWithoutExtension,
19964 _: &mut Window,
19965 cx: &mut Context<Self>,
19966 ) {
19967 if let Some(file) = self.target_file(cx)
19968 && let Some(file_stem) = file.path().file_stem()
19969 {
19970 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19971 }
19972 }
19973
19974 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19975 if let Some(file) = self.target_file(cx)
19976 && let Some(name) = file.path().file_name()
19977 {
19978 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19979 }
19980 }
19981
19982 pub fn toggle_git_blame(
19983 &mut self,
19984 _: &::git::Blame,
19985 window: &mut Window,
19986 cx: &mut Context<Self>,
19987 ) {
19988 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19989
19990 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19991 self.start_git_blame(true, window, cx);
19992 }
19993
19994 cx.notify();
19995 }
19996
19997 pub fn toggle_git_blame_inline(
19998 &mut self,
19999 _: &ToggleGitBlameInline,
20000 window: &mut Window,
20001 cx: &mut Context<Self>,
20002 ) {
20003 self.toggle_git_blame_inline_internal(true, window, cx);
20004 cx.notify();
20005 }
20006
20007 pub fn open_git_blame_commit(
20008 &mut self,
20009 _: &OpenGitBlameCommit,
20010 window: &mut Window,
20011 cx: &mut Context<Self>,
20012 ) {
20013 self.open_git_blame_commit_internal(window, cx);
20014 }
20015
20016 fn open_git_blame_commit_internal(
20017 &mut self,
20018 window: &mut Window,
20019 cx: &mut Context<Self>,
20020 ) -> Option<()> {
20021 let blame = self.blame.as_ref()?;
20022 let snapshot = self.snapshot(window, cx);
20023 let cursor = self
20024 .selections
20025 .newest::<Point>(&snapshot.display_snapshot)
20026 .head();
20027 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20028 let (_, blame_entry) = blame
20029 .update(cx, |blame, cx| {
20030 blame
20031 .blame_for_rows(
20032 &[RowInfo {
20033 buffer_id: Some(buffer.remote_id()),
20034 buffer_row: Some(point.row),
20035 ..Default::default()
20036 }],
20037 cx,
20038 )
20039 .next()
20040 })
20041 .flatten()?;
20042 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20043 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20044 let workspace = self.workspace()?.downgrade();
20045 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20046 None
20047 }
20048
20049 pub fn git_blame_inline_enabled(&self) -> bool {
20050 self.git_blame_inline_enabled
20051 }
20052
20053 pub fn toggle_selection_menu(
20054 &mut self,
20055 _: &ToggleSelectionMenu,
20056 _: &mut Window,
20057 cx: &mut Context<Self>,
20058 ) {
20059 self.show_selection_menu = self
20060 .show_selection_menu
20061 .map(|show_selections_menu| !show_selections_menu)
20062 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20063
20064 cx.notify();
20065 }
20066
20067 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20068 self.show_selection_menu
20069 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20070 }
20071
20072 fn start_git_blame(
20073 &mut self,
20074 user_triggered: bool,
20075 window: &mut Window,
20076 cx: &mut Context<Self>,
20077 ) {
20078 if let Some(project) = self.project() {
20079 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20080 && buffer.read(cx).file().is_none()
20081 {
20082 return;
20083 }
20084
20085 let focused = self.focus_handle(cx).contains_focused(window, cx);
20086
20087 let project = project.clone();
20088 let blame = cx
20089 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20090 self.blame_subscription =
20091 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20092 self.blame = Some(blame);
20093 }
20094 }
20095
20096 fn toggle_git_blame_inline_internal(
20097 &mut self,
20098 user_triggered: bool,
20099 window: &mut Window,
20100 cx: &mut Context<Self>,
20101 ) {
20102 if self.git_blame_inline_enabled {
20103 self.git_blame_inline_enabled = false;
20104 self.show_git_blame_inline = false;
20105 self.show_git_blame_inline_delay_task.take();
20106 } else {
20107 self.git_blame_inline_enabled = true;
20108 self.start_git_blame_inline(user_triggered, window, cx);
20109 }
20110
20111 cx.notify();
20112 }
20113
20114 fn start_git_blame_inline(
20115 &mut self,
20116 user_triggered: bool,
20117 window: &mut Window,
20118 cx: &mut Context<Self>,
20119 ) {
20120 self.start_git_blame(user_triggered, window, cx);
20121
20122 if ProjectSettings::get_global(cx)
20123 .git
20124 .inline_blame_delay()
20125 .is_some()
20126 {
20127 self.start_inline_blame_timer(window, cx);
20128 } else {
20129 self.show_git_blame_inline = true
20130 }
20131 }
20132
20133 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20134 self.blame.as_ref()
20135 }
20136
20137 pub fn show_git_blame_gutter(&self) -> bool {
20138 self.show_git_blame_gutter
20139 }
20140
20141 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20142 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20143 }
20144
20145 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20146 self.show_git_blame_inline
20147 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20148 && !self.newest_selection_head_on_empty_line(cx)
20149 && self.has_blame_entries(cx)
20150 }
20151
20152 fn has_blame_entries(&self, cx: &App) -> bool {
20153 self.blame()
20154 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20155 }
20156
20157 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20158 let cursor_anchor = self.selections.newest_anchor().head();
20159
20160 let snapshot = self.buffer.read(cx).snapshot(cx);
20161 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20162
20163 snapshot.line_len(buffer_row) == 0
20164 }
20165
20166 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20167 let buffer_and_selection = maybe!({
20168 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20169 let selection_range = selection.range();
20170
20171 let multi_buffer = self.buffer().read(cx);
20172 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20173 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20174
20175 let (buffer, range, _) = if selection.reversed {
20176 buffer_ranges.first()
20177 } else {
20178 buffer_ranges.last()
20179 }?;
20180
20181 let selection = text::ToPoint::to_point(&range.start, buffer).row
20182 ..text::ToPoint::to_point(&range.end, buffer).row;
20183 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20184 });
20185
20186 let Some((buffer, selection)) = buffer_and_selection else {
20187 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20188 };
20189
20190 let Some(project) = self.project() else {
20191 return Task::ready(Err(anyhow!("editor does not have project")));
20192 };
20193
20194 project.update(cx, |project, cx| {
20195 project.get_permalink_to_line(&buffer, selection, cx)
20196 })
20197 }
20198
20199 pub fn copy_permalink_to_line(
20200 &mut self,
20201 _: &CopyPermalinkToLine,
20202 window: &mut Window,
20203 cx: &mut Context<Self>,
20204 ) {
20205 let permalink_task = self.get_permalink_to_line(cx);
20206 let workspace = self.workspace();
20207
20208 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20209 Ok(permalink) => {
20210 cx.update(|_, cx| {
20211 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20212 })
20213 .ok();
20214 }
20215 Err(err) => {
20216 let message = format!("Failed to copy permalink: {err}");
20217
20218 anyhow::Result::<()>::Err(err).log_err();
20219
20220 if let Some(workspace) = workspace {
20221 workspace
20222 .update_in(cx, |workspace, _, cx| {
20223 struct CopyPermalinkToLine;
20224
20225 workspace.show_toast(
20226 Toast::new(
20227 NotificationId::unique::<CopyPermalinkToLine>(),
20228 message,
20229 ),
20230 cx,
20231 )
20232 })
20233 .ok();
20234 }
20235 }
20236 })
20237 .detach();
20238 }
20239
20240 pub fn copy_file_location(
20241 &mut self,
20242 _: &CopyFileLocation,
20243 _: &mut Window,
20244 cx: &mut Context<Self>,
20245 ) {
20246 let selection = self
20247 .selections
20248 .newest::<Point>(&self.display_snapshot(cx))
20249 .start
20250 .row
20251 + 1;
20252 if let Some(file) = self.target_file(cx) {
20253 let path = file.path().display(file.path_style(cx));
20254 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20255 }
20256 }
20257
20258 pub fn open_permalink_to_line(
20259 &mut self,
20260 _: &OpenPermalinkToLine,
20261 window: &mut Window,
20262 cx: &mut Context<Self>,
20263 ) {
20264 let permalink_task = self.get_permalink_to_line(cx);
20265 let workspace = self.workspace();
20266
20267 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20268 Ok(permalink) => {
20269 cx.update(|_, cx| {
20270 cx.open_url(permalink.as_ref());
20271 })
20272 .ok();
20273 }
20274 Err(err) => {
20275 let message = format!("Failed to open permalink: {err}");
20276
20277 anyhow::Result::<()>::Err(err).log_err();
20278
20279 if let Some(workspace) = workspace {
20280 workspace
20281 .update(cx, |workspace, cx| {
20282 struct OpenPermalinkToLine;
20283
20284 workspace.show_toast(
20285 Toast::new(
20286 NotificationId::unique::<OpenPermalinkToLine>(),
20287 message,
20288 ),
20289 cx,
20290 )
20291 })
20292 .ok();
20293 }
20294 }
20295 })
20296 .detach();
20297 }
20298
20299 pub fn insert_uuid_v4(
20300 &mut self,
20301 _: &InsertUuidV4,
20302 window: &mut Window,
20303 cx: &mut Context<Self>,
20304 ) {
20305 self.insert_uuid(UuidVersion::V4, window, cx);
20306 }
20307
20308 pub fn insert_uuid_v7(
20309 &mut self,
20310 _: &InsertUuidV7,
20311 window: &mut Window,
20312 cx: &mut Context<Self>,
20313 ) {
20314 self.insert_uuid(UuidVersion::V7, window, cx);
20315 }
20316
20317 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20318 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20319 self.transact(window, cx, |this, window, cx| {
20320 let edits = this
20321 .selections
20322 .all::<Point>(&this.display_snapshot(cx))
20323 .into_iter()
20324 .map(|selection| {
20325 let uuid = match version {
20326 UuidVersion::V4 => uuid::Uuid::new_v4(),
20327 UuidVersion::V7 => uuid::Uuid::now_v7(),
20328 };
20329
20330 (selection.range(), uuid.to_string())
20331 });
20332 this.edit(edits, cx);
20333 this.refresh_edit_prediction(true, false, window, cx);
20334 });
20335 }
20336
20337 pub fn open_selections_in_multibuffer(
20338 &mut self,
20339 _: &OpenSelectionsInMultibuffer,
20340 window: &mut Window,
20341 cx: &mut Context<Self>,
20342 ) {
20343 let multibuffer = self.buffer.read(cx);
20344
20345 let Some(buffer) = multibuffer.as_singleton() else {
20346 return;
20347 };
20348
20349 let Some(workspace) = self.workspace() else {
20350 return;
20351 };
20352
20353 let title = multibuffer.title(cx).to_string();
20354
20355 let locations = self
20356 .selections
20357 .all_anchors(&self.display_snapshot(cx))
20358 .iter()
20359 .map(|selection| {
20360 (
20361 buffer.clone(),
20362 (selection.start.text_anchor..selection.end.text_anchor)
20363 .to_point(buffer.read(cx)),
20364 )
20365 })
20366 .into_group_map();
20367
20368 cx.spawn_in(window, async move |_, cx| {
20369 workspace.update_in(cx, |workspace, window, cx| {
20370 Self::open_locations_in_multibuffer(
20371 workspace,
20372 locations,
20373 format!("Selections for '{title}'"),
20374 false,
20375 MultibufferSelectionMode::All,
20376 window,
20377 cx,
20378 );
20379 })
20380 })
20381 .detach();
20382 }
20383
20384 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20385 /// last highlight added will be used.
20386 ///
20387 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20388 pub fn highlight_rows<T: 'static>(
20389 &mut self,
20390 range: Range<Anchor>,
20391 color: Hsla,
20392 options: RowHighlightOptions,
20393 cx: &mut Context<Self>,
20394 ) {
20395 let snapshot = self.buffer().read(cx).snapshot(cx);
20396 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20397 let ix = row_highlights.binary_search_by(|highlight| {
20398 Ordering::Equal
20399 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20400 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20401 });
20402
20403 if let Err(mut ix) = ix {
20404 let index = post_inc(&mut self.highlight_order);
20405
20406 // If this range intersects with the preceding highlight, then merge it with
20407 // the preceding highlight. Otherwise insert a new highlight.
20408 let mut merged = false;
20409 if ix > 0 {
20410 let prev_highlight = &mut row_highlights[ix - 1];
20411 if prev_highlight
20412 .range
20413 .end
20414 .cmp(&range.start, &snapshot)
20415 .is_ge()
20416 {
20417 ix -= 1;
20418 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20419 prev_highlight.range.end = range.end;
20420 }
20421 merged = true;
20422 prev_highlight.index = index;
20423 prev_highlight.color = color;
20424 prev_highlight.options = options;
20425 }
20426 }
20427
20428 if !merged {
20429 row_highlights.insert(
20430 ix,
20431 RowHighlight {
20432 range,
20433 index,
20434 color,
20435 options,
20436 type_id: TypeId::of::<T>(),
20437 },
20438 );
20439 }
20440
20441 // If any of the following highlights intersect with this one, merge them.
20442 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20443 let highlight = &row_highlights[ix];
20444 if next_highlight
20445 .range
20446 .start
20447 .cmp(&highlight.range.end, &snapshot)
20448 .is_le()
20449 {
20450 if next_highlight
20451 .range
20452 .end
20453 .cmp(&highlight.range.end, &snapshot)
20454 .is_gt()
20455 {
20456 row_highlights[ix].range.end = next_highlight.range.end;
20457 }
20458 row_highlights.remove(ix + 1);
20459 } else {
20460 break;
20461 }
20462 }
20463 }
20464 }
20465
20466 /// Remove any highlighted row ranges of the given type that intersect the
20467 /// given ranges.
20468 pub fn remove_highlighted_rows<T: 'static>(
20469 &mut self,
20470 ranges_to_remove: Vec<Range<Anchor>>,
20471 cx: &mut Context<Self>,
20472 ) {
20473 let snapshot = self.buffer().read(cx).snapshot(cx);
20474 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20475 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20476 row_highlights.retain(|highlight| {
20477 while let Some(range_to_remove) = ranges_to_remove.peek() {
20478 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20479 Ordering::Less | Ordering::Equal => {
20480 ranges_to_remove.next();
20481 }
20482 Ordering::Greater => {
20483 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20484 Ordering::Less | Ordering::Equal => {
20485 return false;
20486 }
20487 Ordering::Greater => break,
20488 }
20489 }
20490 }
20491 }
20492
20493 true
20494 })
20495 }
20496
20497 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20498 pub fn clear_row_highlights<T: 'static>(&mut self) {
20499 self.highlighted_rows.remove(&TypeId::of::<T>());
20500 }
20501
20502 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20503 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20504 self.highlighted_rows
20505 .get(&TypeId::of::<T>())
20506 .map_or(&[] as &[_], |vec| vec.as_slice())
20507 .iter()
20508 .map(|highlight| (highlight.range.clone(), highlight.color))
20509 }
20510
20511 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20512 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20513 /// Allows to ignore certain kinds of highlights.
20514 pub fn highlighted_display_rows(
20515 &self,
20516 window: &mut Window,
20517 cx: &mut App,
20518 ) -> BTreeMap<DisplayRow, LineHighlight> {
20519 let snapshot = self.snapshot(window, cx);
20520 let mut used_highlight_orders = HashMap::default();
20521 self.highlighted_rows
20522 .iter()
20523 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20524 .fold(
20525 BTreeMap::<DisplayRow, LineHighlight>::new(),
20526 |mut unique_rows, highlight| {
20527 let start = highlight.range.start.to_display_point(&snapshot);
20528 let end = highlight.range.end.to_display_point(&snapshot);
20529 let start_row = start.row().0;
20530 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20531 && end.column() == 0
20532 {
20533 end.row().0.saturating_sub(1)
20534 } else {
20535 end.row().0
20536 };
20537 for row in start_row..=end_row {
20538 let used_index =
20539 used_highlight_orders.entry(row).or_insert(highlight.index);
20540 if highlight.index >= *used_index {
20541 *used_index = highlight.index;
20542 unique_rows.insert(
20543 DisplayRow(row),
20544 LineHighlight {
20545 include_gutter: highlight.options.include_gutter,
20546 border: None,
20547 background: highlight.color.into(),
20548 type_id: Some(highlight.type_id),
20549 },
20550 );
20551 }
20552 }
20553 unique_rows
20554 },
20555 )
20556 }
20557
20558 pub fn highlighted_display_row_for_autoscroll(
20559 &self,
20560 snapshot: &DisplaySnapshot,
20561 ) -> Option<DisplayRow> {
20562 self.highlighted_rows
20563 .values()
20564 .flat_map(|highlighted_rows| highlighted_rows.iter())
20565 .filter_map(|highlight| {
20566 if highlight.options.autoscroll {
20567 Some(highlight.range.start.to_display_point(snapshot).row())
20568 } else {
20569 None
20570 }
20571 })
20572 .min()
20573 }
20574
20575 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20576 self.highlight_background::<SearchWithinRange>(
20577 ranges,
20578 |colors| colors.colors().editor_document_highlight_read_background,
20579 cx,
20580 )
20581 }
20582
20583 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20584 self.breadcrumb_header = Some(new_header);
20585 }
20586
20587 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20588 self.clear_background_highlights::<SearchWithinRange>(cx);
20589 }
20590
20591 pub fn highlight_background<T: 'static>(
20592 &mut self,
20593 ranges: &[Range<Anchor>],
20594 color_fetcher: fn(&Theme) -> Hsla,
20595 cx: &mut Context<Self>,
20596 ) {
20597 self.background_highlights.insert(
20598 HighlightKey::Type(TypeId::of::<T>()),
20599 (color_fetcher, Arc::from(ranges)),
20600 );
20601 self.scrollbar_marker_state.dirty = true;
20602 cx.notify();
20603 }
20604
20605 pub fn highlight_background_key<T: 'static>(
20606 &mut self,
20607 key: usize,
20608 ranges: &[Range<Anchor>],
20609 color_fetcher: fn(&Theme) -> Hsla,
20610 cx: &mut Context<Self>,
20611 ) {
20612 self.background_highlights.insert(
20613 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20614 (color_fetcher, Arc::from(ranges)),
20615 );
20616 self.scrollbar_marker_state.dirty = true;
20617 cx.notify();
20618 }
20619
20620 pub fn clear_background_highlights<T: 'static>(
20621 &mut self,
20622 cx: &mut Context<Self>,
20623 ) -> Option<BackgroundHighlight> {
20624 let text_highlights = self
20625 .background_highlights
20626 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20627 if !text_highlights.1.is_empty() {
20628 self.scrollbar_marker_state.dirty = true;
20629 cx.notify();
20630 }
20631 Some(text_highlights)
20632 }
20633
20634 pub fn highlight_gutter<T: 'static>(
20635 &mut self,
20636 ranges: impl Into<Vec<Range<Anchor>>>,
20637 color_fetcher: fn(&App) -> Hsla,
20638 cx: &mut Context<Self>,
20639 ) {
20640 self.gutter_highlights
20641 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20642 cx.notify();
20643 }
20644
20645 pub fn clear_gutter_highlights<T: 'static>(
20646 &mut self,
20647 cx: &mut Context<Self>,
20648 ) -> Option<GutterHighlight> {
20649 cx.notify();
20650 self.gutter_highlights.remove(&TypeId::of::<T>())
20651 }
20652
20653 pub fn insert_gutter_highlight<T: 'static>(
20654 &mut self,
20655 range: Range<Anchor>,
20656 color_fetcher: fn(&App) -> Hsla,
20657 cx: &mut Context<Self>,
20658 ) {
20659 let snapshot = self.buffer().read(cx).snapshot(cx);
20660 let mut highlights = self
20661 .gutter_highlights
20662 .remove(&TypeId::of::<T>())
20663 .map(|(_, highlights)| highlights)
20664 .unwrap_or_default();
20665 let ix = highlights.binary_search_by(|highlight| {
20666 Ordering::Equal
20667 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20668 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20669 });
20670 if let Err(ix) = ix {
20671 highlights.insert(ix, range);
20672 }
20673 self.gutter_highlights
20674 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20675 }
20676
20677 pub fn remove_gutter_highlights<T: 'static>(
20678 &mut self,
20679 ranges_to_remove: Vec<Range<Anchor>>,
20680 cx: &mut Context<Self>,
20681 ) {
20682 let snapshot = self.buffer().read(cx).snapshot(cx);
20683 let Some((color_fetcher, mut gutter_highlights)) =
20684 self.gutter_highlights.remove(&TypeId::of::<T>())
20685 else {
20686 return;
20687 };
20688 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20689 gutter_highlights.retain(|highlight| {
20690 while let Some(range_to_remove) = ranges_to_remove.peek() {
20691 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20692 Ordering::Less | Ordering::Equal => {
20693 ranges_to_remove.next();
20694 }
20695 Ordering::Greater => {
20696 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20697 Ordering::Less | Ordering::Equal => {
20698 return false;
20699 }
20700 Ordering::Greater => break,
20701 }
20702 }
20703 }
20704 }
20705
20706 true
20707 });
20708 self.gutter_highlights
20709 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20710 }
20711
20712 #[cfg(feature = "test-support")]
20713 pub fn all_text_highlights(
20714 &self,
20715 window: &mut Window,
20716 cx: &mut Context<Self>,
20717 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20718 let snapshot = self.snapshot(window, cx);
20719 self.display_map.update(cx, |display_map, _| {
20720 display_map
20721 .all_text_highlights()
20722 .map(|highlight| {
20723 let (style, ranges) = highlight.as_ref();
20724 (
20725 *style,
20726 ranges
20727 .iter()
20728 .map(|range| range.clone().to_display_points(&snapshot))
20729 .collect(),
20730 )
20731 })
20732 .collect()
20733 })
20734 }
20735
20736 #[cfg(feature = "test-support")]
20737 pub fn all_text_background_highlights(
20738 &self,
20739 window: &mut Window,
20740 cx: &mut Context<Self>,
20741 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20742 let snapshot = self.snapshot(window, cx);
20743 let buffer = &snapshot.buffer_snapshot();
20744 let start = buffer.anchor_before(0);
20745 let end = buffer.anchor_after(buffer.len());
20746 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20747 }
20748
20749 #[cfg(any(test, feature = "test-support"))]
20750 pub fn sorted_background_highlights_in_range(
20751 &self,
20752 search_range: Range<Anchor>,
20753 display_snapshot: &DisplaySnapshot,
20754 theme: &Theme,
20755 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20756 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20757 res.sort_by(|a, b| {
20758 a.0.start
20759 .cmp(&b.0.start)
20760 .then_with(|| a.0.end.cmp(&b.0.end))
20761 .then_with(|| a.1.cmp(&b.1))
20762 });
20763 res
20764 }
20765
20766 #[cfg(feature = "test-support")]
20767 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20768 let snapshot = self.buffer().read(cx).snapshot(cx);
20769
20770 let highlights = self
20771 .background_highlights
20772 .get(&HighlightKey::Type(TypeId::of::<
20773 items::BufferSearchHighlights,
20774 >()));
20775
20776 if let Some((_color, ranges)) = highlights {
20777 ranges
20778 .iter()
20779 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20780 .collect_vec()
20781 } else {
20782 vec![]
20783 }
20784 }
20785
20786 fn document_highlights_for_position<'a>(
20787 &'a self,
20788 position: Anchor,
20789 buffer: &'a MultiBufferSnapshot,
20790 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20791 let read_highlights = self
20792 .background_highlights
20793 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20794 .map(|h| &h.1);
20795 let write_highlights = self
20796 .background_highlights
20797 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20798 .map(|h| &h.1);
20799 let left_position = position.bias_left(buffer);
20800 let right_position = position.bias_right(buffer);
20801 read_highlights
20802 .into_iter()
20803 .chain(write_highlights)
20804 .flat_map(move |ranges| {
20805 let start_ix = match ranges.binary_search_by(|probe| {
20806 let cmp = probe.end.cmp(&left_position, buffer);
20807 if cmp.is_ge() {
20808 Ordering::Greater
20809 } else {
20810 Ordering::Less
20811 }
20812 }) {
20813 Ok(i) | Err(i) => i,
20814 };
20815
20816 ranges[start_ix..]
20817 .iter()
20818 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20819 })
20820 }
20821
20822 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20823 self.background_highlights
20824 .get(&HighlightKey::Type(TypeId::of::<T>()))
20825 .is_some_and(|(_, highlights)| !highlights.is_empty())
20826 }
20827
20828 /// Returns all background highlights for a given range.
20829 ///
20830 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20831 pub fn background_highlights_in_range(
20832 &self,
20833 search_range: Range<Anchor>,
20834 display_snapshot: &DisplaySnapshot,
20835 theme: &Theme,
20836 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20837 let mut results = Vec::new();
20838 for (color_fetcher, ranges) in self.background_highlights.values() {
20839 let color = color_fetcher(theme);
20840 let start_ix = match ranges.binary_search_by(|probe| {
20841 let cmp = probe
20842 .end
20843 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20844 if cmp.is_gt() {
20845 Ordering::Greater
20846 } else {
20847 Ordering::Less
20848 }
20849 }) {
20850 Ok(i) | Err(i) => i,
20851 };
20852 for range in &ranges[start_ix..] {
20853 if range
20854 .start
20855 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20856 .is_ge()
20857 {
20858 break;
20859 }
20860
20861 let start = range.start.to_display_point(display_snapshot);
20862 let end = range.end.to_display_point(display_snapshot);
20863 results.push((start..end, color))
20864 }
20865 }
20866 results
20867 }
20868
20869 pub fn gutter_highlights_in_range(
20870 &self,
20871 search_range: Range<Anchor>,
20872 display_snapshot: &DisplaySnapshot,
20873 cx: &App,
20874 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20875 let mut results = Vec::new();
20876 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20877 let color = color_fetcher(cx);
20878 let start_ix = match ranges.binary_search_by(|probe| {
20879 let cmp = probe
20880 .end
20881 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20882 if cmp.is_gt() {
20883 Ordering::Greater
20884 } else {
20885 Ordering::Less
20886 }
20887 }) {
20888 Ok(i) | Err(i) => i,
20889 };
20890 for range in &ranges[start_ix..] {
20891 if range
20892 .start
20893 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20894 .is_ge()
20895 {
20896 break;
20897 }
20898
20899 let start = range.start.to_display_point(display_snapshot);
20900 let end = range.end.to_display_point(display_snapshot);
20901 results.push((start..end, color))
20902 }
20903 }
20904 results
20905 }
20906
20907 /// Get the text ranges corresponding to the redaction query
20908 pub fn redacted_ranges(
20909 &self,
20910 search_range: Range<Anchor>,
20911 display_snapshot: &DisplaySnapshot,
20912 cx: &App,
20913 ) -> Vec<Range<DisplayPoint>> {
20914 display_snapshot
20915 .buffer_snapshot()
20916 .redacted_ranges(search_range, |file| {
20917 if let Some(file) = file {
20918 file.is_private()
20919 && EditorSettings::get(
20920 Some(SettingsLocation {
20921 worktree_id: file.worktree_id(cx),
20922 path: file.path().as_ref(),
20923 }),
20924 cx,
20925 )
20926 .redact_private_values
20927 } else {
20928 false
20929 }
20930 })
20931 .map(|range| {
20932 range.start.to_display_point(display_snapshot)
20933 ..range.end.to_display_point(display_snapshot)
20934 })
20935 .collect()
20936 }
20937
20938 pub fn highlight_text_key<T: 'static>(
20939 &mut self,
20940 key: usize,
20941 ranges: Vec<Range<Anchor>>,
20942 style: HighlightStyle,
20943 cx: &mut Context<Self>,
20944 ) {
20945 self.display_map.update(cx, |map, _| {
20946 map.highlight_text(
20947 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20948 ranges,
20949 style,
20950 );
20951 });
20952 cx.notify();
20953 }
20954
20955 pub fn highlight_text<T: 'static>(
20956 &mut self,
20957 ranges: Vec<Range<Anchor>>,
20958 style: HighlightStyle,
20959 cx: &mut Context<Self>,
20960 ) {
20961 self.display_map.update(cx, |map, _| {
20962 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20963 });
20964 cx.notify();
20965 }
20966
20967 pub fn text_highlights<'a, T: 'static>(
20968 &'a self,
20969 cx: &'a App,
20970 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20971 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20972 }
20973
20974 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20975 let cleared = self
20976 .display_map
20977 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20978 if cleared {
20979 cx.notify();
20980 }
20981 }
20982
20983 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20984 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20985 && self.focus_handle.is_focused(window)
20986 }
20987
20988 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20989 self.show_cursor_when_unfocused = is_enabled;
20990 cx.notify();
20991 }
20992
20993 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20994 cx.notify();
20995 }
20996
20997 fn on_debug_session_event(
20998 &mut self,
20999 _session: Entity<Session>,
21000 event: &SessionEvent,
21001 cx: &mut Context<Self>,
21002 ) {
21003 if let SessionEvent::InvalidateInlineValue = event {
21004 self.refresh_inline_values(cx);
21005 }
21006 }
21007
21008 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21009 let Some(project) = self.project.clone() else {
21010 return;
21011 };
21012
21013 if !self.inline_value_cache.enabled {
21014 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21015 self.splice_inlays(&inlays, Vec::new(), cx);
21016 return;
21017 }
21018
21019 let current_execution_position = self
21020 .highlighted_rows
21021 .get(&TypeId::of::<ActiveDebugLine>())
21022 .and_then(|lines| lines.last().map(|line| line.range.end));
21023
21024 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21025 let inline_values = editor
21026 .update(cx, |editor, cx| {
21027 let Some(current_execution_position) = current_execution_position else {
21028 return Some(Task::ready(Ok(Vec::new())));
21029 };
21030
21031 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21032 let snapshot = buffer.snapshot(cx);
21033
21034 let excerpt = snapshot.excerpt_containing(
21035 current_execution_position..current_execution_position,
21036 )?;
21037
21038 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21039 })?;
21040
21041 let range =
21042 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21043
21044 project.inline_values(buffer, range, cx)
21045 })
21046 .ok()
21047 .flatten()?
21048 .await
21049 .context("refreshing debugger inlays")
21050 .log_err()?;
21051
21052 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21053
21054 for (buffer_id, inline_value) in inline_values
21055 .into_iter()
21056 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21057 {
21058 buffer_inline_values
21059 .entry(buffer_id)
21060 .or_default()
21061 .push(inline_value);
21062 }
21063
21064 editor
21065 .update(cx, |editor, cx| {
21066 let snapshot = editor.buffer.read(cx).snapshot(cx);
21067 let mut new_inlays = Vec::default();
21068
21069 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21070 let buffer_id = buffer_snapshot.remote_id();
21071 buffer_inline_values
21072 .get(&buffer_id)
21073 .into_iter()
21074 .flatten()
21075 .for_each(|hint| {
21076 let inlay = Inlay::debugger(
21077 post_inc(&mut editor.next_inlay_id),
21078 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
21079 hint.text(),
21080 );
21081 if !inlay.text().chars().contains(&'\n') {
21082 new_inlays.push(inlay);
21083 }
21084 });
21085 }
21086
21087 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21088 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21089
21090 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21091 })
21092 .ok()?;
21093 Some(())
21094 });
21095 }
21096
21097 fn on_buffer_event(
21098 &mut self,
21099 multibuffer: &Entity<MultiBuffer>,
21100 event: &multi_buffer::Event,
21101 window: &mut Window,
21102 cx: &mut Context<Self>,
21103 ) {
21104 match event {
21105 multi_buffer::Event::Edited { edited_buffer } => {
21106 self.scrollbar_marker_state.dirty = true;
21107 self.active_indent_guides_state.dirty = true;
21108 self.refresh_active_diagnostics(cx);
21109 self.refresh_code_actions(window, cx);
21110 self.refresh_selected_text_highlights(true, window, cx);
21111 self.refresh_single_line_folds(window, cx);
21112 self.refresh_matching_bracket_highlights(window, cx);
21113 if self.has_active_edit_prediction() {
21114 self.update_visible_edit_prediction(window, cx);
21115 }
21116
21117 if let Some(buffer) = edited_buffer {
21118 if buffer.read(cx).file().is_none() {
21119 cx.emit(EditorEvent::TitleChanged);
21120 }
21121
21122 if self.project.is_some() {
21123 let buffer_id = buffer.read(cx).remote_id();
21124 self.register_buffer(buffer_id, cx);
21125 self.update_lsp_data(Some(buffer_id), window, cx);
21126 self.refresh_inlay_hints(
21127 InlayHintRefreshReason::BufferEdited(buffer_id),
21128 cx,
21129 );
21130 }
21131 }
21132
21133 cx.emit(EditorEvent::BufferEdited);
21134 cx.emit(SearchEvent::MatchesInvalidated);
21135
21136 let Some(project) = &self.project else { return };
21137 let (telemetry, is_via_ssh) = {
21138 let project = project.read(cx);
21139 let telemetry = project.client().telemetry().clone();
21140 let is_via_ssh = project.is_via_remote_server();
21141 (telemetry, is_via_ssh)
21142 };
21143 telemetry.log_edit_event("editor", is_via_ssh);
21144 }
21145 multi_buffer::Event::ExcerptsAdded {
21146 buffer,
21147 predecessor,
21148 excerpts,
21149 } => {
21150 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21151 let buffer_id = buffer.read(cx).remote_id();
21152 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21153 && let Some(project) = &self.project
21154 {
21155 update_uncommitted_diff_for_buffer(
21156 cx.entity(),
21157 project,
21158 [buffer.clone()],
21159 self.buffer.clone(),
21160 cx,
21161 )
21162 .detach();
21163 }
21164 self.update_lsp_data(Some(buffer_id), window, cx);
21165 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21166 cx.emit(EditorEvent::ExcerptsAdded {
21167 buffer: buffer.clone(),
21168 predecessor: *predecessor,
21169 excerpts: excerpts.clone(),
21170 });
21171 }
21172 multi_buffer::Event::ExcerptsRemoved {
21173 ids,
21174 removed_buffer_ids,
21175 } => {
21176 if let Some(inlay_hints) = &mut self.inlay_hints {
21177 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21178 }
21179 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21180 for buffer_id in removed_buffer_ids {
21181 self.registered_buffers.remove(buffer_id);
21182 }
21183 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21184 cx.emit(EditorEvent::ExcerptsRemoved {
21185 ids: ids.clone(),
21186 removed_buffer_ids: removed_buffer_ids.clone(),
21187 });
21188 }
21189 multi_buffer::Event::ExcerptsEdited {
21190 excerpt_ids,
21191 buffer_ids,
21192 } => {
21193 self.display_map.update(cx, |map, cx| {
21194 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21195 });
21196 cx.emit(EditorEvent::ExcerptsEdited {
21197 ids: excerpt_ids.clone(),
21198 });
21199 }
21200 multi_buffer::Event::ExcerptsExpanded { ids } => {
21201 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21202 self.refresh_document_highlights(cx);
21203 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21204 }
21205 multi_buffer::Event::Reparsed(buffer_id) => {
21206 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21207 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21208
21209 cx.emit(EditorEvent::Reparsed(*buffer_id));
21210 }
21211 multi_buffer::Event::DiffHunksToggled => {
21212 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21213 }
21214 multi_buffer::Event::LanguageChanged(buffer_id) => {
21215 self.registered_buffers.remove(&buffer_id);
21216 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21217 cx.emit(EditorEvent::Reparsed(*buffer_id));
21218 cx.notify();
21219 }
21220 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21221 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21222 multi_buffer::Event::FileHandleChanged
21223 | multi_buffer::Event::Reloaded
21224 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21225 multi_buffer::Event::DiagnosticsUpdated => {
21226 self.update_diagnostics_state(window, cx);
21227 }
21228 _ => {}
21229 };
21230 }
21231
21232 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21233 if !self.diagnostics_enabled() {
21234 return;
21235 }
21236 self.refresh_active_diagnostics(cx);
21237 self.refresh_inline_diagnostics(true, window, cx);
21238 self.scrollbar_marker_state.dirty = true;
21239 cx.notify();
21240 }
21241
21242 pub fn start_temporary_diff_override(&mut self) {
21243 self.load_diff_task.take();
21244 self.temporary_diff_override = true;
21245 }
21246
21247 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21248 self.temporary_diff_override = false;
21249 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21250 self.buffer.update(cx, |buffer, cx| {
21251 buffer.set_all_diff_hunks_collapsed(cx);
21252 });
21253
21254 if let Some(project) = self.project.clone() {
21255 self.load_diff_task = Some(
21256 update_uncommitted_diff_for_buffer(
21257 cx.entity(),
21258 &project,
21259 self.buffer.read(cx).all_buffers(),
21260 self.buffer.clone(),
21261 cx,
21262 )
21263 .shared(),
21264 );
21265 }
21266 }
21267
21268 fn on_display_map_changed(
21269 &mut self,
21270 _: Entity<DisplayMap>,
21271 _: &mut Window,
21272 cx: &mut Context<Self>,
21273 ) {
21274 cx.notify();
21275 }
21276
21277 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21278 if self.diagnostics_enabled() {
21279 let new_severity = EditorSettings::get_global(cx)
21280 .diagnostics_max_severity
21281 .unwrap_or(DiagnosticSeverity::Hint);
21282 self.set_max_diagnostics_severity(new_severity, cx);
21283 }
21284 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21285 self.update_edit_prediction_settings(cx);
21286 self.refresh_edit_prediction(true, false, window, cx);
21287 self.refresh_inline_values(cx);
21288 self.refresh_inlay_hints(
21289 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21290 self.selections.newest_anchor().head(),
21291 &self.buffer.read(cx).snapshot(cx),
21292 cx,
21293 )),
21294 cx,
21295 );
21296
21297 let old_cursor_shape = self.cursor_shape;
21298 let old_show_breadcrumbs = self.show_breadcrumbs;
21299
21300 {
21301 let editor_settings = EditorSettings::get_global(cx);
21302 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21303 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21304 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21305 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21306 }
21307
21308 if old_cursor_shape != self.cursor_shape {
21309 cx.emit(EditorEvent::CursorShapeChanged);
21310 }
21311
21312 if old_show_breadcrumbs != self.show_breadcrumbs {
21313 cx.emit(EditorEvent::BreadcrumbsChanged);
21314 }
21315
21316 let project_settings = ProjectSettings::get_global(cx);
21317 self.buffer_serialization = self
21318 .should_serialize_buffer()
21319 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21320
21321 if self.mode.is_full() {
21322 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21323 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21324 if self.show_inline_diagnostics != show_inline_diagnostics {
21325 self.show_inline_diagnostics = show_inline_diagnostics;
21326 self.refresh_inline_diagnostics(false, window, cx);
21327 }
21328
21329 if self.git_blame_inline_enabled != inline_blame_enabled {
21330 self.toggle_git_blame_inline_internal(false, window, cx);
21331 }
21332
21333 let minimap_settings = EditorSettings::get_global(cx).minimap;
21334 if self.minimap_visibility != MinimapVisibility::Disabled {
21335 if self.minimap_visibility.settings_visibility()
21336 != minimap_settings.minimap_enabled()
21337 {
21338 self.set_minimap_visibility(
21339 MinimapVisibility::for_mode(self.mode(), cx),
21340 window,
21341 cx,
21342 );
21343 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21344 minimap_entity.update(cx, |minimap_editor, cx| {
21345 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21346 })
21347 }
21348 }
21349 }
21350
21351 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21352 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21353 }) {
21354 if !inlay_splice.is_empty() {
21355 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21356 }
21357 self.refresh_colors_for_visible_range(None, window, cx);
21358 }
21359
21360 cx.notify();
21361 }
21362
21363 pub fn set_searchable(&mut self, searchable: bool) {
21364 self.searchable = searchable;
21365 }
21366
21367 pub fn searchable(&self) -> bool {
21368 self.searchable
21369 }
21370
21371 pub fn open_excerpts_in_split(
21372 &mut self,
21373 _: &OpenExcerptsSplit,
21374 window: &mut Window,
21375 cx: &mut Context<Self>,
21376 ) {
21377 self.open_excerpts_common(None, true, window, cx)
21378 }
21379
21380 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21381 self.open_excerpts_common(None, false, window, cx)
21382 }
21383
21384 fn open_excerpts_common(
21385 &mut self,
21386 jump_data: Option<JumpData>,
21387 split: bool,
21388 window: &mut Window,
21389 cx: &mut Context<Self>,
21390 ) {
21391 let Some(workspace) = self.workspace() else {
21392 cx.propagate();
21393 return;
21394 };
21395
21396 if self.buffer.read(cx).is_singleton() {
21397 cx.propagate();
21398 return;
21399 }
21400
21401 let mut new_selections_by_buffer = HashMap::default();
21402 match &jump_data {
21403 Some(JumpData::MultiBufferPoint {
21404 excerpt_id,
21405 position,
21406 anchor,
21407 line_offset_from_top,
21408 }) => {
21409 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21410 if let Some(buffer) = multi_buffer_snapshot
21411 .buffer_id_for_excerpt(*excerpt_id)
21412 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21413 {
21414 let buffer_snapshot = buffer.read(cx).snapshot();
21415 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21416 language::ToPoint::to_point(anchor, &buffer_snapshot)
21417 } else {
21418 buffer_snapshot.clip_point(*position, Bias::Left)
21419 };
21420 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21421 new_selections_by_buffer.insert(
21422 buffer,
21423 (
21424 vec![jump_to_offset..jump_to_offset],
21425 Some(*line_offset_from_top),
21426 ),
21427 );
21428 }
21429 }
21430 Some(JumpData::MultiBufferRow {
21431 row,
21432 line_offset_from_top,
21433 }) => {
21434 let point = MultiBufferPoint::new(row.0, 0);
21435 if let Some((buffer, buffer_point, _)) =
21436 self.buffer.read(cx).point_to_buffer_point(point, cx)
21437 {
21438 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21439 new_selections_by_buffer
21440 .entry(buffer)
21441 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21442 .0
21443 .push(buffer_offset..buffer_offset)
21444 }
21445 }
21446 None => {
21447 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21448 let multi_buffer = self.buffer.read(cx);
21449 for selection in selections {
21450 for (snapshot, range, _, anchor) in multi_buffer
21451 .snapshot(cx)
21452 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21453 {
21454 if let Some(anchor) = anchor {
21455 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21456 else {
21457 continue;
21458 };
21459 let offset = text::ToOffset::to_offset(
21460 &anchor.text_anchor,
21461 &buffer_handle.read(cx).snapshot(),
21462 );
21463 let range = offset..offset;
21464 new_selections_by_buffer
21465 .entry(buffer_handle)
21466 .or_insert((Vec::new(), None))
21467 .0
21468 .push(range)
21469 } else {
21470 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21471 else {
21472 continue;
21473 };
21474 new_selections_by_buffer
21475 .entry(buffer_handle)
21476 .or_insert((Vec::new(), None))
21477 .0
21478 .push(range)
21479 }
21480 }
21481 }
21482 }
21483 }
21484
21485 new_selections_by_buffer
21486 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21487
21488 if new_selections_by_buffer.is_empty() {
21489 return;
21490 }
21491
21492 // We defer the pane interaction because we ourselves are a workspace item
21493 // and activating a new item causes the pane to call a method on us reentrantly,
21494 // which panics if we're on the stack.
21495 window.defer(cx, move |window, cx| {
21496 workspace.update(cx, |workspace, cx| {
21497 let pane = if split {
21498 workspace.adjacent_pane(window, cx)
21499 } else {
21500 workspace.active_pane().clone()
21501 };
21502
21503 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21504 let editor = buffer
21505 .read(cx)
21506 .file()
21507 .is_none()
21508 .then(|| {
21509 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21510 // so `workspace.open_project_item` will never find them, always opening a new editor.
21511 // Instead, we try to activate the existing editor in the pane first.
21512 let (editor, pane_item_index) =
21513 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21514 let editor = item.downcast::<Editor>()?;
21515 let singleton_buffer =
21516 editor.read(cx).buffer().read(cx).as_singleton()?;
21517 if singleton_buffer == buffer {
21518 Some((editor, i))
21519 } else {
21520 None
21521 }
21522 })?;
21523 pane.update(cx, |pane, cx| {
21524 pane.activate_item(pane_item_index, true, true, window, cx)
21525 });
21526 Some(editor)
21527 })
21528 .flatten()
21529 .unwrap_or_else(|| {
21530 workspace.open_project_item::<Self>(
21531 pane.clone(),
21532 buffer,
21533 true,
21534 true,
21535 window,
21536 cx,
21537 )
21538 });
21539
21540 editor.update(cx, |editor, cx| {
21541 let autoscroll = match scroll_offset {
21542 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21543 None => Autoscroll::newest(),
21544 };
21545 let nav_history = editor.nav_history.take();
21546 editor.change_selections(
21547 SelectionEffects::scroll(autoscroll),
21548 window,
21549 cx,
21550 |s| {
21551 s.select_ranges(ranges);
21552 },
21553 );
21554 editor.nav_history = nav_history;
21555 });
21556 }
21557 })
21558 });
21559 }
21560
21561 // For now, don't allow opening excerpts in buffers that aren't backed by
21562 // regular project files.
21563 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21564 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21565 }
21566
21567 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21568 let snapshot = self.buffer.read(cx).read(cx);
21569 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21570 Some(
21571 ranges
21572 .iter()
21573 .map(move |range| {
21574 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21575 })
21576 .collect(),
21577 )
21578 }
21579
21580 fn selection_replacement_ranges(
21581 &self,
21582 range: Range<OffsetUtf16>,
21583 cx: &mut App,
21584 ) -> Vec<Range<OffsetUtf16>> {
21585 let selections = self
21586 .selections
21587 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21588 let newest_selection = selections
21589 .iter()
21590 .max_by_key(|selection| selection.id)
21591 .unwrap();
21592 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21593 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21594 let snapshot = self.buffer.read(cx).read(cx);
21595 selections
21596 .into_iter()
21597 .map(|mut selection| {
21598 selection.start.0 =
21599 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21600 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21601 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21602 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21603 })
21604 .collect()
21605 }
21606
21607 fn report_editor_event(
21608 &self,
21609 reported_event: ReportEditorEvent,
21610 file_extension: Option<String>,
21611 cx: &App,
21612 ) {
21613 if cfg!(any(test, feature = "test-support")) {
21614 return;
21615 }
21616
21617 let Some(project) = &self.project else { return };
21618
21619 // If None, we are in a file without an extension
21620 let file = self
21621 .buffer
21622 .read(cx)
21623 .as_singleton()
21624 .and_then(|b| b.read(cx).file());
21625 let file_extension = file_extension.or(file
21626 .as_ref()
21627 .and_then(|file| Path::new(file.file_name(cx)).extension())
21628 .and_then(|e| e.to_str())
21629 .map(|a| a.to_string()));
21630
21631 let vim_mode = vim_flavor(cx).is_some();
21632
21633 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21634 let copilot_enabled = edit_predictions_provider
21635 == language::language_settings::EditPredictionProvider::Copilot;
21636 let copilot_enabled_for_language = self
21637 .buffer
21638 .read(cx)
21639 .language_settings(cx)
21640 .show_edit_predictions;
21641
21642 let project = project.read(cx);
21643 let event_type = reported_event.event_type();
21644
21645 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21646 telemetry::event!(
21647 event_type,
21648 type = if auto_saved {"autosave"} else {"manual"},
21649 file_extension,
21650 vim_mode,
21651 copilot_enabled,
21652 copilot_enabled_for_language,
21653 edit_predictions_provider,
21654 is_via_ssh = project.is_via_remote_server(),
21655 );
21656 } else {
21657 telemetry::event!(
21658 event_type,
21659 file_extension,
21660 vim_mode,
21661 copilot_enabled,
21662 copilot_enabled_for_language,
21663 edit_predictions_provider,
21664 is_via_ssh = project.is_via_remote_server(),
21665 );
21666 };
21667 }
21668
21669 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21670 /// with each line being an array of {text, highlight} objects.
21671 fn copy_highlight_json(
21672 &mut self,
21673 _: &CopyHighlightJson,
21674 window: &mut Window,
21675 cx: &mut Context<Self>,
21676 ) {
21677 #[derive(Serialize)]
21678 struct Chunk<'a> {
21679 text: String,
21680 highlight: Option<&'a str>,
21681 }
21682
21683 let snapshot = self.buffer.read(cx).snapshot(cx);
21684 let range = self
21685 .selected_text_range(false, window, cx)
21686 .and_then(|selection| {
21687 if selection.range.is_empty() {
21688 None
21689 } else {
21690 Some(
21691 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21692 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21693 )
21694 }
21695 })
21696 .unwrap_or_else(|| 0..snapshot.len());
21697
21698 let chunks = snapshot.chunks(range, true);
21699 let mut lines = Vec::new();
21700 let mut line: VecDeque<Chunk> = VecDeque::new();
21701
21702 let Some(style) = self.style.as_ref() else {
21703 return;
21704 };
21705
21706 for chunk in chunks {
21707 let highlight = chunk
21708 .syntax_highlight_id
21709 .and_then(|id| id.name(&style.syntax));
21710 let mut chunk_lines = chunk.text.split('\n').peekable();
21711 while let Some(text) = chunk_lines.next() {
21712 let mut merged_with_last_token = false;
21713 if let Some(last_token) = line.back_mut()
21714 && last_token.highlight == highlight
21715 {
21716 last_token.text.push_str(text);
21717 merged_with_last_token = true;
21718 }
21719
21720 if !merged_with_last_token {
21721 line.push_back(Chunk {
21722 text: text.into(),
21723 highlight,
21724 });
21725 }
21726
21727 if chunk_lines.peek().is_some() {
21728 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21729 line.pop_front();
21730 }
21731 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21732 line.pop_back();
21733 }
21734
21735 lines.push(mem::take(&mut line));
21736 }
21737 }
21738 }
21739
21740 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21741 return;
21742 };
21743 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21744 }
21745
21746 pub fn open_context_menu(
21747 &mut self,
21748 _: &OpenContextMenu,
21749 window: &mut Window,
21750 cx: &mut Context<Self>,
21751 ) {
21752 self.request_autoscroll(Autoscroll::newest(), cx);
21753 let position = self
21754 .selections
21755 .newest_display(&self.display_snapshot(cx))
21756 .start;
21757 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21758 }
21759
21760 pub fn replay_insert_event(
21761 &mut self,
21762 text: &str,
21763 relative_utf16_range: Option<Range<isize>>,
21764 window: &mut Window,
21765 cx: &mut Context<Self>,
21766 ) {
21767 if !self.input_enabled {
21768 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21769 return;
21770 }
21771 if let Some(relative_utf16_range) = relative_utf16_range {
21772 let selections = self
21773 .selections
21774 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21775 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21776 let new_ranges = selections.into_iter().map(|range| {
21777 let start = OffsetUtf16(
21778 range
21779 .head()
21780 .0
21781 .saturating_add_signed(relative_utf16_range.start),
21782 );
21783 let end = OffsetUtf16(
21784 range
21785 .head()
21786 .0
21787 .saturating_add_signed(relative_utf16_range.end),
21788 );
21789 start..end
21790 });
21791 s.select_ranges(new_ranges);
21792 });
21793 }
21794
21795 self.handle_input(text, window, cx);
21796 }
21797
21798 pub fn is_focused(&self, window: &Window) -> bool {
21799 self.focus_handle.is_focused(window)
21800 }
21801
21802 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21803 cx.emit(EditorEvent::Focused);
21804
21805 if let Some(descendant) = self
21806 .last_focused_descendant
21807 .take()
21808 .and_then(|descendant| descendant.upgrade())
21809 {
21810 window.focus(&descendant);
21811 } else {
21812 if let Some(blame) = self.blame.as_ref() {
21813 blame.update(cx, GitBlame::focus)
21814 }
21815
21816 self.blink_manager.update(cx, BlinkManager::enable);
21817 self.show_cursor_names(window, cx);
21818 self.buffer.update(cx, |buffer, cx| {
21819 buffer.finalize_last_transaction(cx);
21820 if self.leader_id.is_none() {
21821 buffer.set_active_selections(
21822 &self.selections.disjoint_anchors_arc(),
21823 self.selections.line_mode(),
21824 self.cursor_shape,
21825 cx,
21826 );
21827 }
21828 });
21829 }
21830 }
21831
21832 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21833 cx.emit(EditorEvent::FocusedIn)
21834 }
21835
21836 fn handle_focus_out(
21837 &mut self,
21838 event: FocusOutEvent,
21839 _window: &mut Window,
21840 cx: &mut Context<Self>,
21841 ) {
21842 if event.blurred != self.focus_handle {
21843 self.last_focused_descendant = Some(event.blurred);
21844 }
21845 self.selection_drag_state = SelectionDragState::None;
21846 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21847 }
21848
21849 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21850 self.blink_manager.update(cx, BlinkManager::disable);
21851 self.buffer
21852 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21853
21854 if let Some(blame) = self.blame.as_ref() {
21855 blame.update(cx, GitBlame::blur)
21856 }
21857 if !self.hover_state.focused(window, cx) {
21858 hide_hover(self, cx);
21859 }
21860 if !self
21861 .context_menu
21862 .borrow()
21863 .as_ref()
21864 .is_some_and(|context_menu| context_menu.focused(window, cx))
21865 {
21866 self.hide_context_menu(window, cx);
21867 }
21868 self.take_active_edit_prediction(cx);
21869 cx.emit(EditorEvent::Blurred);
21870 cx.notify();
21871 }
21872
21873 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21874 let mut pending: String = window
21875 .pending_input_keystrokes()
21876 .into_iter()
21877 .flatten()
21878 .filter_map(|keystroke| {
21879 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21880 keystroke.key_char.clone()
21881 } else {
21882 None
21883 }
21884 })
21885 .collect();
21886
21887 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21888 pending = "".to_string();
21889 }
21890
21891 let existing_pending = self
21892 .text_highlights::<PendingInput>(cx)
21893 .map(|(_, ranges)| ranges.to_vec());
21894 if existing_pending.is_none() && pending.is_empty() {
21895 return;
21896 }
21897 let transaction =
21898 self.transact(window, cx, |this, window, cx| {
21899 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21900 let edits = selections
21901 .iter()
21902 .map(|selection| (selection.end..selection.end, pending.clone()));
21903 this.edit(edits, cx);
21904 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21905 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21906 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21907 }));
21908 });
21909 if let Some(existing_ranges) = existing_pending {
21910 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21911 this.edit(edits, cx);
21912 }
21913 });
21914
21915 let snapshot = self.snapshot(window, cx);
21916 let ranges = self
21917 .selections
21918 .all::<usize>(&snapshot.display_snapshot)
21919 .into_iter()
21920 .map(|selection| {
21921 snapshot.buffer_snapshot().anchor_after(selection.end)
21922 ..snapshot
21923 .buffer_snapshot()
21924 .anchor_before(selection.end + pending.len())
21925 })
21926 .collect();
21927
21928 if pending.is_empty() {
21929 self.clear_highlights::<PendingInput>(cx);
21930 } else {
21931 self.highlight_text::<PendingInput>(
21932 ranges,
21933 HighlightStyle {
21934 underline: Some(UnderlineStyle {
21935 thickness: px(1.),
21936 color: None,
21937 wavy: false,
21938 }),
21939 ..Default::default()
21940 },
21941 cx,
21942 );
21943 }
21944
21945 self.ime_transaction = self.ime_transaction.or(transaction);
21946 if let Some(transaction) = self.ime_transaction {
21947 self.buffer.update(cx, |buffer, cx| {
21948 buffer.group_until_transaction(transaction, cx);
21949 });
21950 }
21951
21952 if self.text_highlights::<PendingInput>(cx).is_none() {
21953 self.ime_transaction.take();
21954 }
21955 }
21956
21957 pub fn register_action_renderer(
21958 &mut self,
21959 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21960 ) -> Subscription {
21961 let id = self.next_editor_action_id.post_inc();
21962 self.editor_actions
21963 .borrow_mut()
21964 .insert(id, Box::new(listener));
21965
21966 let editor_actions = self.editor_actions.clone();
21967 Subscription::new(move || {
21968 editor_actions.borrow_mut().remove(&id);
21969 })
21970 }
21971
21972 pub fn register_action<A: Action>(
21973 &mut self,
21974 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21975 ) -> Subscription {
21976 let id = self.next_editor_action_id.post_inc();
21977 let listener = Arc::new(listener);
21978 self.editor_actions.borrow_mut().insert(
21979 id,
21980 Box::new(move |_, window, _| {
21981 let listener = listener.clone();
21982 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21983 let action = action.downcast_ref().unwrap();
21984 if phase == DispatchPhase::Bubble {
21985 listener(action, window, cx)
21986 }
21987 })
21988 }),
21989 );
21990
21991 let editor_actions = self.editor_actions.clone();
21992 Subscription::new(move || {
21993 editor_actions.borrow_mut().remove(&id);
21994 })
21995 }
21996
21997 pub fn file_header_size(&self) -> u32 {
21998 FILE_HEADER_HEIGHT
21999 }
22000
22001 pub fn restore(
22002 &mut self,
22003 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22004 window: &mut Window,
22005 cx: &mut Context<Self>,
22006 ) {
22007 let workspace = self.workspace();
22008 let project = self.project();
22009 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22010 let mut tasks = Vec::new();
22011 for (buffer_id, changes) in revert_changes {
22012 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22013 buffer.update(cx, |buffer, cx| {
22014 buffer.edit(
22015 changes
22016 .into_iter()
22017 .map(|(range, text)| (range, text.to_string())),
22018 None,
22019 cx,
22020 );
22021 });
22022
22023 if let Some(project) =
22024 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22025 {
22026 project.update(cx, |project, cx| {
22027 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22028 })
22029 }
22030 }
22031 }
22032 tasks
22033 });
22034 cx.spawn_in(window, async move |_, cx| {
22035 for (buffer, task) in save_tasks {
22036 let result = task.await;
22037 if result.is_err() {
22038 let Some(path) = buffer
22039 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22040 .ok()
22041 else {
22042 continue;
22043 };
22044 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22045 let Some(task) = cx
22046 .update_window_entity(workspace, |workspace, window, cx| {
22047 workspace
22048 .open_path_preview(path, None, false, false, false, window, cx)
22049 })
22050 .ok()
22051 else {
22052 continue;
22053 };
22054 task.await.log_err();
22055 }
22056 }
22057 }
22058 })
22059 .detach();
22060 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22061 selections.refresh()
22062 });
22063 }
22064
22065 pub fn to_pixel_point(
22066 &self,
22067 source: multi_buffer::Anchor,
22068 editor_snapshot: &EditorSnapshot,
22069 window: &mut Window,
22070 ) -> Option<gpui::Point<Pixels>> {
22071 let source_point = source.to_display_point(editor_snapshot);
22072 self.display_to_pixel_point(source_point, editor_snapshot, window)
22073 }
22074
22075 pub fn display_to_pixel_point(
22076 &self,
22077 source: DisplayPoint,
22078 editor_snapshot: &EditorSnapshot,
22079 window: &mut Window,
22080 ) -> Option<gpui::Point<Pixels>> {
22081 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22082 let text_layout_details = self.text_layout_details(window);
22083 let scroll_top = text_layout_details
22084 .scroll_anchor
22085 .scroll_position(editor_snapshot)
22086 .y;
22087
22088 if source.row().as_f64() < scroll_top.floor() {
22089 return None;
22090 }
22091 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22092 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22093 Some(gpui::Point::new(source_x, source_y))
22094 }
22095
22096 pub fn has_visible_completions_menu(&self) -> bool {
22097 !self.edit_prediction_preview_is_active()
22098 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22099 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22100 })
22101 }
22102
22103 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22104 if self.mode.is_minimap() {
22105 return;
22106 }
22107 self.addons
22108 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22109 }
22110
22111 pub fn unregister_addon<T: Addon>(&mut self) {
22112 self.addons.remove(&std::any::TypeId::of::<T>());
22113 }
22114
22115 pub fn addon<T: Addon>(&self) -> Option<&T> {
22116 let type_id = std::any::TypeId::of::<T>();
22117 self.addons
22118 .get(&type_id)
22119 .and_then(|item| item.to_any().downcast_ref::<T>())
22120 }
22121
22122 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22123 let type_id = std::any::TypeId::of::<T>();
22124 self.addons
22125 .get_mut(&type_id)
22126 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22127 }
22128
22129 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22130 let text_layout_details = self.text_layout_details(window);
22131 let style = &text_layout_details.editor_style;
22132 let font_id = window.text_system().resolve_font(&style.text.font());
22133 let font_size = style.text.font_size.to_pixels(window.rem_size());
22134 let line_height = style.text.line_height_in_pixels(window.rem_size());
22135 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22136 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22137
22138 CharacterDimensions {
22139 em_width,
22140 em_advance,
22141 line_height,
22142 }
22143 }
22144
22145 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22146 self.load_diff_task.clone()
22147 }
22148
22149 fn read_metadata_from_db(
22150 &mut self,
22151 item_id: u64,
22152 workspace_id: WorkspaceId,
22153 window: &mut Window,
22154 cx: &mut Context<Editor>,
22155 ) {
22156 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22157 && !self.mode.is_minimap()
22158 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22159 {
22160 let buffer_snapshot = OnceCell::new();
22161
22162 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22163 && !folds.is_empty()
22164 {
22165 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22166 self.fold_ranges(
22167 folds
22168 .into_iter()
22169 .map(|(start, end)| {
22170 snapshot.clip_offset(start, Bias::Left)
22171 ..snapshot.clip_offset(end, Bias::Right)
22172 })
22173 .collect(),
22174 false,
22175 window,
22176 cx,
22177 );
22178 }
22179
22180 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22181 && !selections.is_empty()
22182 {
22183 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22184 // skip adding the initial selection to selection history
22185 self.selection_history.mode = SelectionHistoryMode::Skipping;
22186 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22187 s.select_ranges(selections.into_iter().map(|(start, end)| {
22188 snapshot.clip_offset(start, Bias::Left)
22189 ..snapshot.clip_offset(end, Bias::Right)
22190 }));
22191 });
22192 self.selection_history.mode = SelectionHistoryMode::Normal;
22193 };
22194 }
22195
22196 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22197 }
22198
22199 fn update_lsp_data(
22200 &mut self,
22201 for_buffer: Option<BufferId>,
22202 window: &mut Window,
22203 cx: &mut Context<'_, Self>,
22204 ) {
22205 self.pull_diagnostics(for_buffer, window, cx);
22206 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22207 }
22208
22209 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22210 if self.ignore_lsp_data() {
22211 return;
22212 }
22213 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22214 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22215 }
22216 }
22217
22218 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22219 if self.ignore_lsp_data() {
22220 return;
22221 }
22222
22223 if !self.registered_buffers.contains_key(&buffer_id)
22224 && let Some(project) = self.project.as_ref()
22225 {
22226 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22227 project.update(cx, |project, cx| {
22228 self.registered_buffers.insert(
22229 buffer_id,
22230 project.register_buffer_with_language_servers(&buffer, cx),
22231 );
22232 });
22233 } else {
22234 self.registered_buffers.remove(&buffer_id);
22235 }
22236 }
22237 }
22238
22239 fn ignore_lsp_data(&self) -> bool {
22240 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22241 // skip any LSP updates for it.
22242 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22243 }
22244}
22245
22246fn edit_for_markdown_paste<'a>(
22247 buffer: &MultiBufferSnapshot,
22248 range: Range<usize>,
22249 to_insert: &'a str,
22250 url: Option<url::Url>,
22251) -> (Range<usize>, Cow<'a, str>) {
22252 if url.is_none() {
22253 return (range, Cow::Borrowed(to_insert));
22254 };
22255
22256 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22257
22258 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22259 Cow::Borrowed(to_insert)
22260 } else {
22261 Cow::Owned(format!("[{old_text}]({to_insert})"))
22262 };
22263 (range, new_text)
22264}
22265
22266#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
22267pub enum VimFlavor {
22268 Vim,
22269 Helix,
22270}
22271
22272pub fn vim_flavor(cx: &App) -> Option<VimFlavor> {
22273 if vim_mode_setting::HelixModeSetting::try_get(cx)
22274 .map(|helix_mode| helix_mode.0)
22275 .unwrap_or(false)
22276 {
22277 Some(VimFlavor::Helix)
22278 } else if vim_mode_setting::VimModeSetting::try_get(cx)
22279 .map(|vim_mode| vim_mode.0)
22280 .unwrap_or(false)
22281 {
22282 Some(VimFlavor::Vim)
22283 } else {
22284 None // neither vim nor helix mode
22285 }
22286}
22287
22288fn process_completion_for_edit(
22289 completion: &Completion,
22290 intent: CompletionIntent,
22291 buffer: &Entity<Buffer>,
22292 cursor_position: &text::Anchor,
22293 cx: &mut Context<Editor>,
22294) -> CompletionEdit {
22295 let buffer = buffer.read(cx);
22296 let buffer_snapshot = buffer.snapshot();
22297 let (snippet, new_text) = if completion.is_snippet() {
22298 let mut snippet_source = completion.new_text.clone();
22299 // Workaround for typescript language server issues so that methods don't expand within
22300 // strings and functions with type expressions. The previous point is used because the query
22301 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22302 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22303 let previous_point = if previous_point.column > 0 {
22304 cursor_position.to_previous_offset(&buffer_snapshot)
22305 } else {
22306 cursor_position.to_offset(&buffer_snapshot)
22307 };
22308 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22309 && scope.prefers_label_for_snippet_in_completion()
22310 && let Some(label) = completion.label()
22311 && matches!(
22312 completion.kind(),
22313 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22314 )
22315 {
22316 snippet_source = label;
22317 }
22318 match Snippet::parse(&snippet_source).log_err() {
22319 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22320 None => (None, completion.new_text.clone()),
22321 }
22322 } else {
22323 (None, completion.new_text.clone())
22324 };
22325
22326 let mut range_to_replace = {
22327 let replace_range = &completion.replace_range;
22328 if let CompletionSource::Lsp {
22329 insert_range: Some(insert_range),
22330 ..
22331 } = &completion.source
22332 {
22333 debug_assert_eq!(
22334 insert_range.start, replace_range.start,
22335 "insert_range and replace_range should start at the same position"
22336 );
22337 debug_assert!(
22338 insert_range
22339 .start
22340 .cmp(cursor_position, &buffer_snapshot)
22341 .is_le(),
22342 "insert_range should start before or at cursor position"
22343 );
22344 debug_assert!(
22345 replace_range
22346 .start
22347 .cmp(cursor_position, &buffer_snapshot)
22348 .is_le(),
22349 "replace_range should start before or at cursor position"
22350 );
22351
22352 let should_replace = match intent {
22353 CompletionIntent::CompleteWithInsert => false,
22354 CompletionIntent::CompleteWithReplace => true,
22355 CompletionIntent::Complete | CompletionIntent::Compose => {
22356 let insert_mode =
22357 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22358 .completions
22359 .lsp_insert_mode;
22360 match insert_mode {
22361 LspInsertMode::Insert => false,
22362 LspInsertMode::Replace => true,
22363 LspInsertMode::ReplaceSubsequence => {
22364 let mut text_to_replace = buffer.chars_for_range(
22365 buffer.anchor_before(replace_range.start)
22366 ..buffer.anchor_after(replace_range.end),
22367 );
22368 let mut current_needle = text_to_replace.next();
22369 for haystack_ch in completion.label.text.chars() {
22370 if let Some(needle_ch) = current_needle
22371 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22372 {
22373 current_needle = text_to_replace.next();
22374 }
22375 }
22376 current_needle.is_none()
22377 }
22378 LspInsertMode::ReplaceSuffix => {
22379 if replace_range
22380 .end
22381 .cmp(cursor_position, &buffer_snapshot)
22382 .is_gt()
22383 {
22384 let range_after_cursor = *cursor_position..replace_range.end;
22385 let text_after_cursor = buffer
22386 .text_for_range(
22387 buffer.anchor_before(range_after_cursor.start)
22388 ..buffer.anchor_after(range_after_cursor.end),
22389 )
22390 .collect::<String>()
22391 .to_ascii_lowercase();
22392 completion
22393 .label
22394 .text
22395 .to_ascii_lowercase()
22396 .ends_with(&text_after_cursor)
22397 } else {
22398 true
22399 }
22400 }
22401 }
22402 }
22403 };
22404
22405 if should_replace {
22406 replace_range.clone()
22407 } else {
22408 insert_range.clone()
22409 }
22410 } else {
22411 replace_range.clone()
22412 }
22413 };
22414
22415 if range_to_replace
22416 .end
22417 .cmp(cursor_position, &buffer_snapshot)
22418 .is_lt()
22419 {
22420 range_to_replace.end = *cursor_position;
22421 }
22422
22423 CompletionEdit {
22424 new_text,
22425 replace_range: range_to_replace.to_offset(buffer),
22426 snippet,
22427 }
22428}
22429
22430struct CompletionEdit {
22431 new_text: String,
22432 replace_range: Range<usize>,
22433 snippet: Option<Snippet>,
22434}
22435
22436fn insert_extra_newline_brackets(
22437 buffer: &MultiBufferSnapshot,
22438 range: Range<usize>,
22439 language: &language::LanguageScope,
22440) -> bool {
22441 let leading_whitespace_len = buffer
22442 .reversed_chars_at(range.start)
22443 .take_while(|c| c.is_whitespace() && *c != '\n')
22444 .map(|c| c.len_utf8())
22445 .sum::<usize>();
22446 let trailing_whitespace_len = buffer
22447 .chars_at(range.end)
22448 .take_while(|c| c.is_whitespace() && *c != '\n')
22449 .map(|c| c.len_utf8())
22450 .sum::<usize>();
22451 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22452
22453 language.brackets().any(|(pair, enabled)| {
22454 let pair_start = pair.start.trim_end();
22455 let pair_end = pair.end.trim_start();
22456
22457 enabled
22458 && pair.newline
22459 && buffer.contains_str_at(range.end, pair_end)
22460 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22461 })
22462}
22463
22464fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22465 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22466 [(buffer, range, _)] => (*buffer, range.clone()),
22467 _ => return false,
22468 };
22469 let pair = {
22470 let mut result: Option<BracketMatch> = None;
22471
22472 for pair in buffer
22473 .all_bracket_ranges(range.clone())
22474 .filter(move |pair| {
22475 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22476 })
22477 {
22478 let len = pair.close_range.end - pair.open_range.start;
22479
22480 if let Some(existing) = &result {
22481 let existing_len = existing.close_range.end - existing.open_range.start;
22482 if len > existing_len {
22483 continue;
22484 }
22485 }
22486
22487 result = Some(pair);
22488 }
22489
22490 result
22491 };
22492 let Some(pair) = pair else {
22493 return false;
22494 };
22495 pair.newline_only
22496 && buffer
22497 .chars_for_range(pair.open_range.end..range.start)
22498 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22499 .all(|c| c.is_whitespace() && c != '\n')
22500}
22501
22502fn update_uncommitted_diff_for_buffer(
22503 editor: Entity<Editor>,
22504 project: &Entity<Project>,
22505 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22506 buffer: Entity<MultiBuffer>,
22507 cx: &mut App,
22508) -> Task<()> {
22509 let mut tasks = Vec::new();
22510 project.update(cx, |project, cx| {
22511 for buffer in buffers {
22512 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22513 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22514 }
22515 }
22516 });
22517 cx.spawn(async move |cx| {
22518 let diffs = future::join_all(tasks).await;
22519 if editor
22520 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22521 .unwrap_or(false)
22522 {
22523 return;
22524 }
22525
22526 buffer
22527 .update(cx, |buffer, cx| {
22528 for diff in diffs.into_iter().flatten() {
22529 buffer.add_diff(diff, cx);
22530 }
22531 })
22532 .ok();
22533 })
22534}
22535
22536fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22537 let tab_size = tab_size.get() as usize;
22538 let mut width = offset;
22539
22540 for ch in text.chars() {
22541 width += if ch == '\t' {
22542 tab_size - (width % tab_size)
22543 } else {
22544 1
22545 };
22546 }
22547
22548 width - offset
22549}
22550
22551#[cfg(test)]
22552mod tests {
22553 use super::*;
22554
22555 #[test]
22556 fn test_string_size_with_expanded_tabs() {
22557 let nz = |val| NonZeroU32::new(val).unwrap();
22558 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22559 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22560 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22561 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22562 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22563 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22564 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22565 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22566 }
22567}
22568
22569/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22570struct WordBreakingTokenizer<'a> {
22571 input: &'a str,
22572}
22573
22574impl<'a> WordBreakingTokenizer<'a> {
22575 fn new(input: &'a str) -> Self {
22576 Self { input }
22577 }
22578}
22579
22580fn is_char_ideographic(ch: char) -> bool {
22581 use unicode_script::Script::*;
22582 use unicode_script::UnicodeScript;
22583 matches!(ch.script(), Han | Tangut | Yi)
22584}
22585
22586fn is_grapheme_ideographic(text: &str) -> bool {
22587 text.chars().any(is_char_ideographic)
22588}
22589
22590fn is_grapheme_whitespace(text: &str) -> bool {
22591 text.chars().any(|x| x.is_whitespace())
22592}
22593
22594fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22595 text.chars()
22596 .next()
22597 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22598}
22599
22600#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22601enum WordBreakToken<'a> {
22602 Word { token: &'a str, grapheme_len: usize },
22603 InlineWhitespace { token: &'a str, grapheme_len: usize },
22604 Newline,
22605}
22606
22607impl<'a> Iterator for WordBreakingTokenizer<'a> {
22608 /// Yields a span, the count of graphemes in the token, and whether it was
22609 /// whitespace. Note that it also breaks at word boundaries.
22610 type Item = WordBreakToken<'a>;
22611
22612 fn next(&mut self) -> Option<Self::Item> {
22613 use unicode_segmentation::UnicodeSegmentation;
22614 if self.input.is_empty() {
22615 return None;
22616 }
22617
22618 let mut iter = self.input.graphemes(true).peekable();
22619 let mut offset = 0;
22620 let mut grapheme_len = 0;
22621 if let Some(first_grapheme) = iter.next() {
22622 let is_newline = first_grapheme == "\n";
22623 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22624 offset += first_grapheme.len();
22625 grapheme_len += 1;
22626 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22627 if let Some(grapheme) = iter.peek().copied()
22628 && should_stay_with_preceding_ideograph(grapheme)
22629 {
22630 offset += grapheme.len();
22631 grapheme_len += 1;
22632 }
22633 } else {
22634 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22635 let mut next_word_bound = words.peek().copied();
22636 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22637 next_word_bound = words.next();
22638 }
22639 while let Some(grapheme) = iter.peek().copied() {
22640 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22641 break;
22642 };
22643 if is_grapheme_whitespace(grapheme) != is_whitespace
22644 || (grapheme == "\n") != is_newline
22645 {
22646 break;
22647 };
22648 offset += grapheme.len();
22649 grapheme_len += 1;
22650 iter.next();
22651 }
22652 }
22653 let token = &self.input[..offset];
22654 self.input = &self.input[offset..];
22655 if token == "\n" {
22656 Some(WordBreakToken::Newline)
22657 } else if is_whitespace {
22658 Some(WordBreakToken::InlineWhitespace {
22659 token,
22660 grapheme_len,
22661 })
22662 } else {
22663 Some(WordBreakToken::Word {
22664 token,
22665 grapheme_len,
22666 })
22667 }
22668 } else {
22669 None
22670 }
22671 }
22672}
22673
22674#[test]
22675fn test_word_breaking_tokenizer() {
22676 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22677 ("", &[]),
22678 (" ", &[whitespace(" ", 2)]),
22679 ("Ʒ", &[word("Ʒ", 1)]),
22680 ("Ǽ", &[word("Ǽ", 1)]),
22681 ("⋑", &[word("⋑", 1)]),
22682 ("⋑⋑", &[word("⋑⋑", 2)]),
22683 (
22684 "原理,进而",
22685 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22686 ),
22687 (
22688 "hello world",
22689 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22690 ),
22691 (
22692 "hello, world",
22693 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22694 ),
22695 (
22696 " hello world",
22697 &[
22698 whitespace(" ", 2),
22699 word("hello", 5),
22700 whitespace(" ", 1),
22701 word("world", 5),
22702 ],
22703 ),
22704 (
22705 "这是什么 \n 钢笔",
22706 &[
22707 word("这", 1),
22708 word("是", 1),
22709 word("什", 1),
22710 word("么", 1),
22711 whitespace(" ", 1),
22712 newline(),
22713 whitespace(" ", 1),
22714 word("钢", 1),
22715 word("笔", 1),
22716 ],
22717 ),
22718 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22719 ];
22720
22721 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22722 WordBreakToken::Word {
22723 token,
22724 grapheme_len,
22725 }
22726 }
22727
22728 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22729 WordBreakToken::InlineWhitespace {
22730 token,
22731 grapheme_len,
22732 }
22733 }
22734
22735 fn newline() -> WordBreakToken<'static> {
22736 WordBreakToken::Newline
22737 }
22738
22739 for (input, result) in tests {
22740 assert_eq!(
22741 WordBreakingTokenizer::new(input)
22742 .collect::<Vec<_>>()
22743 .as_slice(),
22744 *result,
22745 );
22746 }
22747}
22748
22749fn wrap_with_prefix(
22750 first_line_prefix: String,
22751 subsequent_lines_prefix: String,
22752 unwrapped_text: String,
22753 wrap_column: usize,
22754 tab_size: NonZeroU32,
22755 preserve_existing_whitespace: bool,
22756) -> String {
22757 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22758 let subsequent_lines_prefix_len =
22759 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22760 let mut wrapped_text = String::new();
22761 let mut current_line = first_line_prefix;
22762 let mut is_first_line = true;
22763
22764 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22765 let mut current_line_len = first_line_prefix_len;
22766 let mut in_whitespace = false;
22767 for token in tokenizer {
22768 let have_preceding_whitespace = in_whitespace;
22769 match token {
22770 WordBreakToken::Word {
22771 token,
22772 grapheme_len,
22773 } => {
22774 in_whitespace = false;
22775 let current_prefix_len = if is_first_line {
22776 first_line_prefix_len
22777 } else {
22778 subsequent_lines_prefix_len
22779 };
22780 if current_line_len + grapheme_len > wrap_column
22781 && current_line_len != current_prefix_len
22782 {
22783 wrapped_text.push_str(current_line.trim_end());
22784 wrapped_text.push('\n');
22785 is_first_line = false;
22786 current_line = subsequent_lines_prefix.clone();
22787 current_line_len = subsequent_lines_prefix_len;
22788 }
22789 current_line.push_str(token);
22790 current_line_len += grapheme_len;
22791 }
22792 WordBreakToken::InlineWhitespace {
22793 mut token,
22794 mut grapheme_len,
22795 } => {
22796 in_whitespace = true;
22797 if have_preceding_whitespace && !preserve_existing_whitespace {
22798 continue;
22799 }
22800 if !preserve_existing_whitespace {
22801 // Keep a single whitespace grapheme as-is
22802 if let Some(first) =
22803 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22804 {
22805 token = first;
22806 } else {
22807 token = " ";
22808 }
22809 grapheme_len = 1;
22810 }
22811 let current_prefix_len = if is_first_line {
22812 first_line_prefix_len
22813 } else {
22814 subsequent_lines_prefix_len
22815 };
22816 if current_line_len + grapheme_len > wrap_column {
22817 wrapped_text.push_str(current_line.trim_end());
22818 wrapped_text.push('\n');
22819 is_first_line = false;
22820 current_line = subsequent_lines_prefix.clone();
22821 current_line_len = subsequent_lines_prefix_len;
22822 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22823 current_line.push_str(token);
22824 current_line_len += grapheme_len;
22825 }
22826 }
22827 WordBreakToken::Newline => {
22828 in_whitespace = true;
22829 let current_prefix_len = if is_first_line {
22830 first_line_prefix_len
22831 } else {
22832 subsequent_lines_prefix_len
22833 };
22834 if preserve_existing_whitespace {
22835 wrapped_text.push_str(current_line.trim_end());
22836 wrapped_text.push('\n');
22837 is_first_line = false;
22838 current_line = subsequent_lines_prefix.clone();
22839 current_line_len = subsequent_lines_prefix_len;
22840 } else if have_preceding_whitespace {
22841 continue;
22842 } else if current_line_len + 1 > wrap_column
22843 && current_line_len != current_prefix_len
22844 {
22845 wrapped_text.push_str(current_line.trim_end());
22846 wrapped_text.push('\n');
22847 is_first_line = false;
22848 current_line = subsequent_lines_prefix.clone();
22849 current_line_len = subsequent_lines_prefix_len;
22850 } else if current_line_len != current_prefix_len {
22851 current_line.push(' ');
22852 current_line_len += 1;
22853 }
22854 }
22855 }
22856 }
22857
22858 if !current_line.is_empty() {
22859 wrapped_text.push_str(¤t_line);
22860 }
22861 wrapped_text
22862}
22863
22864#[test]
22865fn test_wrap_with_prefix() {
22866 assert_eq!(
22867 wrap_with_prefix(
22868 "# ".to_string(),
22869 "# ".to_string(),
22870 "abcdefg".to_string(),
22871 4,
22872 NonZeroU32::new(4).unwrap(),
22873 false,
22874 ),
22875 "# abcdefg"
22876 );
22877 assert_eq!(
22878 wrap_with_prefix(
22879 "".to_string(),
22880 "".to_string(),
22881 "\thello world".to_string(),
22882 8,
22883 NonZeroU32::new(4).unwrap(),
22884 false,
22885 ),
22886 "hello\nworld"
22887 );
22888 assert_eq!(
22889 wrap_with_prefix(
22890 "// ".to_string(),
22891 "// ".to_string(),
22892 "xx \nyy zz aa bb cc".to_string(),
22893 12,
22894 NonZeroU32::new(4).unwrap(),
22895 false,
22896 ),
22897 "// xx yy zz\n// aa bb cc"
22898 );
22899 assert_eq!(
22900 wrap_with_prefix(
22901 String::new(),
22902 String::new(),
22903 "这是什么 \n 钢笔".to_string(),
22904 3,
22905 NonZeroU32::new(4).unwrap(),
22906 false,
22907 ),
22908 "这是什\n么 钢\n笔"
22909 );
22910 assert_eq!(
22911 wrap_with_prefix(
22912 String::new(),
22913 String::new(),
22914 format!("foo{}bar", '\u{2009}'), // thin space
22915 80,
22916 NonZeroU32::new(4).unwrap(),
22917 false,
22918 ),
22919 format!("foo{}bar", '\u{2009}')
22920 );
22921}
22922
22923pub trait CollaborationHub {
22924 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22925 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22926 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22927}
22928
22929impl CollaborationHub for Entity<Project> {
22930 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22931 self.read(cx).collaborators()
22932 }
22933
22934 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22935 self.read(cx).user_store().read(cx).participant_indices()
22936 }
22937
22938 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22939 let this = self.read(cx);
22940 let user_ids = this.collaborators().values().map(|c| c.user_id);
22941 this.user_store().read(cx).participant_names(user_ids, cx)
22942 }
22943}
22944
22945pub trait SemanticsProvider {
22946 fn hover(
22947 &self,
22948 buffer: &Entity<Buffer>,
22949 position: text::Anchor,
22950 cx: &mut App,
22951 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22952
22953 fn inline_values(
22954 &self,
22955 buffer_handle: Entity<Buffer>,
22956 range: Range<text::Anchor>,
22957 cx: &mut App,
22958 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22959
22960 fn applicable_inlay_chunks(
22961 &self,
22962 buffer: &Entity<Buffer>,
22963 ranges: &[Range<text::Anchor>],
22964 cx: &mut App,
22965 ) -> Vec<Range<BufferRow>>;
22966
22967 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
22968
22969 fn inlay_hints(
22970 &self,
22971 invalidate: InvalidationStrategy,
22972 buffer: Entity<Buffer>,
22973 ranges: Vec<Range<text::Anchor>>,
22974 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
22975 cx: &mut App,
22976 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
22977
22978 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22979
22980 fn document_highlights(
22981 &self,
22982 buffer: &Entity<Buffer>,
22983 position: text::Anchor,
22984 cx: &mut App,
22985 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22986
22987 fn definitions(
22988 &self,
22989 buffer: &Entity<Buffer>,
22990 position: text::Anchor,
22991 kind: GotoDefinitionKind,
22992 cx: &mut App,
22993 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22994
22995 fn range_for_rename(
22996 &self,
22997 buffer: &Entity<Buffer>,
22998 position: text::Anchor,
22999 cx: &mut App,
23000 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23001
23002 fn perform_rename(
23003 &self,
23004 buffer: &Entity<Buffer>,
23005 position: text::Anchor,
23006 new_name: String,
23007 cx: &mut App,
23008 ) -> Option<Task<Result<ProjectTransaction>>>;
23009}
23010
23011pub trait CompletionProvider {
23012 fn completions(
23013 &self,
23014 excerpt_id: ExcerptId,
23015 buffer: &Entity<Buffer>,
23016 buffer_position: text::Anchor,
23017 trigger: CompletionContext,
23018 window: &mut Window,
23019 cx: &mut Context<Editor>,
23020 ) -> Task<Result<Vec<CompletionResponse>>>;
23021
23022 fn resolve_completions(
23023 &self,
23024 _buffer: Entity<Buffer>,
23025 _completion_indices: Vec<usize>,
23026 _completions: Rc<RefCell<Box<[Completion]>>>,
23027 _cx: &mut Context<Editor>,
23028 ) -> Task<Result<bool>> {
23029 Task::ready(Ok(false))
23030 }
23031
23032 fn apply_additional_edits_for_completion(
23033 &self,
23034 _buffer: Entity<Buffer>,
23035 _completions: Rc<RefCell<Box<[Completion]>>>,
23036 _completion_index: usize,
23037 _push_to_history: bool,
23038 _cx: &mut Context<Editor>,
23039 ) -> Task<Result<Option<language::Transaction>>> {
23040 Task::ready(Ok(None))
23041 }
23042
23043 fn is_completion_trigger(
23044 &self,
23045 buffer: &Entity<Buffer>,
23046 position: language::Anchor,
23047 text: &str,
23048 trigger_in_words: bool,
23049 menu_is_open: bool,
23050 cx: &mut Context<Editor>,
23051 ) -> bool;
23052
23053 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23054
23055 fn sort_completions(&self) -> bool {
23056 true
23057 }
23058
23059 fn filter_completions(&self) -> bool {
23060 true
23061 }
23062}
23063
23064pub trait CodeActionProvider {
23065 fn id(&self) -> Arc<str>;
23066
23067 fn code_actions(
23068 &self,
23069 buffer: &Entity<Buffer>,
23070 range: Range<text::Anchor>,
23071 window: &mut Window,
23072 cx: &mut App,
23073 ) -> Task<Result<Vec<CodeAction>>>;
23074
23075 fn apply_code_action(
23076 &self,
23077 buffer_handle: Entity<Buffer>,
23078 action: CodeAction,
23079 excerpt_id: ExcerptId,
23080 push_to_history: bool,
23081 window: &mut Window,
23082 cx: &mut App,
23083 ) -> Task<Result<ProjectTransaction>>;
23084}
23085
23086impl CodeActionProvider for Entity<Project> {
23087 fn id(&self) -> Arc<str> {
23088 "project".into()
23089 }
23090
23091 fn code_actions(
23092 &self,
23093 buffer: &Entity<Buffer>,
23094 range: Range<text::Anchor>,
23095 _window: &mut Window,
23096 cx: &mut App,
23097 ) -> Task<Result<Vec<CodeAction>>> {
23098 self.update(cx, |project, cx| {
23099 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23100 let code_actions = project.code_actions(buffer, range, None, cx);
23101 cx.background_spawn(async move {
23102 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23103 Ok(code_lens_actions
23104 .context("code lens fetch")?
23105 .into_iter()
23106 .flatten()
23107 .chain(
23108 code_actions
23109 .context("code action fetch")?
23110 .into_iter()
23111 .flatten(),
23112 )
23113 .collect())
23114 })
23115 })
23116 }
23117
23118 fn apply_code_action(
23119 &self,
23120 buffer_handle: Entity<Buffer>,
23121 action: CodeAction,
23122 _excerpt_id: ExcerptId,
23123 push_to_history: bool,
23124 _window: &mut Window,
23125 cx: &mut App,
23126 ) -> Task<Result<ProjectTransaction>> {
23127 self.update(cx, |project, cx| {
23128 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23129 })
23130 }
23131}
23132
23133fn snippet_completions(
23134 project: &Project,
23135 buffer: &Entity<Buffer>,
23136 buffer_position: text::Anchor,
23137 cx: &mut App,
23138) -> Task<Result<CompletionResponse>> {
23139 let languages = buffer.read(cx).languages_at(buffer_position);
23140 let snippet_store = project.snippets().read(cx);
23141
23142 let scopes: Vec<_> = languages
23143 .iter()
23144 .filter_map(|language| {
23145 let language_name = language.lsp_id();
23146 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23147
23148 if snippets.is_empty() {
23149 None
23150 } else {
23151 Some((language.default_scope(), snippets))
23152 }
23153 })
23154 .collect();
23155
23156 if scopes.is_empty() {
23157 return Task::ready(Ok(CompletionResponse {
23158 completions: vec![],
23159 display_options: CompletionDisplayOptions::default(),
23160 is_incomplete: false,
23161 }));
23162 }
23163
23164 let snapshot = buffer.read(cx).text_snapshot();
23165 let executor = cx.background_executor().clone();
23166
23167 cx.background_spawn(async move {
23168 let mut is_incomplete = false;
23169 let mut completions: Vec<Completion> = Vec::new();
23170 for (scope, snippets) in scopes.into_iter() {
23171 let classifier =
23172 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
23173
23174 const MAX_WORD_PREFIX_LEN: usize = 128;
23175 let last_word: String = snapshot
23176 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
23177 .take(MAX_WORD_PREFIX_LEN)
23178 .take_while(|c| classifier.is_word(*c))
23179 .collect::<String>()
23180 .chars()
23181 .rev()
23182 .collect();
23183
23184 if last_word.is_empty() {
23185 return Ok(CompletionResponse {
23186 completions: vec![],
23187 display_options: CompletionDisplayOptions::default(),
23188 is_incomplete: true,
23189 });
23190 }
23191
23192 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
23193 let to_lsp = |point: &text::Anchor| {
23194 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23195 point_to_lsp(end)
23196 };
23197 let lsp_end = to_lsp(&buffer_position);
23198
23199 let candidates = snippets
23200 .iter()
23201 .enumerate()
23202 .flat_map(|(ix, snippet)| {
23203 snippet
23204 .prefix
23205 .iter()
23206 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23207 })
23208 .collect::<Vec<StringMatchCandidate>>();
23209
23210 const MAX_RESULTS: usize = 100;
23211 let mut matches = fuzzy::match_strings(
23212 &candidates,
23213 &last_word,
23214 last_word.chars().any(|c| c.is_uppercase()),
23215 true,
23216 MAX_RESULTS,
23217 &Default::default(),
23218 executor.clone(),
23219 )
23220 .await;
23221
23222 if matches.len() >= MAX_RESULTS {
23223 is_incomplete = true;
23224 }
23225
23226 // Remove all candidates where the query's start does not match the start of any word in the candidate
23227 if let Some(query_start) = last_word.chars().next() {
23228 matches.retain(|string_match| {
23229 split_words(&string_match.string).any(|word| {
23230 // Check that the first codepoint of the word as lowercase matches the first
23231 // codepoint of the query as lowercase
23232 word.chars()
23233 .flat_map(|codepoint| codepoint.to_lowercase())
23234 .zip(query_start.to_lowercase())
23235 .all(|(word_cp, query_cp)| word_cp == query_cp)
23236 })
23237 });
23238 }
23239
23240 let matched_strings = matches
23241 .into_iter()
23242 .map(|m| m.string)
23243 .collect::<HashSet<_>>();
23244
23245 completions.extend(snippets.iter().filter_map(|snippet| {
23246 let matching_prefix = snippet
23247 .prefix
23248 .iter()
23249 .find(|prefix| matched_strings.contains(*prefix))?;
23250 let start = as_offset - last_word.len();
23251 let start = snapshot.anchor_before(start);
23252 let range = start..buffer_position;
23253 let lsp_start = to_lsp(&start);
23254 let lsp_range = lsp::Range {
23255 start: lsp_start,
23256 end: lsp_end,
23257 };
23258 Some(Completion {
23259 replace_range: range,
23260 new_text: snippet.body.clone(),
23261 source: CompletionSource::Lsp {
23262 insert_range: None,
23263 server_id: LanguageServerId(usize::MAX),
23264 resolved: true,
23265 lsp_completion: Box::new(lsp::CompletionItem {
23266 label: snippet.prefix.first().unwrap().clone(),
23267 kind: Some(CompletionItemKind::SNIPPET),
23268 label_details: snippet.description.as_ref().map(|description| {
23269 lsp::CompletionItemLabelDetails {
23270 detail: Some(description.clone()),
23271 description: None,
23272 }
23273 }),
23274 insert_text_format: Some(InsertTextFormat::SNIPPET),
23275 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23276 lsp::InsertReplaceEdit {
23277 new_text: snippet.body.clone(),
23278 insert: lsp_range,
23279 replace: lsp_range,
23280 },
23281 )),
23282 filter_text: Some(snippet.body.clone()),
23283 sort_text: Some(char::MAX.to_string()),
23284 ..lsp::CompletionItem::default()
23285 }),
23286 lsp_defaults: None,
23287 },
23288 label: CodeLabel::plain(matching_prefix.clone(), None),
23289 icon_path: None,
23290 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23291 single_line: snippet.name.clone().into(),
23292 plain_text: snippet
23293 .description
23294 .clone()
23295 .map(|description| description.into()),
23296 }),
23297 insert_text_mode: None,
23298 confirm: None,
23299 })
23300 }))
23301 }
23302
23303 Ok(CompletionResponse {
23304 completions,
23305 display_options: CompletionDisplayOptions::default(),
23306 is_incomplete,
23307 })
23308 })
23309}
23310
23311impl CompletionProvider for Entity<Project> {
23312 fn completions(
23313 &self,
23314 _excerpt_id: ExcerptId,
23315 buffer: &Entity<Buffer>,
23316 buffer_position: text::Anchor,
23317 options: CompletionContext,
23318 _window: &mut Window,
23319 cx: &mut Context<Editor>,
23320 ) -> Task<Result<Vec<CompletionResponse>>> {
23321 self.update(cx, |project, cx| {
23322 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23323 let project_completions = project.completions(buffer, buffer_position, options, cx);
23324 cx.background_spawn(async move {
23325 let mut responses = project_completions.await?;
23326 let snippets = snippets.await?;
23327 if !snippets.completions.is_empty() {
23328 responses.push(snippets);
23329 }
23330 Ok(responses)
23331 })
23332 })
23333 }
23334
23335 fn resolve_completions(
23336 &self,
23337 buffer: Entity<Buffer>,
23338 completion_indices: Vec<usize>,
23339 completions: Rc<RefCell<Box<[Completion]>>>,
23340 cx: &mut Context<Editor>,
23341 ) -> Task<Result<bool>> {
23342 self.update(cx, |project, cx| {
23343 project.lsp_store().update(cx, |lsp_store, cx| {
23344 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23345 })
23346 })
23347 }
23348
23349 fn apply_additional_edits_for_completion(
23350 &self,
23351 buffer: Entity<Buffer>,
23352 completions: Rc<RefCell<Box<[Completion]>>>,
23353 completion_index: usize,
23354 push_to_history: bool,
23355 cx: &mut Context<Editor>,
23356 ) -> Task<Result<Option<language::Transaction>>> {
23357 self.update(cx, |project, cx| {
23358 project.lsp_store().update(cx, |lsp_store, cx| {
23359 lsp_store.apply_additional_edits_for_completion(
23360 buffer,
23361 completions,
23362 completion_index,
23363 push_to_history,
23364 cx,
23365 )
23366 })
23367 })
23368 }
23369
23370 fn is_completion_trigger(
23371 &self,
23372 buffer: &Entity<Buffer>,
23373 position: language::Anchor,
23374 text: &str,
23375 trigger_in_words: bool,
23376 menu_is_open: bool,
23377 cx: &mut Context<Editor>,
23378 ) -> bool {
23379 let mut chars = text.chars();
23380 let char = if let Some(char) = chars.next() {
23381 char
23382 } else {
23383 return false;
23384 };
23385 if chars.next().is_some() {
23386 return false;
23387 }
23388
23389 let buffer = buffer.read(cx);
23390 let snapshot = buffer.snapshot();
23391 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23392 return false;
23393 }
23394 let classifier = snapshot
23395 .char_classifier_at(position)
23396 .scope_context(Some(CharScopeContext::Completion));
23397 if trigger_in_words && classifier.is_word(char) {
23398 return true;
23399 }
23400
23401 buffer.completion_triggers().contains(text)
23402 }
23403}
23404
23405impl SemanticsProvider for Entity<Project> {
23406 fn hover(
23407 &self,
23408 buffer: &Entity<Buffer>,
23409 position: text::Anchor,
23410 cx: &mut App,
23411 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23412 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23413 }
23414
23415 fn document_highlights(
23416 &self,
23417 buffer: &Entity<Buffer>,
23418 position: text::Anchor,
23419 cx: &mut App,
23420 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23421 Some(self.update(cx, |project, cx| {
23422 project.document_highlights(buffer, position, cx)
23423 }))
23424 }
23425
23426 fn definitions(
23427 &self,
23428 buffer: &Entity<Buffer>,
23429 position: text::Anchor,
23430 kind: GotoDefinitionKind,
23431 cx: &mut App,
23432 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23433 Some(self.update(cx, |project, cx| match kind {
23434 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23435 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23436 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23437 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23438 }))
23439 }
23440
23441 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23442 self.update(cx, |project, cx| {
23443 if project
23444 .active_debug_session(cx)
23445 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23446 {
23447 return true;
23448 }
23449
23450 buffer.update(cx, |buffer, cx| {
23451 project.any_language_server_supports_inlay_hints(buffer, cx)
23452 })
23453 })
23454 }
23455
23456 fn inline_values(
23457 &self,
23458 buffer_handle: Entity<Buffer>,
23459 range: Range<text::Anchor>,
23460 cx: &mut App,
23461 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23462 self.update(cx, |project, cx| {
23463 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23464
23465 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23466 })
23467 }
23468
23469 fn applicable_inlay_chunks(
23470 &self,
23471 buffer: &Entity<Buffer>,
23472 ranges: &[Range<text::Anchor>],
23473 cx: &mut App,
23474 ) -> Vec<Range<BufferRow>> {
23475 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23476 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23477 })
23478 }
23479
23480 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23481 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23482 lsp_store.invalidate_inlay_hints(for_buffers)
23483 });
23484 }
23485
23486 fn inlay_hints(
23487 &self,
23488 invalidate: InvalidationStrategy,
23489 buffer: Entity<Buffer>,
23490 ranges: Vec<Range<text::Anchor>>,
23491 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23492 cx: &mut App,
23493 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23494 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23495 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23496 }))
23497 }
23498
23499 fn range_for_rename(
23500 &self,
23501 buffer: &Entity<Buffer>,
23502 position: text::Anchor,
23503 cx: &mut App,
23504 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23505 Some(self.update(cx, |project, cx| {
23506 let buffer = buffer.clone();
23507 let task = project.prepare_rename(buffer.clone(), position, cx);
23508 cx.spawn(async move |_, cx| {
23509 Ok(match task.await? {
23510 PrepareRenameResponse::Success(range) => Some(range),
23511 PrepareRenameResponse::InvalidPosition => None,
23512 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23513 // Fallback on using TreeSitter info to determine identifier range
23514 buffer.read_with(cx, |buffer, _| {
23515 let snapshot = buffer.snapshot();
23516 let (range, kind) = snapshot.surrounding_word(position, None);
23517 if kind != Some(CharKind::Word) {
23518 return None;
23519 }
23520 Some(
23521 snapshot.anchor_before(range.start)
23522 ..snapshot.anchor_after(range.end),
23523 )
23524 })?
23525 }
23526 })
23527 })
23528 }))
23529 }
23530
23531 fn perform_rename(
23532 &self,
23533 buffer: &Entity<Buffer>,
23534 position: text::Anchor,
23535 new_name: String,
23536 cx: &mut App,
23537 ) -> Option<Task<Result<ProjectTransaction>>> {
23538 Some(self.update(cx, |project, cx| {
23539 project.perform_rename(buffer.clone(), position, new_name, cx)
23540 }))
23541 }
23542}
23543
23544fn consume_contiguous_rows(
23545 contiguous_row_selections: &mut Vec<Selection<Point>>,
23546 selection: &Selection<Point>,
23547 display_map: &DisplaySnapshot,
23548 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23549) -> (MultiBufferRow, MultiBufferRow) {
23550 contiguous_row_selections.push(selection.clone());
23551 let start_row = starting_row(selection, display_map);
23552 let mut end_row = ending_row(selection, display_map);
23553
23554 while let Some(next_selection) = selections.peek() {
23555 if next_selection.start.row <= end_row.0 {
23556 end_row = ending_row(next_selection, display_map);
23557 contiguous_row_selections.push(selections.next().unwrap().clone());
23558 } else {
23559 break;
23560 }
23561 }
23562 (start_row, end_row)
23563}
23564
23565fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23566 if selection.start.column > 0 {
23567 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23568 } else {
23569 MultiBufferRow(selection.start.row)
23570 }
23571}
23572
23573fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23574 if next_selection.end.column > 0 || next_selection.is_empty() {
23575 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23576 } else {
23577 MultiBufferRow(next_selection.end.row)
23578 }
23579}
23580
23581impl EditorSnapshot {
23582 pub fn remote_selections_in_range<'a>(
23583 &'a self,
23584 range: &'a Range<Anchor>,
23585 collaboration_hub: &dyn CollaborationHub,
23586 cx: &'a App,
23587 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23588 let participant_names = collaboration_hub.user_names(cx);
23589 let participant_indices = collaboration_hub.user_participant_indices(cx);
23590 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23591 let collaborators_by_replica_id = collaborators_by_peer_id
23592 .values()
23593 .map(|collaborator| (collaborator.replica_id, collaborator))
23594 .collect::<HashMap<_, _>>();
23595 self.buffer_snapshot()
23596 .selections_in_range(range, false)
23597 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23598 if replica_id == ReplicaId::AGENT {
23599 Some(RemoteSelection {
23600 replica_id,
23601 selection,
23602 cursor_shape,
23603 line_mode,
23604 collaborator_id: CollaboratorId::Agent,
23605 user_name: Some("Agent".into()),
23606 color: cx.theme().players().agent(),
23607 })
23608 } else {
23609 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23610 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23611 let user_name = participant_names.get(&collaborator.user_id).cloned();
23612 Some(RemoteSelection {
23613 replica_id,
23614 selection,
23615 cursor_shape,
23616 line_mode,
23617 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23618 user_name,
23619 color: if let Some(index) = participant_index {
23620 cx.theme().players().color_for_participant(index.0)
23621 } else {
23622 cx.theme().players().absent()
23623 },
23624 })
23625 }
23626 })
23627 }
23628
23629 pub fn hunks_for_ranges(
23630 &self,
23631 ranges: impl IntoIterator<Item = Range<Point>>,
23632 ) -> Vec<MultiBufferDiffHunk> {
23633 let mut hunks = Vec::new();
23634 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23635 HashMap::default();
23636 for query_range in ranges {
23637 let query_rows =
23638 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23639 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23640 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23641 ) {
23642 // Include deleted hunks that are adjacent to the query range, because
23643 // otherwise they would be missed.
23644 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23645 if hunk.status().is_deleted() {
23646 intersects_range |= hunk.row_range.start == query_rows.end;
23647 intersects_range |= hunk.row_range.end == query_rows.start;
23648 }
23649 if intersects_range {
23650 if !processed_buffer_rows
23651 .entry(hunk.buffer_id)
23652 .or_default()
23653 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23654 {
23655 continue;
23656 }
23657 hunks.push(hunk);
23658 }
23659 }
23660 }
23661
23662 hunks
23663 }
23664
23665 fn display_diff_hunks_for_rows<'a>(
23666 &'a self,
23667 display_rows: Range<DisplayRow>,
23668 folded_buffers: &'a HashSet<BufferId>,
23669 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23670 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23671 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23672
23673 self.buffer_snapshot()
23674 .diff_hunks_in_range(buffer_start..buffer_end)
23675 .filter_map(|hunk| {
23676 if folded_buffers.contains(&hunk.buffer_id) {
23677 return None;
23678 }
23679
23680 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23681 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23682
23683 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23684 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23685
23686 let display_hunk = if hunk_display_start.column() != 0 {
23687 DisplayDiffHunk::Folded {
23688 display_row: hunk_display_start.row(),
23689 }
23690 } else {
23691 let mut end_row = hunk_display_end.row();
23692 if hunk_display_end.column() > 0 {
23693 end_row.0 += 1;
23694 }
23695 let is_created_file = hunk.is_created_file();
23696 DisplayDiffHunk::Unfolded {
23697 status: hunk.status(),
23698 diff_base_byte_range: hunk.diff_base_byte_range,
23699 display_row_range: hunk_display_start.row()..end_row,
23700 multi_buffer_range: Anchor::range_in_buffer(
23701 hunk.excerpt_id,
23702 hunk.buffer_id,
23703 hunk.buffer_range,
23704 ),
23705 is_created_file,
23706 }
23707 };
23708
23709 Some(display_hunk)
23710 })
23711 }
23712
23713 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23714 self.display_snapshot
23715 .buffer_snapshot()
23716 .language_at(position)
23717 }
23718
23719 pub fn is_focused(&self) -> bool {
23720 self.is_focused
23721 }
23722
23723 pub fn placeholder_text(&self) -> Option<String> {
23724 self.placeholder_display_snapshot
23725 .as_ref()
23726 .map(|display_map| display_map.text())
23727 }
23728
23729 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23730 self.scroll_anchor.scroll_position(&self.display_snapshot)
23731 }
23732
23733 fn gutter_dimensions(
23734 &self,
23735 font_id: FontId,
23736 font_size: Pixels,
23737 max_line_number_width: Pixels,
23738 cx: &App,
23739 ) -> Option<GutterDimensions> {
23740 if !self.show_gutter {
23741 return None;
23742 }
23743
23744 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23745 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23746
23747 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23748 matches!(
23749 ProjectSettings::get_global(cx).git.git_gutter,
23750 GitGutterSetting::TrackedFiles
23751 )
23752 });
23753 let gutter_settings = EditorSettings::get_global(cx).gutter;
23754 let show_line_numbers = self
23755 .show_line_numbers
23756 .unwrap_or(gutter_settings.line_numbers);
23757 let line_gutter_width = if show_line_numbers {
23758 // Avoid flicker-like gutter resizes when the line number gains another digit by
23759 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23760 let min_width_for_number_on_gutter =
23761 ch_advance * gutter_settings.min_line_number_digits as f32;
23762 max_line_number_width.max(min_width_for_number_on_gutter)
23763 } else {
23764 0.0.into()
23765 };
23766
23767 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23768 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23769
23770 let git_blame_entries_width =
23771 self.git_blame_gutter_max_author_length
23772 .map(|max_author_length| {
23773 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23774 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23775
23776 /// The number of characters to dedicate to gaps and margins.
23777 const SPACING_WIDTH: usize = 4;
23778
23779 let max_char_count = max_author_length.min(renderer.max_author_length())
23780 + ::git::SHORT_SHA_LENGTH
23781 + MAX_RELATIVE_TIMESTAMP.len()
23782 + SPACING_WIDTH;
23783
23784 ch_advance * max_char_count
23785 });
23786
23787 let is_singleton = self.buffer_snapshot().is_singleton();
23788
23789 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23790 left_padding += if !is_singleton {
23791 ch_width * 4.0
23792 } else if show_runnables || show_breakpoints {
23793 ch_width * 3.0
23794 } else if show_git_gutter && show_line_numbers {
23795 ch_width * 2.0
23796 } else if show_git_gutter || show_line_numbers {
23797 ch_width
23798 } else {
23799 px(0.)
23800 };
23801
23802 let shows_folds = is_singleton && gutter_settings.folds;
23803
23804 let right_padding = if shows_folds && show_line_numbers {
23805 ch_width * 4.0
23806 } else if shows_folds || (!is_singleton && show_line_numbers) {
23807 ch_width * 3.0
23808 } else if show_line_numbers {
23809 ch_width
23810 } else {
23811 px(0.)
23812 };
23813
23814 Some(GutterDimensions {
23815 left_padding,
23816 right_padding,
23817 width: line_gutter_width + left_padding + right_padding,
23818 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23819 git_blame_entries_width,
23820 })
23821 }
23822
23823 pub fn render_crease_toggle(
23824 &self,
23825 buffer_row: MultiBufferRow,
23826 row_contains_cursor: bool,
23827 editor: Entity<Editor>,
23828 window: &mut Window,
23829 cx: &mut App,
23830 ) -> Option<AnyElement> {
23831 let folded = self.is_line_folded(buffer_row);
23832 let mut is_foldable = false;
23833
23834 if let Some(crease) = self
23835 .crease_snapshot
23836 .query_row(buffer_row, self.buffer_snapshot())
23837 {
23838 is_foldable = true;
23839 match crease {
23840 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23841 if let Some(render_toggle) = render_toggle {
23842 let toggle_callback =
23843 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23844 if folded {
23845 editor.update(cx, |editor, cx| {
23846 editor.fold_at(buffer_row, window, cx)
23847 });
23848 } else {
23849 editor.update(cx, |editor, cx| {
23850 editor.unfold_at(buffer_row, window, cx)
23851 });
23852 }
23853 });
23854 return Some((render_toggle)(
23855 buffer_row,
23856 folded,
23857 toggle_callback,
23858 window,
23859 cx,
23860 ));
23861 }
23862 }
23863 }
23864 }
23865
23866 is_foldable |= self.starts_indent(buffer_row);
23867
23868 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23869 Some(
23870 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23871 .toggle_state(folded)
23872 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23873 if folded {
23874 this.unfold_at(buffer_row, window, cx);
23875 } else {
23876 this.fold_at(buffer_row, window, cx);
23877 }
23878 }))
23879 .into_any_element(),
23880 )
23881 } else {
23882 None
23883 }
23884 }
23885
23886 pub fn render_crease_trailer(
23887 &self,
23888 buffer_row: MultiBufferRow,
23889 window: &mut Window,
23890 cx: &mut App,
23891 ) -> Option<AnyElement> {
23892 let folded = self.is_line_folded(buffer_row);
23893 if let Crease::Inline { render_trailer, .. } = self
23894 .crease_snapshot
23895 .query_row(buffer_row, self.buffer_snapshot())?
23896 {
23897 let render_trailer = render_trailer.as_ref()?;
23898 Some(render_trailer(buffer_row, folded, window, cx))
23899 } else {
23900 None
23901 }
23902 }
23903}
23904
23905impl Deref for EditorSnapshot {
23906 type Target = DisplaySnapshot;
23907
23908 fn deref(&self) -> &Self::Target {
23909 &self.display_snapshot
23910 }
23911}
23912
23913#[derive(Clone, Debug, PartialEq, Eq)]
23914pub enum EditorEvent {
23915 InputIgnored {
23916 text: Arc<str>,
23917 },
23918 InputHandled {
23919 utf16_range_to_replace: Option<Range<isize>>,
23920 text: Arc<str>,
23921 },
23922 ExcerptsAdded {
23923 buffer: Entity<Buffer>,
23924 predecessor: ExcerptId,
23925 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23926 },
23927 ExcerptsRemoved {
23928 ids: Vec<ExcerptId>,
23929 removed_buffer_ids: Vec<BufferId>,
23930 },
23931 BufferFoldToggled {
23932 ids: Vec<ExcerptId>,
23933 folded: bool,
23934 },
23935 ExcerptsEdited {
23936 ids: Vec<ExcerptId>,
23937 },
23938 ExcerptsExpanded {
23939 ids: Vec<ExcerptId>,
23940 },
23941 BufferEdited,
23942 Edited {
23943 transaction_id: clock::Lamport,
23944 },
23945 Reparsed(BufferId),
23946 Focused,
23947 FocusedIn,
23948 Blurred,
23949 DirtyChanged,
23950 Saved,
23951 TitleChanged,
23952 SelectionsChanged {
23953 local: bool,
23954 },
23955 ScrollPositionChanged {
23956 local: bool,
23957 autoscroll: bool,
23958 },
23959 TransactionUndone {
23960 transaction_id: clock::Lamport,
23961 },
23962 TransactionBegun {
23963 transaction_id: clock::Lamport,
23964 },
23965 CursorShapeChanged,
23966 BreadcrumbsChanged,
23967 PushedToNavHistory {
23968 anchor: Anchor,
23969 is_deactivate: bool,
23970 },
23971}
23972
23973impl EventEmitter<EditorEvent> for Editor {}
23974
23975impl Focusable for Editor {
23976 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23977 self.focus_handle.clone()
23978 }
23979}
23980
23981impl Render for Editor {
23982 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23983 let settings = ThemeSettings::get_global(cx);
23984
23985 let mut text_style = match self.mode {
23986 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23987 color: cx.theme().colors().editor_foreground,
23988 font_family: settings.ui_font.family.clone(),
23989 font_features: settings.ui_font.features.clone(),
23990 font_fallbacks: settings.ui_font.fallbacks.clone(),
23991 font_size: rems(0.875).into(),
23992 font_weight: settings.ui_font.weight,
23993 line_height: relative(settings.buffer_line_height.value()),
23994 ..Default::default()
23995 },
23996 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23997 color: cx.theme().colors().editor_foreground,
23998 font_family: settings.buffer_font.family.clone(),
23999 font_features: settings.buffer_font.features.clone(),
24000 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24001 font_size: settings.buffer_font_size(cx).into(),
24002 font_weight: settings.buffer_font.weight,
24003 line_height: relative(settings.buffer_line_height.value()),
24004 ..Default::default()
24005 },
24006 };
24007 if let Some(text_style_refinement) = &self.text_style_refinement {
24008 text_style.refine(text_style_refinement)
24009 }
24010
24011 let background = match self.mode {
24012 EditorMode::SingleLine => cx.theme().system().transparent,
24013 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24014 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24015 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24016 };
24017
24018 EditorElement::new(
24019 &cx.entity(),
24020 EditorStyle {
24021 background,
24022 border: cx.theme().colors().border,
24023 local_player: cx.theme().players().local(),
24024 text: text_style,
24025 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24026 syntax: cx.theme().syntax().clone(),
24027 status: cx.theme().status().clone(),
24028 inlay_hints_style: make_inlay_hints_style(cx),
24029 edit_prediction_styles: make_suggestion_styles(cx),
24030 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
24031 show_underlines: self.diagnostics_enabled(),
24032 },
24033 )
24034 }
24035}
24036
24037impl EntityInputHandler for Editor {
24038 fn text_for_range(
24039 &mut self,
24040 range_utf16: Range<usize>,
24041 adjusted_range: &mut Option<Range<usize>>,
24042 _: &mut Window,
24043 cx: &mut Context<Self>,
24044 ) -> Option<String> {
24045 let snapshot = self.buffer.read(cx).read(cx);
24046 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
24047 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
24048 if (start.0..end.0) != range_utf16 {
24049 adjusted_range.replace(start.0..end.0);
24050 }
24051 Some(snapshot.text_for_range(start..end).collect())
24052 }
24053
24054 fn selected_text_range(
24055 &mut self,
24056 ignore_disabled_input: bool,
24057 _: &mut Window,
24058 cx: &mut Context<Self>,
24059 ) -> Option<UTF16Selection> {
24060 // Prevent the IME menu from appearing when holding down an alphabetic key
24061 // while input is disabled.
24062 if !ignore_disabled_input && !self.input_enabled {
24063 return None;
24064 }
24065
24066 let selection = self
24067 .selections
24068 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
24069 let range = selection.range();
24070
24071 Some(UTF16Selection {
24072 range: range.start.0..range.end.0,
24073 reversed: selection.reversed,
24074 })
24075 }
24076
24077 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24078 let snapshot = self.buffer.read(cx).read(cx);
24079 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24080 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
24081 }
24082
24083 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24084 self.clear_highlights::<InputComposition>(cx);
24085 self.ime_transaction.take();
24086 }
24087
24088 fn replace_text_in_range(
24089 &mut self,
24090 range_utf16: Option<Range<usize>>,
24091 text: &str,
24092 window: &mut Window,
24093 cx: &mut Context<Self>,
24094 ) {
24095 if !self.input_enabled {
24096 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24097 return;
24098 }
24099
24100 self.transact(window, cx, |this, window, cx| {
24101 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24102 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24103 Some(this.selection_replacement_ranges(range_utf16, cx))
24104 } else {
24105 this.marked_text_ranges(cx)
24106 };
24107
24108 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24109 let newest_selection_id = this.selections.newest_anchor().id;
24110 this.selections
24111 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24112 .iter()
24113 .zip(ranges_to_replace.iter())
24114 .find_map(|(selection, range)| {
24115 if selection.id == newest_selection_id {
24116 Some(
24117 (range.start.0 as isize - selection.head().0 as isize)
24118 ..(range.end.0 as isize - selection.head().0 as isize),
24119 )
24120 } else {
24121 None
24122 }
24123 })
24124 });
24125
24126 cx.emit(EditorEvent::InputHandled {
24127 utf16_range_to_replace: range_to_replace,
24128 text: text.into(),
24129 });
24130
24131 if let Some(new_selected_ranges) = new_selected_ranges {
24132 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24133 selections.select_ranges(new_selected_ranges)
24134 });
24135 this.backspace(&Default::default(), window, cx);
24136 }
24137
24138 this.handle_input(text, window, cx);
24139 });
24140
24141 if let Some(transaction) = self.ime_transaction {
24142 self.buffer.update(cx, |buffer, cx| {
24143 buffer.group_until_transaction(transaction, cx);
24144 });
24145 }
24146
24147 self.unmark_text(window, cx);
24148 }
24149
24150 fn replace_and_mark_text_in_range(
24151 &mut self,
24152 range_utf16: Option<Range<usize>>,
24153 text: &str,
24154 new_selected_range_utf16: Option<Range<usize>>,
24155 window: &mut Window,
24156 cx: &mut Context<Self>,
24157 ) {
24158 if !self.input_enabled {
24159 return;
24160 }
24161
24162 let transaction = self.transact(window, cx, |this, window, cx| {
24163 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24164 let snapshot = this.buffer.read(cx).read(cx);
24165 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24166 for marked_range in &mut marked_ranges {
24167 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24168 marked_range.start.0 += relative_range_utf16.start;
24169 marked_range.start =
24170 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24171 marked_range.end =
24172 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24173 }
24174 }
24175 Some(marked_ranges)
24176 } else if let Some(range_utf16) = range_utf16 {
24177 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24178 Some(this.selection_replacement_ranges(range_utf16, cx))
24179 } else {
24180 None
24181 };
24182
24183 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24184 let newest_selection_id = this.selections.newest_anchor().id;
24185 this.selections
24186 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24187 .iter()
24188 .zip(ranges_to_replace.iter())
24189 .find_map(|(selection, range)| {
24190 if selection.id == newest_selection_id {
24191 Some(
24192 (range.start.0 as isize - selection.head().0 as isize)
24193 ..(range.end.0 as isize - selection.head().0 as isize),
24194 )
24195 } else {
24196 None
24197 }
24198 })
24199 });
24200
24201 cx.emit(EditorEvent::InputHandled {
24202 utf16_range_to_replace: range_to_replace,
24203 text: text.into(),
24204 });
24205
24206 if let Some(ranges) = ranges_to_replace {
24207 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24208 s.select_ranges(ranges)
24209 });
24210 }
24211
24212 let marked_ranges = {
24213 let snapshot = this.buffer.read(cx).read(cx);
24214 this.selections
24215 .disjoint_anchors_arc()
24216 .iter()
24217 .map(|selection| {
24218 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24219 })
24220 .collect::<Vec<_>>()
24221 };
24222
24223 if text.is_empty() {
24224 this.unmark_text(window, cx);
24225 } else {
24226 this.highlight_text::<InputComposition>(
24227 marked_ranges.clone(),
24228 HighlightStyle {
24229 underline: Some(UnderlineStyle {
24230 thickness: px(1.),
24231 color: None,
24232 wavy: false,
24233 }),
24234 ..Default::default()
24235 },
24236 cx,
24237 );
24238 }
24239
24240 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24241 let use_autoclose = this.use_autoclose;
24242 let use_auto_surround = this.use_auto_surround;
24243 this.set_use_autoclose(false);
24244 this.set_use_auto_surround(false);
24245 this.handle_input(text, window, cx);
24246 this.set_use_autoclose(use_autoclose);
24247 this.set_use_auto_surround(use_auto_surround);
24248
24249 if let Some(new_selected_range) = new_selected_range_utf16 {
24250 let snapshot = this.buffer.read(cx).read(cx);
24251 let new_selected_ranges = marked_ranges
24252 .into_iter()
24253 .map(|marked_range| {
24254 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24255 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24256 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24257 snapshot.clip_offset_utf16(new_start, Bias::Left)
24258 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24259 })
24260 .collect::<Vec<_>>();
24261
24262 drop(snapshot);
24263 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24264 selections.select_ranges(new_selected_ranges)
24265 });
24266 }
24267 });
24268
24269 self.ime_transaction = self.ime_transaction.or(transaction);
24270 if let Some(transaction) = self.ime_transaction {
24271 self.buffer.update(cx, |buffer, cx| {
24272 buffer.group_until_transaction(transaction, cx);
24273 });
24274 }
24275
24276 if self.text_highlights::<InputComposition>(cx).is_none() {
24277 self.ime_transaction.take();
24278 }
24279 }
24280
24281 fn bounds_for_range(
24282 &mut self,
24283 range_utf16: Range<usize>,
24284 element_bounds: gpui::Bounds<Pixels>,
24285 window: &mut Window,
24286 cx: &mut Context<Self>,
24287 ) -> Option<gpui::Bounds<Pixels>> {
24288 let text_layout_details = self.text_layout_details(window);
24289 let CharacterDimensions {
24290 em_width,
24291 em_advance,
24292 line_height,
24293 } = self.character_dimensions(window);
24294
24295 let snapshot = self.snapshot(window, cx);
24296 let scroll_position = snapshot.scroll_position();
24297 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24298
24299 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24300 let x = Pixels::from(
24301 ScrollOffset::from(
24302 snapshot.x_for_display_point(start, &text_layout_details)
24303 + self.gutter_dimensions.full_width(),
24304 ) - scroll_left,
24305 );
24306 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24307
24308 Some(Bounds {
24309 origin: element_bounds.origin + point(x, y),
24310 size: size(em_width, line_height),
24311 })
24312 }
24313
24314 fn character_index_for_point(
24315 &mut self,
24316 point: gpui::Point<Pixels>,
24317 _window: &mut Window,
24318 _cx: &mut Context<Self>,
24319 ) -> Option<usize> {
24320 let position_map = self.last_position_map.as_ref()?;
24321 if !position_map.text_hitbox.contains(&point) {
24322 return None;
24323 }
24324 let display_point = position_map.point_for_position(point).previous_valid;
24325 let anchor = position_map
24326 .snapshot
24327 .display_point_to_anchor(display_point, Bias::Left);
24328 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24329 Some(utf16_offset.0)
24330 }
24331
24332 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24333 self.input_enabled
24334 }
24335}
24336
24337trait SelectionExt {
24338 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24339 fn spanned_rows(
24340 &self,
24341 include_end_if_at_line_start: bool,
24342 map: &DisplaySnapshot,
24343 ) -> Range<MultiBufferRow>;
24344}
24345
24346impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24347 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24348 let start = self
24349 .start
24350 .to_point(map.buffer_snapshot())
24351 .to_display_point(map);
24352 let end = self
24353 .end
24354 .to_point(map.buffer_snapshot())
24355 .to_display_point(map);
24356 if self.reversed {
24357 end..start
24358 } else {
24359 start..end
24360 }
24361 }
24362
24363 fn spanned_rows(
24364 &self,
24365 include_end_if_at_line_start: bool,
24366 map: &DisplaySnapshot,
24367 ) -> Range<MultiBufferRow> {
24368 let start = self.start.to_point(map.buffer_snapshot());
24369 let mut end = self.end.to_point(map.buffer_snapshot());
24370 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24371 end.row -= 1;
24372 }
24373
24374 let buffer_start = map.prev_line_boundary(start).0;
24375 let buffer_end = map.next_line_boundary(end).0;
24376 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24377 }
24378}
24379
24380impl<T: InvalidationRegion> InvalidationStack<T> {
24381 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24382 where
24383 S: Clone + ToOffset,
24384 {
24385 while let Some(region) = self.last() {
24386 let all_selections_inside_invalidation_ranges =
24387 if selections.len() == region.ranges().len() {
24388 selections
24389 .iter()
24390 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24391 .all(|(selection, invalidation_range)| {
24392 let head = selection.head().to_offset(buffer);
24393 invalidation_range.start <= head && invalidation_range.end >= head
24394 })
24395 } else {
24396 false
24397 };
24398
24399 if all_selections_inside_invalidation_ranges {
24400 break;
24401 } else {
24402 self.pop();
24403 }
24404 }
24405 }
24406}
24407
24408impl<T> Default for InvalidationStack<T> {
24409 fn default() -> Self {
24410 Self(Default::default())
24411 }
24412}
24413
24414impl<T> Deref for InvalidationStack<T> {
24415 type Target = Vec<T>;
24416
24417 fn deref(&self) -> &Self::Target {
24418 &self.0
24419 }
24420}
24421
24422impl<T> DerefMut for InvalidationStack<T> {
24423 fn deref_mut(&mut self) -> &mut Self::Target {
24424 &mut self.0
24425 }
24426}
24427
24428impl InvalidationRegion for SnippetState {
24429 fn ranges(&self) -> &[Range<Anchor>] {
24430 &self.ranges[self.active_index]
24431 }
24432}
24433
24434fn edit_prediction_edit_text(
24435 current_snapshot: &BufferSnapshot,
24436 edits: &[(Range<Anchor>, impl AsRef<str>)],
24437 edit_preview: &EditPreview,
24438 include_deletions: bool,
24439 cx: &App,
24440) -> HighlightedText {
24441 let edits = edits
24442 .iter()
24443 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24444 .collect::<Vec<_>>();
24445
24446 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24447}
24448
24449fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24450 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24451 // Just show the raw edit text with basic styling
24452 let mut text = String::new();
24453 let mut highlights = Vec::new();
24454
24455 let insertion_highlight_style = HighlightStyle {
24456 color: Some(cx.theme().colors().text),
24457 ..Default::default()
24458 };
24459
24460 for (_, edit_text) in edits {
24461 let start_offset = text.len();
24462 text.push_str(edit_text);
24463 let end_offset = text.len();
24464
24465 if start_offset < end_offset {
24466 highlights.push((start_offset..end_offset, insertion_highlight_style));
24467 }
24468 }
24469
24470 HighlightedText {
24471 text: text.into(),
24472 highlights,
24473 }
24474}
24475
24476pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24477 match severity {
24478 lsp::DiagnosticSeverity::ERROR => colors.error,
24479 lsp::DiagnosticSeverity::WARNING => colors.warning,
24480 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24481 lsp::DiagnosticSeverity::HINT => colors.info,
24482 _ => colors.ignored,
24483 }
24484}
24485
24486pub fn styled_runs_for_code_label<'a>(
24487 label: &'a CodeLabel,
24488 syntax_theme: &'a theme::SyntaxTheme,
24489) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24490 let fade_out = HighlightStyle {
24491 fade_out: Some(0.35),
24492 ..Default::default()
24493 };
24494
24495 let mut prev_end = label.filter_range.end;
24496 label
24497 .runs
24498 .iter()
24499 .enumerate()
24500 .flat_map(move |(ix, (range, highlight_id))| {
24501 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24502 style
24503 } else {
24504 return Default::default();
24505 };
24506 let muted_style = style.highlight(fade_out);
24507
24508 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24509 if range.start >= label.filter_range.end {
24510 if range.start > prev_end {
24511 runs.push((prev_end..range.start, fade_out));
24512 }
24513 runs.push((range.clone(), muted_style));
24514 } else if range.end <= label.filter_range.end {
24515 runs.push((range.clone(), style));
24516 } else {
24517 runs.push((range.start..label.filter_range.end, style));
24518 runs.push((label.filter_range.end..range.end, muted_style));
24519 }
24520 prev_end = cmp::max(prev_end, range.end);
24521
24522 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24523 runs.push((prev_end..label.text.len(), fade_out));
24524 }
24525
24526 runs
24527 })
24528}
24529
24530pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24531 let mut prev_index = 0;
24532 let mut prev_codepoint: Option<char> = None;
24533 text.char_indices()
24534 .chain([(text.len(), '\0')])
24535 .filter_map(move |(index, codepoint)| {
24536 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24537 let is_boundary = index == text.len()
24538 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24539 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24540 if is_boundary {
24541 let chunk = &text[prev_index..index];
24542 prev_index = index;
24543 Some(chunk)
24544 } else {
24545 None
24546 }
24547 })
24548}
24549
24550pub trait RangeToAnchorExt: Sized {
24551 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24552
24553 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24554 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24555 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24556 }
24557}
24558
24559impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24560 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24561 let start_offset = self.start.to_offset(snapshot);
24562 let end_offset = self.end.to_offset(snapshot);
24563 if start_offset == end_offset {
24564 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24565 } else {
24566 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24567 }
24568 }
24569}
24570
24571pub trait RowExt {
24572 fn as_f64(&self) -> f64;
24573
24574 fn next_row(&self) -> Self;
24575
24576 fn previous_row(&self) -> Self;
24577
24578 fn minus(&self, other: Self) -> u32;
24579}
24580
24581impl RowExt for DisplayRow {
24582 fn as_f64(&self) -> f64 {
24583 self.0 as _
24584 }
24585
24586 fn next_row(&self) -> Self {
24587 Self(self.0 + 1)
24588 }
24589
24590 fn previous_row(&self) -> Self {
24591 Self(self.0.saturating_sub(1))
24592 }
24593
24594 fn minus(&self, other: Self) -> u32 {
24595 self.0 - other.0
24596 }
24597}
24598
24599impl RowExt for MultiBufferRow {
24600 fn as_f64(&self) -> f64 {
24601 self.0 as _
24602 }
24603
24604 fn next_row(&self) -> Self {
24605 Self(self.0 + 1)
24606 }
24607
24608 fn previous_row(&self) -> Self {
24609 Self(self.0.saturating_sub(1))
24610 }
24611
24612 fn minus(&self, other: Self) -> u32 {
24613 self.0 - other.0
24614 }
24615}
24616
24617trait RowRangeExt {
24618 type Row;
24619
24620 fn len(&self) -> usize;
24621
24622 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24623}
24624
24625impl RowRangeExt for Range<MultiBufferRow> {
24626 type Row = MultiBufferRow;
24627
24628 fn len(&self) -> usize {
24629 (self.end.0 - self.start.0) as usize
24630 }
24631
24632 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24633 (self.start.0..self.end.0).map(MultiBufferRow)
24634 }
24635}
24636
24637impl RowRangeExt for Range<DisplayRow> {
24638 type Row = DisplayRow;
24639
24640 fn len(&self) -> usize {
24641 (self.end.0 - self.start.0) as usize
24642 }
24643
24644 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24645 (self.start.0..self.end.0).map(DisplayRow)
24646 }
24647}
24648
24649/// If select range has more than one line, we
24650/// just point the cursor to range.start.
24651fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24652 if range.start.row == range.end.row {
24653 range
24654 } else {
24655 range.start..range.start
24656 }
24657}
24658pub struct KillRing(ClipboardItem);
24659impl Global for KillRing {}
24660
24661const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24662
24663enum BreakpointPromptEditAction {
24664 Log,
24665 Condition,
24666 HitCondition,
24667}
24668
24669struct BreakpointPromptEditor {
24670 pub(crate) prompt: Entity<Editor>,
24671 editor: WeakEntity<Editor>,
24672 breakpoint_anchor: Anchor,
24673 breakpoint: Breakpoint,
24674 edit_action: BreakpointPromptEditAction,
24675 block_ids: HashSet<CustomBlockId>,
24676 editor_margins: Arc<Mutex<EditorMargins>>,
24677 _subscriptions: Vec<Subscription>,
24678}
24679
24680impl BreakpointPromptEditor {
24681 const MAX_LINES: u8 = 4;
24682
24683 fn new(
24684 editor: WeakEntity<Editor>,
24685 breakpoint_anchor: Anchor,
24686 breakpoint: Breakpoint,
24687 edit_action: BreakpointPromptEditAction,
24688 window: &mut Window,
24689 cx: &mut Context<Self>,
24690 ) -> Self {
24691 let base_text = match edit_action {
24692 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24693 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24694 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24695 }
24696 .map(|msg| msg.to_string())
24697 .unwrap_or_default();
24698
24699 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24700 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24701
24702 let prompt = cx.new(|cx| {
24703 let mut prompt = Editor::new(
24704 EditorMode::AutoHeight {
24705 min_lines: 1,
24706 max_lines: Some(Self::MAX_LINES as usize),
24707 },
24708 buffer,
24709 None,
24710 window,
24711 cx,
24712 );
24713 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24714 prompt.set_show_cursor_when_unfocused(false, cx);
24715 prompt.set_placeholder_text(
24716 match edit_action {
24717 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24718 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24719 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24720 },
24721 window,
24722 cx,
24723 );
24724
24725 prompt
24726 });
24727
24728 Self {
24729 prompt,
24730 editor,
24731 breakpoint_anchor,
24732 breakpoint,
24733 edit_action,
24734 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24735 block_ids: Default::default(),
24736 _subscriptions: vec![],
24737 }
24738 }
24739
24740 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24741 self.block_ids.extend(block_ids)
24742 }
24743
24744 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24745 if let Some(editor) = self.editor.upgrade() {
24746 let message = self
24747 .prompt
24748 .read(cx)
24749 .buffer
24750 .read(cx)
24751 .as_singleton()
24752 .expect("A multi buffer in breakpoint prompt isn't possible")
24753 .read(cx)
24754 .as_rope()
24755 .to_string();
24756
24757 editor.update(cx, |editor, cx| {
24758 editor.edit_breakpoint_at_anchor(
24759 self.breakpoint_anchor,
24760 self.breakpoint.clone(),
24761 match self.edit_action {
24762 BreakpointPromptEditAction::Log => {
24763 BreakpointEditAction::EditLogMessage(message.into())
24764 }
24765 BreakpointPromptEditAction::Condition => {
24766 BreakpointEditAction::EditCondition(message.into())
24767 }
24768 BreakpointPromptEditAction::HitCondition => {
24769 BreakpointEditAction::EditHitCondition(message.into())
24770 }
24771 },
24772 cx,
24773 );
24774
24775 editor.remove_blocks(self.block_ids.clone(), None, cx);
24776 cx.focus_self(window);
24777 });
24778 }
24779 }
24780
24781 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24782 self.editor
24783 .update(cx, |editor, cx| {
24784 editor.remove_blocks(self.block_ids.clone(), None, cx);
24785 window.focus(&editor.focus_handle);
24786 })
24787 .log_err();
24788 }
24789
24790 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24791 let settings = ThemeSettings::get_global(cx);
24792 let text_style = TextStyle {
24793 color: if self.prompt.read(cx).read_only(cx) {
24794 cx.theme().colors().text_disabled
24795 } else {
24796 cx.theme().colors().text
24797 },
24798 font_family: settings.buffer_font.family.clone(),
24799 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24800 font_size: settings.buffer_font_size(cx).into(),
24801 font_weight: settings.buffer_font.weight,
24802 line_height: relative(settings.buffer_line_height.value()),
24803 ..Default::default()
24804 };
24805 EditorElement::new(
24806 &self.prompt,
24807 EditorStyle {
24808 background: cx.theme().colors().editor_background,
24809 local_player: cx.theme().players().local(),
24810 text: text_style,
24811 ..Default::default()
24812 },
24813 )
24814 }
24815}
24816
24817impl Render for BreakpointPromptEditor {
24818 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24819 let editor_margins = *self.editor_margins.lock();
24820 let gutter_dimensions = editor_margins.gutter;
24821 h_flex()
24822 .key_context("Editor")
24823 .bg(cx.theme().colors().editor_background)
24824 .border_y_1()
24825 .border_color(cx.theme().status().info_border)
24826 .size_full()
24827 .py(window.line_height() / 2.5)
24828 .on_action(cx.listener(Self::confirm))
24829 .on_action(cx.listener(Self::cancel))
24830 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24831 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24832 }
24833}
24834
24835impl Focusable for BreakpointPromptEditor {
24836 fn focus_handle(&self, cx: &App) -> FocusHandle {
24837 self.prompt.focus_handle(cx)
24838 }
24839}
24840
24841fn all_edits_insertions_or_deletions(
24842 edits: &Vec<(Range<Anchor>, Arc<str>)>,
24843 snapshot: &MultiBufferSnapshot,
24844) -> bool {
24845 let mut all_insertions = true;
24846 let mut all_deletions = true;
24847
24848 for (range, new_text) in edits.iter() {
24849 let range_is_empty = range.to_offset(snapshot).is_empty();
24850 let text_is_empty = new_text.is_empty();
24851
24852 if range_is_empty != text_is_empty {
24853 if range_is_empty {
24854 all_deletions = false;
24855 } else {
24856 all_insertions = false;
24857 }
24858 } else {
24859 return false;
24860 }
24861
24862 if !all_insertions && !all_deletions {
24863 return false;
24864 }
24865 }
24866 all_insertions || all_deletions
24867}
24868
24869struct MissingEditPredictionKeybindingTooltip;
24870
24871impl Render for MissingEditPredictionKeybindingTooltip {
24872 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24873 ui::tooltip_container(cx, |container, cx| {
24874 container
24875 .flex_shrink_0()
24876 .max_w_80()
24877 .min_h(rems_from_px(124.))
24878 .justify_between()
24879 .child(
24880 v_flex()
24881 .flex_1()
24882 .text_ui_sm(cx)
24883 .child(Label::new("Conflict with Accept Keybinding"))
24884 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24885 )
24886 .child(
24887 h_flex()
24888 .pb_1()
24889 .gap_1()
24890 .items_end()
24891 .w_full()
24892 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24893 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24894 }))
24895 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24896 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24897 })),
24898 )
24899 })
24900 }
24901}
24902
24903#[derive(Debug, Clone, Copy, PartialEq)]
24904pub struct LineHighlight {
24905 pub background: Background,
24906 pub border: Option<gpui::Hsla>,
24907 pub include_gutter: bool,
24908 pub type_id: Option<TypeId>,
24909}
24910
24911struct LineManipulationResult {
24912 pub new_text: String,
24913 pub line_count_before: usize,
24914 pub line_count_after: usize,
24915}
24916
24917fn render_diff_hunk_controls(
24918 row: u32,
24919 status: &DiffHunkStatus,
24920 hunk_range: Range<Anchor>,
24921 is_created_file: bool,
24922 line_height: Pixels,
24923 editor: &Entity<Editor>,
24924 _window: &mut Window,
24925 cx: &mut App,
24926) -> AnyElement {
24927 h_flex()
24928 .h(line_height)
24929 .mr_1()
24930 .gap_1()
24931 .px_0p5()
24932 .pb_1()
24933 .border_x_1()
24934 .border_b_1()
24935 .border_color(cx.theme().colors().border_variant)
24936 .rounded_b_lg()
24937 .bg(cx.theme().colors().editor_background)
24938 .gap_1()
24939 .block_mouse_except_scroll()
24940 .shadow_md()
24941 .child(if status.has_secondary_hunk() {
24942 Button::new(("stage", row as u64), "Stage")
24943 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24944 .tooltip({
24945 let focus_handle = editor.focus_handle(cx);
24946 move |_window, cx| {
24947 Tooltip::for_action_in(
24948 "Stage Hunk",
24949 &::git::ToggleStaged,
24950 &focus_handle,
24951 cx,
24952 )
24953 }
24954 })
24955 .on_click({
24956 let editor = editor.clone();
24957 move |_event, _window, cx| {
24958 editor.update(cx, |editor, cx| {
24959 editor.stage_or_unstage_diff_hunks(
24960 true,
24961 vec![hunk_range.start..hunk_range.start],
24962 cx,
24963 );
24964 });
24965 }
24966 })
24967 } else {
24968 Button::new(("unstage", row as u64), "Unstage")
24969 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24970 .tooltip({
24971 let focus_handle = editor.focus_handle(cx);
24972 move |_window, cx| {
24973 Tooltip::for_action_in(
24974 "Unstage Hunk",
24975 &::git::ToggleStaged,
24976 &focus_handle,
24977 cx,
24978 )
24979 }
24980 })
24981 .on_click({
24982 let editor = editor.clone();
24983 move |_event, _window, cx| {
24984 editor.update(cx, |editor, cx| {
24985 editor.stage_or_unstage_diff_hunks(
24986 false,
24987 vec![hunk_range.start..hunk_range.start],
24988 cx,
24989 );
24990 });
24991 }
24992 })
24993 })
24994 .child(
24995 Button::new(("restore", row as u64), "Restore")
24996 .tooltip({
24997 let focus_handle = editor.focus_handle(cx);
24998 move |_window, cx| {
24999 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
25000 }
25001 })
25002 .on_click({
25003 let editor = editor.clone();
25004 move |_event, window, cx| {
25005 editor.update(cx, |editor, cx| {
25006 let snapshot = editor.snapshot(window, cx);
25007 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
25008 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
25009 });
25010 }
25011 })
25012 .disabled(is_created_file),
25013 )
25014 .when(
25015 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
25016 |el| {
25017 el.child(
25018 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
25019 .shape(IconButtonShape::Square)
25020 .icon_size(IconSize::Small)
25021 // .disabled(!has_multiple_hunks)
25022 .tooltip({
25023 let focus_handle = editor.focus_handle(cx);
25024 move |_window, cx| {
25025 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
25026 }
25027 })
25028 .on_click({
25029 let editor = editor.clone();
25030 move |_event, window, cx| {
25031 editor.update(cx, |editor, cx| {
25032 let snapshot = editor.snapshot(window, cx);
25033 let position =
25034 hunk_range.end.to_point(&snapshot.buffer_snapshot());
25035 editor.go_to_hunk_before_or_after_position(
25036 &snapshot,
25037 position,
25038 Direction::Next,
25039 window,
25040 cx,
25041 );
25042 editor.expand_selected_diff_hunks(cx);
25043 });
25044 }
25045 }),
25046 )
25047 .child(
25048 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25049 .shape(IconButtonShape::Square)
25050 .icon_size(IconSize::Small)
25051 // .disabled(!has_multiple_hunks)
25052 .tooltip({
25053 let focus_handle = editor.focus_handle(cx);
25054 move |_window, cx| {
25055 Tooltip::for_action_in(
25056 "Previous Hunk",
25057 &GoToPreviousHunk,
25058 &focus_handle,
25059 cx,
25060 )
25061 }
25062 })
25063 .on_click({
25064 let editor = editor.clone();
25065 move |_event, window, cx| {
25066 editor.update(cx, |editor, cx| {
25067 let snapshot = editor.snapshot(window, cx);
25068 let point =
25069 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25070 editor.go_to_hunk_before_or_after_position(
25071 &snapshot,
25072 point,
25073 Direction::Prev,
25074 window,
25075 cx,
25076 );
25077 editor.expand_selected_diff_hunks(cx);
25078 });
25079 }
25080 }),
25081 )
25082 },
25083 )
25084 .into_any_element()
25085}
25086
25087pub fn multibuffer_context_lines(cx: &App) -> u32 {
25088 EditorSettings::try_get(cx)
25089 .map(|settings| settings.excerpt_context_lines)
25090 .unwrap_or(2)
25091 .min(32)
25092}