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, Point, Runnable, RunnableRange, Selection, SelectionGoal,
121 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::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
167use smallvec::{SmallVec, smallvec};
168use snippet::Snippet;
169use std::{
170 any::{Any, TypeId},
171 borrow::Cow,
172 cell::{OnceCell, RefCell},
173 cmp::{self, Ordering, Reverse},
174 iter::{self, Peekable},
175 mem,
176 num::NonZeroU32,
177 ops::{Deref, DerefMut, Not, Range, RangeInclusive},
178 path::{Path, PathBuf},
179 rc::Rc,
180 sync::Arc,
181 time::{Duration, Instant},
182};
183use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
184use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
185use theme::{
186 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
187 observe_buffer_font_size_adjustment,
188};
189use ui::{
190 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
191 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
192};
193use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
194use workspace::{
195 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
196 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
197 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
198 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
199 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
200 searchable::SearchEvent,
201};
202
203use crate::{
204 code_context_menus::CompletionsMenuSource,
205 editor_settings::MultiCursorModifier,
206 hover_links::{find_url, find_url_from_range},
207 inlays::{
208 InlineValueCache,
209 inlay_hints::{LspInlayHintData, inlay_hint_settings},
210 },
211 scroll::{ScrollOffset, ScrollPixelOffset},
212 selections_collection::resolve_selections_wrapping_blocks,
213 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
214};
215
216pub const FILE_HEADER_HEIGHT: u32 = 2;
217pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
218const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
219const MAX_LINE_LEN: usize = 1024;
220const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
221const MAX_SELECTION_HISTORY_LEN: usize = 1024;
222pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
223#[doc(hidden)]
224pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
225pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
226
227pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
229pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
230pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
231
232pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
233pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
234pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
235
236pub type RenderDiffHunkControlsFn = Arc<
237 dyn Fn(
238 u32,
239 &DiffHunkStatus,
240 Range<Anchor>,
241 bool,
242 Pixels,
243 &Entity<Editor>,
244 &mut Window,
245 &mut App,
246 ) -> AnyElement,
247>;
248
249enum ReportEditorEvent {
250 Saved { auto_saved: bool },
251 EditorOpened,
252 Closed,
253}
254
255impl ReportEditorEvent {
256 pub fn event_type(&self) -> &'static str {
257 match self {
258 Self::Saved { .. } => "Editor Saved",
259 Self::EditorOpened => "Editor Opened",
260 Self::Closed => "Editor Closed",
261 }
262 }
263}
264
265pub enum ActiveDebugLine {}
266pub enum DebugStackFrameLine {}
267enum DocumentHighlightRead {}
268enum DocumentHighlightWrite {}
269enum InputComposition {}
270pub enum PendingInput {}
271enum SelectedTextHighlight {}
272
273pub enum ConflictsOuter {}
274pub enum ConflictsOurs {}
275pub enum ConflictsTheirs {}
276pub enum ConflictsOursMarker {}
277pub enum ConflictsTheirsMarker {}
278
279#[derive(Debug, Copy, Clone, PartialEq, Eq)]
280pub enum Navigated {
281 Yes,
282 No,
283}
284
285impl Navigated {
286 pub fn from_bool(yes: bool) -> Navigated {
287 if yes { Navigated::Yes } else { Navigated::No }
288 }
289}
290
291#[derive(Debug, Clone, PartialEq, Eq)]
292enum DisplayDiffHunk {
293 Folded {
294 display_row: DisplayRow,
295 },
296 Unfolded {
297 is_created_file: bool,
298 diff_base_byte_range: Range<usize>,
299 display_row_range: Range<DisplayRow>,
300 multi_buffer_range: Range<Anchor>,
301 status: DiffHunkStatus,
302 },
303}
304
305pub enum HideMouseCursorOrigin {
306 TypingAction,
307 MovementAction,
308}
309
310pub fn init_settings(cx: &mut App) {
311 EditorSettings::register(cx);
312}
313
314pub fn init(cx: &mut App) {
315 init_settings(cx);
316
317 cx.set_global(GlobalBlameRenderer(Arc::new(())));
318
319 workspace::register_project_item::<Editor>(cx);
320 workspace::FollowableViewRegistry::register::<Editor>(cx);
321 workspace::register_serializable_item::<Editor>(cx);
322
323 cx.observe_new(
324 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
325 workspace.register_action(Editor::new_file);
326 workspace.register_action(Editor::new_file_split);
327 workspace.register_action(Editor::new_file_vertical);
328 workspace.register_action(Editor::new_file_horizontal);
329 workspace.register_action(Editor::cancel_language_server_work);
330 workspace.register_action(Editor::toggle_focus);
331 },
332 )
333 .detach();
334
335 cx.on_action(move |_: &workspace::NewFile, cx| {
336 let app_state = workspace::AppState::global(cx);
337 if let Some(app_state) = app_state.upgrade() {
338 workspace::open_new(
339 Default::default(),
340 app_state,
341 cx,
342 |workspace, window, cx| {
343 Editor::new_file(workspace, &Default::default(), window, cx)
344 },
345 )
346 .detach();
347 }
348 });
349 cx.on_action(move |_: &workspace::NewWindow, cx| {
350 let app_state = workspace::AppState::global(cx);
351 if let Some(app_state) = app_state.upgrade() {
352 workspace::open_new(
353 Default::default(),
354 app_state,
355 cx,
356 |workspace, window, cx| {
357 cx.activate(true);
358 Editor::new_file(workspace, &Default::default(), window, cx)
359 },
360 )
361 .detach();
362 }
363 });
364}
365
366pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
367 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
368}
369
370pub trait DiagnosticRenderer {
371 fn render_group(
372 &self,
373 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
374 buffer_id: BufferId,
375 snapshot: EditorSnapshot,
376 editor: WeakEntity<Editor>,
377 cx: &mut App,
378 ) -> Vec<BlockProperties<Anchor>>;
379
380 fn render_hover(
381 &self,
382 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
383 range: Range<Point>,
384 buffer_id: BufferId,
385 cx: &mut App,
386 ) -> Option<Entity<markdown::Markdown>>;
387
388 fn open_link(
389 &self,
390 editor: &mut Editor,
391 link: SharedString,
392 window: &mut Window,
393 cx: &mut Context<Editor>,
394 );
395}
396
397pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
398
399impl GlobalDiagnosticRenderer {
400 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
401 cx.try_global::<Self>().map(|g| g.0.clone())
402 }
403}
404
405impl gpui::Global for GlobalDiagnosticRenderer {}
406pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
407 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
408}
409
410pub struct SearchWithinRange;
411
412trait InvalidationRegion {
413 fn ranges(&self) -> &[Range<Anchor>];
414}
415
416#[derive(Clone, Debug, PartialEq)]
417pub enum SelectPhase {
418 Begin {
419 position: DisplayPoint,
420 add: bool,
421 click_count: usize,
422 },
423 BeginColumnar {
424 position: DisplayPoint,
425 reset: bool,
426 mode: ColumnarMode,
427 goal_column: u32,
428 },
429 Extend {
430 position: DisplayPoint,
431 click_count: usize,
432 },
433 Update {
434 position: DisplayPoint,
435 goal_column: u32,
436 scroll_delta: gpui::Point<f32>,
437 },
438 End,
439}
440
441#[derive(Clone, Debug, PartialEq)]
442pub enum ColumnarMode {
443 FromMouse,
444 FromSelection,
445}
446
447#[derive(Clone, Debug)]
448pub enum SelectMode {
449 Character,
450 Word(Range<Anchor>),
451 Line(Range<Anchor>),
452 All,
453}
454
455#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
456pub enum SizingBehavior {
457 /// The editor will layout itself using `size_full` and will include the vertical
458 /// scroll margin as requested by user settings.
459 #[default]
460 Default,
461 /// The editor will layout itself using `size_full`, but will not have any
462 /// vertical overscroll.
463 ExcludeOverscrollMargin,
464 /// The editor will request a vertical size according to its content and will be
465 /// layouted without a vertical scroll margin.
466 SizeByContent,
467}
468
469#[derive(Clone, PartialEq, Eq, Debug)]
470pub enum EditorMode {
471 SingleLine,
472 AutoHeight {
473 min_lines: usize,
474 max_lines: Option<usize>,
475 },
476 Full {
477 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
478 scale_ui_elements_with_buffer_font_size: bool,
479 /// When set to `true`, the editor will render a background for the active line.
480 show_active_line_background: bool,
481 /// Determines the sizing behavior for this editor
482 sizing_behavior: SizingBehavior,
483 },
484 Minimap {
485 parent: WeakEntity<Editor>,
486 },
487}
488
489impl EditorMode {
490 pub fn full() -> Self {
491 Self::Full {
492 scale_ui_elements_with_buffer_font_size: true,
493 show_active_line_background: true,
494 sizing_behavior: SizingBehavior::Default,
495 }
496 }
497
498 #[inline]
499 pub fn is_full(&self) -> bool {
500 matches!(self, Self::Full { .. })
501 }
502
503 #[inline]
504 pub fn is_single_line(&self) -> bool {
505 matches!(self, Self::SingleLine { .. })
506 }
507
508 #[inline]
509 fn is_minimap(&self) -> bool {
510 matches!(self, Self::Minimap { .. })
511 }
512}
513
514#[derive(Copy, Clone, Debug)]
515pub enum SoftWrap {
516 /// Prefer not to wrap at all.
517 ///
518 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
519 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
520 GitDiff,
521 /// Prefer a single line generally, unless an overly long line is encountered.
522 None,
523 /// Soft wrap lines that exceed the editor width.
524 EditorWidth,
525 /// Soft wrap lines at the preferred line length.
526 Column(u32),
527 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
528 Bounded(u32),
529}
530
531#[derive(Clone)]
532pub struct EditorStyle {
533 pub background: Hsla,
534 pub border: Hsla,
535 pub local_player: PlayerColor,
536 pub text: TextStyle,
537 pub scrollbar_width: Pixels,
538 pub syntax: Arc<SyntaxTheme>,
539 pub status: StatusColors,
540 pub inlay_hints_style: HighlightStyle,
541 pub edit_prediction_styles: EditPredictionStyles,
542 pub unnecessary_code_fade: f32,
543 pub show_underlines: bool,
544}
545
546impl Default for EditorStyle {
547 fn default() -> Self {
548 Self {
549 background: Hsla::default(),
550 border: Hsla::default(),
551 local_player: PlayerColor::default(),
552 text: TextStyle::default(),
553 scrollbar_width: Pixels::default(),
554 syntax: Default::default(),
555 // HACK: Status colors don't have a real default.
556 // We should look into removing the status colors from the editor
557 // style and retrieve them directly from the theme.
558 status: StatusColors::dark(),
559 inlay_hints_style: HighlightStyle::default(),
560 edit_prediction_styles: EditPredictionStyles {
561 insertion: HighlightStyle::default(),
562 whitespace: HighlightStyle::default(),
563 },
564 unnecessary_code_fade: Default::default(),
565 show_underlines: true,
566 }
567 }
568}
569
570pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
571 let show_background = language_settings::language_settings(None, None, cx)
572 .inlay_hints
573 .show_background;
574
575 let mut style = cx.theme().syntax().get("hint");
576
577 if style.color.is_none() {
578 style.color = Some(cx.theme().status().hint);
579 }
580
581 if !show_background {
582 style.background_color = None;
583 return style;
584 }
585
586 if style.background_color.is_none() {
587 style.background_color = Some(cx.theme().status().hint_background);
588 }
589
590 style
591}
592
593pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
594 EditPredictionStyles {
595 insertion: HighlightStyle {
596 color: Some(cx.theme().status().predictive),
597 ..HighlightStyle::default()
598 },
599 whitespace: HighlightStyle {
600 background_color: Some(cx.theme().status().created_background),
601 ..HighlightStyle::default()
602 },
603 }
604}
605
606type CompletionId = usize;
607
608pub(crate) enum EditDisplayMode {
609 TabAccept,
610 DiffPopover,
611 Inline,
612}
613
614enum EditPrediction {
615 Edit {
616 edits: Vec<(Range<Anchor>, String)>,
617 edit_preview: Option<EditPreview>,
618 display_mode: EditDisplayMode,
619 snapshot: BufferSnapshot,
620 },
621 /// Move to a specific location in the active editor
622 MoveWithin {
623 target: Anchor,
624 snapshot: BufferSnapshot,
625 },
626 /// Move to a specific location in a different editor (not the active one)
627 MoveOutside {
628 target: language::Anchor,
629 snapshot: BufferSnapshot,
630 },
631}
632
633struct EditPredictionState {
634 inlay_ids: Vec<InlayId>,
635 completion: EditPrediction,
636 completion_id: Option<SharedString>,
637 invalidation_range: Option<Range<Anchor>>,
638}
639
640enum EditPredictionSettings {
641 Disabled,
642 Enabled {
643 show_in_menu: bool,
644 preview_requires_modifier: bool,
645 },
646}
647
648enum EditPredictionHighlight {}
649
650#[derive(Debug, Clone)]
651struct InlineDiagnostic {
652 message: SharedString,
653 group_id: usize,
654 is_primary: bool,
655 start: Point,
656 severity: lsp::DiagnosticSeverity,
657}
658
659pub enum MenuEditPredictionsPolicy {
660 Never,
661 ByProvider,
662}
663
664pub enum EditPredictionPreview {
665 /// Modifier is not pressed
666 Inactive { released_too_fast: bool },
667 /// Modifier pressed
668 Active {
669 since: Instant,
670 previous_scroll_position: Option<ScrollAnchor>,
671 },
672}
673
674impl EditPredictionPreview {
675 pub fn released_too_fast(&self) -> bool {
676 match self {
677 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
678 EditPredictionPreview::Active { .. } => false,
679 }
680 }
681
682 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
683 if let EditPredictionPreview::Active {
684 previous_scroll_position,
685 ..
686 } = self
687 {
688 *previous_scroll_position = scroll_position;
689 }
690 }
691}
692
693pub struct ContextMenuOptions {
694 pub min_entries_visible: usize,
695 pub max_entries_visible: usize,
696 pub placement: Option<ContextMenuPlacement>,
697}
698
699#[derive(Debug, Clone, PartialEq, Eq)]
700pub enum ContextMenuPlacement {
701 Above,
702 Below,
703}
704
705#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
706struct EditorActionId(usize);
707
708impl EditorActionId {
709 pub fn post_inc(&mut self) -> Self {
710 let answer = self.0;
711
712 *self = Self(answer + 1);
713
714 Self(answer)
715 }
716}
717
718// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
719// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
720
721type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
722type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
723
724#[derive(Default)]
725struct ScrollbarMarkerState {
726 scrollbar_size: Size<Pixels>,
727 dirty: bool,
728 markers: Arc<[PaintQuad]>,
729 pending_refresh: Option<Task<Result<()>>>,
730}
731
732impl ScrollbarMarkerState {
733 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
734 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
735 }
736}
737
738#[derive(Clone, Copy, PartialEq, Eq)]
739pub enum MinimapVisibility {
740 Disabled,
741 Enabled {
742 /// The configuration currently present in the users settings.
743 setting_configuration: bool,
744 /// Whether to override the currently set visibility from the users setting.
745 toggle_override: bool,
746 },
747}
748
749impl MinimapVisibility {
750 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
751 if mode.is_full() {
752 Self::Enabled {
753 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
754 toggle_override: false,
755 }
756 } else {
757 Self::Disabled
758 }
759 }
760
761 fn hidden(&self) -> Self {
762 match *self {
763 Self::Enabled {
764 setting_configuration,
765 ..
766 } => Self::Enabled {
767 setting_configuration,
768 toggle_override: setting_configuration,
769 },
770 Self::Disabled => Self::Disabled,
771 }
772 }
773
774 fn disabled(&self) -> bool {
775 matches!(*self, Self::Disabled)
776 }
777
778 fn settings_visibility(&self) -> bool {
779 match *self {
780 Self::Enabled {
781 setting_configuration,
782 ..
783 } => setting_configuration,
784 _ => false,
785 }
786 }
787
788 fn visible(&self) -> bool {
789 match *self {
790 Self::Enabled {
791 setting_configuration,
792 toggle_override,
793 } => setting_configuration ^ toggle_override,
794 _ => false,
795 }
796 }
797
798 fn toggle_visibility(&self) -> Self {
799 match *self {
800 Self::Enabled {
801 toggle_override,
802 setting_configuration,
803 } => Self::Enabled {
804 setting_configuration,
805 toggle_override: !toggle_override,
806 },
807 Self::Disabled => Self::Disabled,
808 }
809 }
810}
811
812#[derive(Clone, Debug)]
813struct RunnableTasks {
814 templates: Vec<(TaskSourceKind, TaskTemplate)>,
815 offset: multi_buffer::Anchor,
816 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
817 column: u32,
818 // Values of all named captures, including those starting with '_'
819 extra_variables: HashMap<String, String>,
820 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
821 context_range: Range<BufferOffset>,
822}
823
824impl RunnableTasks {
825 fn resolve<'a>(
826 &'a self,
827 cx: &'a task::TaskContext,
828 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
829 self.templates.iter().filter_map(|(kind, template)| {
830 template
831 .resolve_task(&kind.to_id_base(), cx)
832 .map(|task| (kind.clone(), task))
833 })
834 }
835}
836
837#[derive(Clone)]
838pub struct ResolvedTasks {
839 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
840 position: Anchor,
841}
842
843#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
844struct BufferOffset(usize);
845
846/// Addons allow storing per-editor state in other crates (e.g. Vim)
847pub trait Addon: 'static {
848 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
849
850 fn render_buffer_header_controls(
851 &self,
852 _: &ExcerptInfo,
853 _: &Window,
854 _: &App,
855 ) -> Option<AnyElement> {
856 None
857 }
858
859 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
860 None
861 }
862
863 fn to_any(&self) -> &dyn std::any::Any;
864
865 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
866 None
867 }
868}
869
870struct ChangeLocation {
871 current: Option<Vec<Anchor>>,
872 original: Vec<Anchor>,
873}
874impl ChangeLocation {
875 fn locations(&self) -> &[Anchor] {
876 self.current.as_ref().unwrap_or(&self.original)
877 }
878}
879
880/// A set of caret positions, registered when the editor was edited.
881pub struct ChangeList {
882 changes: Vec<ChangeLocation>,
883 /// Currently "selected" change.
884 position: Option<usize>,
885}
886
887impl ChangeList {
888 pub fn new() -> Self {
889 Self {
890 changes: Vec::new(),
891 position: None,
892 }
893 }
894
895 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
896 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
897 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
898 if self.changes.is_empty() {
899 return None;
900 }
901
902 let prev = self.position.unwrap_or(self.changes.len());
903 let next = if direction == Direction::Prev {
904 prev.saturating_sub(count)
905 } else {
906 (prev + count).min(self.changes.len() - 1)
907 };
908 self.position = Some(next);
909 self.changes.get(next).map(|change| change.locations())
910 }
911
912 /// Adds a new change to the list, resetting the change list position.
913 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
914 self.position.take();
915 if let Some(last) = self.changes.last_mut()
916 && group
917 {
918 last.current = Some(new_positions)
919 } else {
920 self.changes.push(ChangeLocation {
921 original: new_positions,
922 current: None,
923 });
924 }
925 }
926
927 pub fn last(&self) -> Option<&[Anchor]> {
928 self.changes.last().map(|change| change.locations())
929 }
930
931 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
932 self.changes.last().map(|change| change.original.as_slice())
933 }
934
935 pub fn invert_last_group(&mut self) {
936 if let Some(last) = self.changes.last_mut()
937 && let Some(current) = last.current.as_mut()
938 {
939 mem::swap(&mut last.original, current);
940 }
941 }
942}
943
944#[derive(Clone)]
945struct InlineBlamePopoverState {
946 scroll_handle: ScrollHandle,
947 commit_message: Option<ParsedCommitMessage>,
948 markdown: Entity<Markdown>,
949}
950
951struct InlineBlamePopover {
952 position: gpui::Point<Pixels>,
953 hide_task: Option<Task<()>>,
954 popover_bounds: Option<Bounds<Pixels>>,
955 popover_state: InlineBlamePopoverState,
956 keyboard_grace: bool,
957}
958
959enum SelectionDragState {
960 /// State when no drag related activity is detected.
961 None,
962 /// State when the mouse is down on a selection that is about to be dragged.
963 ReadyToDrag {
964 selection: Selection<Anchor>,
965 click_position: gpui::Point<Pixels>,
966 mouse_down_time: Instant,
967 },
968 /// State when the mouse is dragging the selection in the editor.
969 Dragging {
970 selection: Selection<Anchor>,
971 drop_cursor: Selection<Anchor>,
972 hide_drop_cursor: bool,
973 },
974}
975
976enum ColumnarSelectionState {
977 FromMouse {
978 selection_tail: Anchor,
979 display_point: Option<DisplayPoint>,
980 },
981 FromSelection {
982 selection_tail: Anchor,
983 },
984}
985
986/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
987/// a breakpoint on them.
988#[derive(Clone, Copy, Debug, PartialEq, Eq)]
989struct PhantomBreakpointIndicator {
990 display_row: DisplayRow,
991 /// There's a small debounce between hovering over the line and showing the indicator.
992 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
993 is_active: bool,
994 collides_with_existing_breakpoint: bool,
995}
996
997/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
998///
999/// See the [module level documentation](self) for more information.
1000pub struct Editor {
1001 focus_handle: FocusHandle,
1002 last_focused_descendant: Option<WeakFocusHandle>,
1003 /// The text buffer being edited
1004 buffer: Entity<MultiBuffer>,
1005 /// Map of how text in the buffer should be displayed.
1006 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1007 pub display_map: Entity<DisplayMap>,
1008 placeholder_display_map: Option<Entity<DisplayMap>>,
1009 pub selections: SelectionsCollection,
1010 pub scroll_manager: ScrollManager,
1011 /// When inline assist editors are linked, they all render cursors because
1012 /// typing enters text into each of them, even the ones that aren't focused.
1013 pub(crate) show_cursor_when_unfocused: bool,
1014 columnar_selection_state: Option<ColumnarSelectionState>,
1015 add_selections_state: Option<AddSelectionsState>,
1016 select_next_state: Option<SelectNextState>,
1017 select_prev_state: Option<SelectNextState>,
1018 selection_history: SelectionHistory,
1019 defer_selection_effects: bool,
1020 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1021 autoclose_regions: Vec<AutocloseRegion>,
1022 snippet_stack: InvalidationStack<SnippetState>,
1023 select_syntax_node_history: SelectSyntaxNodeHistory,
1024 ime_transaction: Option<TransactionId>,
1025 pub diagnostics_max_severity: DiagnosticSeverity,
1026 active_diagnostics: ActiveDiagnostic,
1027 show_inline_diagnostics: bool,
1028 inline_diagnostics_update: Task<()>,
1029 inline_diagnostics_enabled: bool,
1030 diagnostics_enabled: bool,
1031 word_completions_enabled: bool,
1032 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1033 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1034 hard_wrap: Option<usize>,
1035 project: Option<Entity<Project>>,
1036 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1037 completion_provider: Option<Rc<dyn CompletionProvider>>,
1038 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1039 blink_manager: Entity<BlinkManager>,
1040 show_cursor_names: bool,
1041 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1042 pub show_local_selections: bool,
1043 mode: EditorMode,
1044 show_breadcrumbs: bool,
1045 show_gutter: bool,
1046 show_scrollbars: ScrollbarAxes,
1047 minimap_visibility: MinimapVisibility,
1048 offset_content: bool,
1049 disable_expand_excerpt_buttons: bool,
1050 show_line_numbers: Option<bool>,
1051 use_relative_line_numbers: Option<bool>,
1052 show_git_diff_gutter: Option<bool>,
1053 show_code_actions: Option<bool>,
1054 show_runnables: Option<bool>,
1055 show_breakpoints: Option<bool>,
1056 show_wrap_guides: Option<bool>,
1057 show_indent_guides: Option<bool>,
1058 highlight_order: usize,
1059 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1060 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1061 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1062 scrollbar_marker_state: ScrollbarMarkerState,
1063 active_indent_guides_state: ActiveIndentGuidesState,
1064 nav_history: Option<ItemNavHistory>,
1065 context_menu: RefCell<Option<CodeContextMenu>>,
1066 context_menu_options: Option<ContextMenuOptions>,
1067 mouse_context_menu: Option<MouseContextMenu>,
1068 completion_tasks: Vec<(CompletionId, Task<()>)>,
1069 inline_blame_popover: Option<InlineBlamePopover>,
1070 inline_blame_popover_show_task: Option<Task<()>>,
1071 signature_help_state: SignatureHelpState,
1072 auto_signature_help: Option<bool>,
1073 find_all_references_task_sources: Vec<Anchor>,
1074 next_completion_id: CompletionId,
1075 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1076 code_actions_task: Option<Task<Result<()>>>,
1077 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1078 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1079 document_highlights_task: Option<Task<()>>,
1080 linked_editing_range_task: Option<Task<Option<()>>>,
1081 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1082 pending_rename: Option<RenameState>,
1083 searchable: bool,
1084 cursor_shape: CursorShape,
1085 current_line_highlight: Option<CurrentLineHighlight>,
1086 autoindent_mode: Option<AutoindentMode>,
1087 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1088 input_enabled: bool,
1089 use_modal_editing: bool,
1090 read_only: bool,
1091 leader_id: Option<CollaboratorId>,
1092 remote_id: Option<ViewId>,
1093 pub hover_state: HoverState,
1094 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1095 gutter_hovered: bool,
1096 hovered_link_state: Option<HoveredLinkState>,
1097 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1098 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1099 active_edit_prediction: Option<EditPredictionState>,
1100 /// Used to prevent flickering as the user types while the menu is open
1101 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1102 edit_prediction_settings: EditPredictionSettings,
1103 edit_predictions_hidden_for_vim_mode: bool,
1104 show_edit_predictions_override: Option<bool>,
1105 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1106 edit_prediction_preview: EditPredictionPreview,
1107 edit_prediction_indent_conflict: bool,
1108 edit_prediction_requires_modifier_in_indent_conflict: bool,
1109 next_inlay_id: usize,
1110 next_color_inlay_id: usize,
1111 _subscriptions: Vec<Subscription>,
1112 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1113 gutter_dimensions: GutterDimensions,
1114 style: Option<EditorStyle>,
1115 text_style_refinement: Option<TextStyleRefinement>,
1116 next_editor_action_id: EditorActionId,
1117 editor_actions: Rc<
1118 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1119 >,
1120 use_autoclose: bool,
1121 use_auto_surround: bool,
1122 auto_replace_emoji_shortcode: bool,
1123 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1124 show_git_blame_gutter: bool,
1125 show_git_blame_inline: bool,
1126 show_git_blame_inline_delay_task: Option<Task<()>>,
1127 git_blame_inline_enabled: bool,
1128 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1129 serialize_dirty_buffers: bool,
1130 show_selection_menu: Option<bool>,
1131 blame: Option<Entity<GitBlame>>,
1132 blame_subscription: Option<Subscription>,
1133 custom_context_menu: Option<
1134 Box<
1135 dyn 'static
1136 + Fn(
1137 &mut Self,
1138 DisplayPoint,
1139 &mut Window,
1140 &mut Context<Self>,
1141 ) -> Option<Entity<ui::ContextMenu>>,
1142 >,
1143 >,
1144 last_bounds: Option<Bounds<Pixels>>,
1145 last_position_map: Option<Rc<PositionMap>>,
1146 expect_bounds_change: Option<Bounds<Pixels>>,
1147 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1148 tasks_update_task: Option<Task<()>>,
1149 breakpoint_store: Option<Entity<BreakpointStore>>,
1150 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1151 hovered_diff_hunk_row: Option<DisplayRow>,
1152 pull_diagnostics_task: Task<()>,
1153 in_project_search: bool,
1154 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1155 breadcrumb_header: Option<String>,
1156 focused_block: Option<FocusedBlock>,
1157 next_scroll_position: NextScrollCursorCenterTopBottom,
1158 addons: HashMap<TypeId, Box<dyn Addon>>,
1159 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1160 load_diff_task: Option<Shared<Task<()>>>,
1161 /// Whether we are temporarily displaying a diff other than git's
1162 temporary_diff_override: bool,
1163 selection_mark_mode: bool,
1164 toggle_fold_multiple_buffers: Task<()>,
1165 _scroll_cursor_center_top_bottom_task: Task<()>,
1166 serialize_selections: Task<()>,
1167 serialize_folds: Task<()>,
1168 mouse_cursor_hidden: bool,
1169 minimap: Option<Entity<Self>>,
1170 hide_mouse_mode: HideMouseMode,
1171 pub change_list: ChangeList,
1172 inline_value_cache: InlineValueCache,
1173 selection_drag_state: SelectionDragState,
1174 colors: Option<LspColorData>,
1175 post_scroll_update: Task<()>,
1176 refresh_colors_task: Task<()>,
1177 inlay_hints: Option<LspInlayHintData>,
1178 folding_newlines: Task<()>,
1179 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1180}
1181
1182fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1183 if debounce_ms > 0 {
1184 Some(Duration::from_millis(debounce_ms))
1185 } else {
1186 None
1187 }
1188}
1189
1190#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1191enum NextScrollCursorCenterTopBottom {
1192 #[default]
1193 Center,
1194 Top,
1195 Bottom,
1196}
1197
1198impl NextScrollCursorCenterTopBottom {
1199 fn next(&self) -> Self {
1200 match self {
1201 Self::Center => Self::Top,
1202 Self::Top => Self::Bottom,
1203 Self::Bottom => Self::Center,
1204 }
1205 }
1206}
1207
1208#[derive(Clone)]
1209pub struct EditorSnapshot {
1210 pub mode: EditorMode,
1211 show_gutter: bool,
1212 show_line_numbers: Option<bool>,
1213 show_git_diff_gutter: Option<bool>,
1214 show_code_actions: Option<bool>,
1215 show_runnables: Option<bool>,
1216 show_breakpoints: Option<bool>,
1217 git_blame_gutter_max_author_length: Option<usize>,
1218 pub display_snapshot: DisplaySnapshot,
1219 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1220 is_focused: bool,
1221 scroll_anchor: ScrollAnchor,
1222 ongoing_scroll: OngoingScroll,
1223 current_line_highlight: CurrentLineHighlight,
1224 gutter_hovered: bool,
1225}
1226
1227#[derive(Default, Debug, Clone, Copy)]
1228pub struct GutterDimensions {
1229 pub left_padding: Pixels,
1230 pub right_padding: Pixels,
1231 pub width: Pixels,
1232 pub margin: Pixels,
1233 pub git_blame_entries_width: Option<Pixels>,
1234}
1235
1236impl GutterDimensions {
1237 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1238 Self {
1239 margin: Self::default_gutter_margin(font_id, font_size, cx),
1240 ..Default::default()
1241 }
1242 }
1243
1244 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1245 -cx.text_system().descent(font_id, font_size)
1246 }
1247 /// The full width of the space taken up by the gutter.
1248 pub fn full_width(&self) -> Pixels {
1249 self.margin + self.width
1250 }
1251
1252 /// The width of the space reserved for the fold indicators,
1253 /// use alongside 'justify_end' and `gutter_width` to
1254 /// right align content with the line numbers
1255 pub fn fold_area_width(&self) -> Pixels {
1256 self.margin + self.right_padding
1257 }
1258}
1259
1260struct CharacterDimensions {
1261 em_width: Pixels,
1262 em_advance: Pixels,
1263 line_height: Pixels,
1264}
1265
1266#[derive(Debug)]
1267pub struct RemoteSelection {
1268 pub replica_id: ReplicaId,
1269 pub selection: Selection<Anchor>,
1270 pub cursor_shape: CursorShape,
1271 pub collaborator_id: CollaboratorId,
1272 pub line_mode: bool,
1273 pub user_name: Option<SharedString>,
1274 pub color: PlayerColor,
1275}
1276
1277#[derive(Clone, Debug)]
1278struct SelectionHistoryEntry {
1279 selections: Arc<[Selection<Anchor>]>,
1280 select_next_state: Option<SelectNextState>,
1281 select_prev_state: Option<SelectNextState>,
1282 add_selections_state: Option<AddSelectionsState>,
1283}
1284
1285#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1286enum SelectionHistoryMode {
1287 Normal,
1288 Undoing,
1289 Redoing,
1290 Skipping,
1291}
1292
1293#[derive(Clone, PartialEq, Eq, Hash)]
1294struct HoveredCursor {
1295 replica_id: ReplicaId,
1296 selection_id: usize,
1297}
1298
1299impl Default for SelectionHistoryMode {
1300 fn default() -> Self {
1301 Self::Normal
1302 }
1303}
1304
1305#[derive(Debug)]
1306/// SelectionEffects controls the side-effects of updating the selection.
1307///
1308/// The default behaviour does "what you mostly want":
1309/// - it pushes to the nav history if the cursor moved by >10 lines
1310/// - it re-triggers completion requests
1311/// - it scrolls to fit
1312///
1313/// You might want to modify these behaviours. For example when doing a "jump"
1314/// like go to definition, we always want to add to nav history; but when scrolling
1315/// in vim mode we never do.
1316///
1317/// Similarly, you might want to disable scrolling if you don't want the viewport to
1318/// move.
1319#[derive(Clone)]
1320pub struct SelectionEffects {
1321 nav_history: Option<bool>,
1322 completions: bool,
1323 scroll: Option<Autoscroll>,
1324}
1325
1326impl Default for SelectionEffects {
1327 fn default() -> Self {
1328 Self {
1329 nav_history: None,
1330 completions: true,
1331 scroll: Some(Autoscroll::fit()),
1332 }
1333 }
1334}
1335impl SelectionEffects {
1336 pub fn scroll(scroll: Autoscroll) -> Self {
1337 Self {
1338 scroll: Some(scroll),
1339 ..Default::default()
1340 }
1341 }
1342
1343 pub fn no_scroll() -> Self {
1344 Self {
1345 scroll: None,
1346 ..Default::default()
1347 }
1348 }
1349
1350 pub fn completions(self, completions: bool) -> Self {
1351 Self {
1352 completions,
1353 ..self
1354 }
1355 }
1356
1357 pub fn nav_history(self, nav_history: bool) -> Self {
1358 Self {
1359 nav_history: Some(nav_history),
1360 ..self
1361 }
1362 }
1363}
1364
1365struct DeferredSelectionEffectsState {
1366 changed: bool,
1367 effects: SelectionEffects,
1368 old_cursor_position: Anchor,
1369 history_entry: SelectionHistoryEntry,
1370}
1371
1372#[derive(Default)]
1373struct SelectionHistory {
1374 #[allow(clippy::type_complexity)]
1375 selections_by_transaction:
1376 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1377 mode: SelectionHistoryMode,
1378 undo_stack: VecDeque<SelectionHistoryEntry>,
1379 redo_stack: VecDeque<SelectionHistoryEntry>,
1380}
1381
1382impl SelectionHistory {
1383 #[track_caller]
1384 fn insert_transaction(
1385 &mut self,
1386 transaction_id: TransactionId,
1387 selections: Arc<[Selection<Anchor>]>,
1388 ) {
1389 if selections.is_empty() {
1390 log::error!(
1391 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1392 std::panic::Location::caller()
1393 );
1394 return;
1395 }
1396 self.selections_by_transaction
1397 .insert(transaction_id, (selections, None));
1398 }
1399
1400 #[allow(clippy::type_complexity)]
1401 fn transaction(
1402 &self,
1403 transaction_id: TransactionId,
1404 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1405 self.selections_by_transaction.get(&transaction_id)
1406 }
1407
1408 #[allow(clippy::type_complexity)]
1409 fn transaction_mut(
1410 &mut self,
1411 transaction_id: TransactionId,
1412 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1413 self.selections_by_transaction.get_mut(&transaction_id)
1414 }
1415
1416 fn push(&mut self, entry: SelectionHistoryEntry) {
1417 if !entry.selections.is_empty() {
1418 match self.mode {
1419 SelectionHistoryMode::Normal => {
1420 self.push_undo(entry);
1421 self.redo_stack.clear();
1422 }
1423 SelectionHistoryMode::Undoing => self.push_redo(entry),
1424 SelectionHistoryMode::Redoing => self.push_undo(entry),
1425 SelectionHistoryMode::Skipping => {}
1426 }
1427 }
1428 }
1429
1430 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1431 if self
1432 .undo_stack
1433 .back()
1434 .is_none_or(|e| e.selections != entry.selections)
1435 {
1436 self.undo_stack.push_back(entry);
1437 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1438 self.undo_stack.pop_front();
1439 }
1440 }
1441 }
1442
1443 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1444 if self
1445 .redo_stack
1446 .back()
1447 .is_none_or(|e| e.selections != entry.selections)
1448 {
1449 self.redo_stack.push_back(entry);
1450 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1451 self.redo_stack.pop_front();
1452 }
1453 }
1454 }
1455}
1456
1457#[derive(Clone, Copy)]
1458pub struct RowHighlightOptions {
1459 pub autoscroll: bool,
1460 pub include_gutter: bool,
1461}
1462
1463impl Default for RowHighlightOptions {
1464 fn default() -> Self {
1465 Self {
1466 autoscroll: Default::default(),
1467 include_gutter: true,
1468 }
1469 }
1470}
1471
1472struct RowHighlight {
1473 index: usize,
1474 range: Range<Anchor>,
1475 color: Hsla,
1476 options: RowHighlightOptions,
1477 type_id: TypeId,
1478}
1479
1480#[derive(Clone, Debug)]
1481struct AddSelectionsState {
1482 groups: Vec<AddSelectionsGroup>,
1483}
1484
1485#[derive(Clone, Debug)]
1486struct AddSelectionsGroup {
1487 above: bool,
1488 stack: Vec<usize>,
1489}
1490
1491#[derive(Clone)]
1492struct SelectNextState {
1493 query: AhoCorasick,
1494 wordwise: bool,
1495 done: bool,
1496}
1497
1498impl std::fmt::Debug for SelectNextState {
1499 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1500 f.debug_struct(std::any::type_name::<Self>())
1501 .field("wordwise", &self.wordwise)
1502 .field("done", &self.done)
1503 .finish()
1504 }
1505}
1506
1507#[derive(Debug)]
1508struct AutocloseRegion {
1509 selection_id: usize,
1510 range: Range<Anchor>,
1511 pair: BracketPair,
1512}
1513
1514#[derive(Debug)]
1515struct SnippetState {
1516 ranges: Vec<Vec<Range<Anchor>>>,
1517 active_index: usize,
1518 choices: Vec<Option<Vec<String>>>,
1519}
1520
1521#[doc(hidden)]
1522pub struct RenameState {
1523 pub range: Range<Anchor>,
1524 pub old_name: Arc<str>,
1525 pub editor: Entity<Editor>,
1526 block_id: CustomBlockId,
1527}
1528
1529struct InvalidationStack<T>(Vec<T>);
1530
1531struct RegisteredEditPredictionProvider {
1532 provider: Arc<dyn EditPredictionProviderHandle>,
1533 _subscription: Subscription,
1534}
1535
1536#[derive(Debug, PartialEq, Eq)]
1537pub struct ActiveDiagnosticGroup {
1538 pub active_range: Range<Anchor>,
1539 pub active_message: String,
1540 pub group_id: usize,
1541 pub blocks: HashSet<CustomBlockId>,
1542}
1543
1544#[derive(Debug, PartialEq, Eq)]
1545
1546pub(crate) enum ActiveDiagnostic {
1547 None,
1548 All,
1549 Group(ActiveDiagnosticGroup),
1550}
1551
1552#[derive(Serialize, Deserialize, Clone, Debug)]
1553pub struct ClipboardSelection {
1554 /// The number of bytes in this selection.
1555 pub len: usize,
1556 /// Whether this was a full-line selection.
1557 pub is_entire_line: bool,
1558 /// The indentation of the first line when this content was originally copied.
1559 pub first_line_indent: u32,
1560}
1561
1562// selections, scroll behavior, was newest selection reversed
1563type SelectSyntaxNodeHistoryState = (
1564 Box<[Selection<usize>]>,
1565 SelectSyntaxNodeScrollBehavior,
1566 bool,
1567);
1568
1569#[derive(Default)]
1570struct SelectSyntaxNodeHistory {
1571 stack: Vec<SelectSyntaxNodeHistoryState>,
1572 // disable temporarily to allow changing selections without losing the stack
1573 pub disable_clearing: bool,
1574}
1575
1576impl SelectSyntaxNodeHistory {
1577 pub fn try_clear(&mut self) {
1578 if !self.disable_clearing {
1579 self.stack.clear();
1580 }
1581 }
1582
1583 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1584 self.stack.push(selection);
1585 }
1586
1587 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1588 self.stack.pop()
1589 }
1590}
1591
1592enum SelectSyntaxNodeScrollBehavior {
1593 CursorTop,
1594 FitSelection,
1595 CursorBottom,
1596}
1597
1598#[derive(Debug)]
1599pub(crate) struct NavigationData {
1600 cursor_anchor: Anchor,
1601 cursor_position: Point,
1602 scroll_anchor: ScrollAnchor,
1603 scroll_top_row: u32,
1604}
1605
1606#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1607pub enum GotoDefinitionKind {
1608 Symbol,
1609 Declaration,
1610 Type,
1611 Implementation,
1612}
1613
1614pub enum FormatTarget {
1615 Buffers(HashSet<Entity<Buffer>>),
1616 Ranges(Vec<Range<MultiBufferPoint>>),
1617}
1618
1619pub(crate) struct FocusedBlock {
1620 id: BlockId,
1621 focus_handle: WeakFocusHandle,
1622}
1623
1624#[derive(Clone)]
1625enum JumpData {
1626 MultiBufferRow {
1627 row: MultiBufferRow,
1628 line_offset_from_top: u32,
1629 },
1630 MultiBufferPoint {
1631 excerpt_id: ExcerptId,
1632 position: Point,
1633 anchor: text::Anchor,
1634 line_offset_from_top: u32,
1635 },
1636}
1637
1638pub enum MultibufferSelectionMode {
1639 First,
1640 All,
1641}
1642
1643#[derive(Clone, Copy, Debug, Default)]
1644pub struct RewrapOptions {
1645 pub override_language_settings: bool,
1646 pub preserve_existing_whitespace: bool,
1647}
1648
1649impl Editor {
1650 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1651 let buffer = cx.new(|cx| Buffer::local("", cx));
1652 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1653 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1654 }
1655
1656 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1657 let buffer = cx.new(|cx| Buffer::local("", cx));
1658 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1659 Self::new(EditorMode::full(), buffer, None, window, cx)
1660 }
1661
1662 pub fn auto_height(
1663 min_lines: usize,
1664 max_lines: usize,
1665 window: &mut Window,
1666 cx: &mut Context<Self>,
1667 ) -> Self {
1668 let buffer = cx.new(|cx| Buffer::local("", cx));
1669 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1670 Self::new(
1671 EditorMode::AutoHeight {
1672 min_lines,
1673 max_lines: Some(max_lines),
1674 },
1675 buffer,
1676 None,
1677 window,
1678 cx,
1679 )
1680 }
1681
1682 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1683 /// The editor grows as tall as needed to fit its content.
1684 pub fn auto_height_unbounded(
1685 min_lines: usize,
1686 window: &mut Window,
1687 cx: &mut Context<Self>,
1688 ) -> Self {
1689 let buffer = cx.new(|cx| Buffer::local("", cx));
1690 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1691 Self::new(
1692 EditorMode::AutoHeight {
1693 min_lines,
1694 max_lines: None,
1695 },
1696 buffer,
1697 None,
1698 window,
1699 cx,
1700 )
1701 }
1702
1703 pub fn for_buffer(
1704 buffer: Entity<Buffer>,
1705 project: Option<Entity<Project>>,
1706 window: &mut Window,
1707 cx: &mut Context<Self>,
1708 ) -> Self {
1709 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1710 Self::new(EditorMode::full(), buffer, project, window, cx)
1711 }
1712
1713 pub fn for_multibuffer(
1714 buffer: Entity<MultiBuffer>,
1715 project: Option<Entity<Project>>,
1716 window: &mut Window,
1717 cx: &mut Context<Self>,
1718 ) -> Self {
1719 Self::new(EditorMode::full(), buffer, project, window, cx)
1720 }
1721
1722 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1723 let mut clone = Self::new(
1724 self.mode.clone(),
1725 self.buffer.clone(),
1726 self.project.clone(),
1727 window,
1728 cx,
1729 );
1730 self.display_map.update(cx, |display_map, cx| {
1731 let snapshot = display_map.snapshot(cx);
1732 clone.display_map.update(cx, |display_map, cx| {
1733 display_map.set_state(&snapshot, cx);
1734 });
1735 });
1736 clone.folds_did_change(cx);
1737 clone.selections.clone_state(&self.selections);
1738 clone.scroll_manager.clone_state(&self.scroll_manager);
1739 clone.searchable = self.searchable;
1740 clone.read_only = self.read_only;
1741 clone
1742 }
1743
1744 pub fn new(
1745 mode: EditorMode,
1746 buffer: Entity<MultiBuffer>,
1747 project: Option<Entity<Project>>,
1748 window: &mut Window,
1749 cx: &mut Context<Self>,
1750 ) -> Self {
1751 Editor::new_internal(mode, buffer, project, None, window, cx)
1752 }
1753
1754 fn new_internal(
1755 mode: EditorMode,
1756 multi_buffer: Entity<MultiBuffer>,
1757 project: Option<Entity<Project>>,
1758 display_map: Option<Entity<DisplayMap>>,
1759 window: &mut Window,
1760 cx: &mut Context<Self>,
1761 ) -> Self {
1762 debug_assert!(
1763 display_map.is_none() || mode.is_minimap(),
1764 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1765 );
1766
1767 let full_mode = mode.is_full();
1768 let is_minimap = mode.is_minimap();
1769 let diagnostics_max_severity = if full_mode {
1770 EditorSettings::get_global(cx)
1771 .diagnostics_max_severity
1772 .unwrap_or(DiagnosticSeverity::Hint)
1773 } else {
1774 DiagnosticSeverity::Off
1775 };
1776 let style = window.text_style();
1777 let font_size = style.font_size.to_pixels(window.rem_size());
1778 let editor = cx.entity().downgrade();
1779 let fold_placeholder = FoldPlaceholder {
1780 constrain_width: false,
1781 render: Arc::new(move |fold_id, fold_range, cx| {
1782 let editor = editor.clone();
1783 div()
1784 .id(fold_id)
1785 .bg(cx.theme().colors().ghost_element_background)
1786 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1787 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1788 .rounded_xs()
1789 .size_full()
1790 .cursor_pointer()
1791 .child("⋯")
1792 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1793 .on_click(move |_, _window, cx| {
1794 editor
1795 .update(cx, |editor, cx| {
1796 editor.unfold_ranges(
1797 &[fold_range.start..fold_range.end],
1798 true,
1799 false,
1800 cx,
1801 );
1802 cx.stop_propagation();
1803 })
1804 .ok();
1805 })
1806 .into_any()
1807 }),
1808 merge_adjacent: true,
1809 ..FoldPlaceholder::default()
1810 };
1811 let display_map = display_map.unwrap_or_else(|| {
1812 cx.new(|cx| {
1813 DisplayMap::new(
1814 multi_buffer.clone(),
1815 style.font(),
1816 font_size,
1817 None,
1818 FILE_HEADER_HEIGHT,
1819 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1820 fold_placeholder,
1821 diagnostics_max_severity,
1822 cx,
1823 )
1824 })
1825 });
1826
1827 let selections = SelectionsCollection::new(display_map.clone(), multi_buffer.clone());
1828
1829 let blink_manager = cx.new(|cx| {
1830 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1831 if is_minimap {
1832 blink_manager.disable(cx);
1833 }
1834 blink_manager
1835 });
1836
1837 let soft_wrap_mode_override =
1838 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1839
1840 let mut project_subscriptions = Vec::new();
1841 if full_mode && let Some(project) = project.as_ref() {
1842 project_subscriptions.push(cx.subscribe_in(
1843 project,
1844 window,
1845 |editor, _, event, window, cx| match event {
1846 project::Event::RefreshCodeLens => {
1847 // we always query lens with actions, without storing them, always refreshing them
1848 }
1849 project::Event::RefreshInlayHints {
1850 server_id,
1851 request_id,
1852 } => {
1853 editor.refresh_inlay_hints(
1854 InlayHintRefreshReason::RefreshRequested {
1855 server_id: *server_id,
1856 request_id: *request_id,
1857 },
1858 cx,
1859 );
1860 }
1861 project::Event::LanguageServerRemoved(..) => {
1862 if editor.tasks_update_task.is_none() {
1863 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1864 }
1865 editor.registered_buffers.clear();
1866 editor.register_visible_buffers(cx);
1867 }
1868 project::Event::LanguageServerAdded(..) => {
1869 if editor.tasks_update_task.is_none() {
1870 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1871 }
1872 }
1873 project::Event::SnippetEdit(id, snippet_edits) => {
1874 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1875 let focus_handle = editor.focus_handle(cx);
1876 if focus_handle.is_focused(window) {
1877 let snapshot = buffer.read(cx).snapshot();
1878 for (range, snippet) in snippet_edits {
1879 let editor_range =
1880 language::range_from_lsp(*range).to_offset(&snapshot);
1881 editor
1882 .insert_snippet(
1883 &[editor_range],
1884 snippet.clone(),
1885 window,
1886 cx,
1887 )
1888 .ok();
1889 }
1890 }
1891 }
1892 }
1893 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1894 let buffer_id = *buffer_id;
1895 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1896 editor.register_buffer(buffer_id, cx);
1897 editor.update_lsp_data(Some(buffer_id), window, cx);
1898 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1899 refresh_linked_ranges(editor, window, cx);
1900 editor.refresh_code_actions(window, cx);
1901 editor.refresh_document_highlights(cx);
1902 }
1903 }
1904
1905 project::Event::EntryRenamed(transaction) => {
1906 let Some(workspace) = editor.workspace() else {
1907 return;
1908 };
1909 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1910 else {
1911 return;
1912 };
1913 if active_editor.entity_id() == cx.entity_id() {
1914 let edited_buffers_already_open = {
1915 let other_editors: Vec<Entity<Editor>> = workspace
1916 .read(cx)
1917 .panes()
1918 .iter()
1919 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1920 .filter(|editor| editor.entity_id() != cx.entity_id())
1921 .collect();
1922
1923 transaction.0.keys().all(|buffer| {
1924 other_editors.iter().any(|editor| {
1925 let multi_buffer = editor.read(cx).buffer();
1926 multi_buffer.read(cx).is_singleton()
1927 && multi_buffer.read(cx).as_singleton().map_or(
1928 false,
1929 |singleton| {
1930 singleton.entity_id() == buffer.entity_id()
1931 },
1932 )
1933 })
1934 })
1935 };
1936
1937 if !edited_buffers_already_open {
1938 let workspace = workspace.downgrade();
1939 let transaction = transaction.clone();
1940 cx.defer_in(window, move |_, window, cx| {
1941 cx.spawn_in(window, async move |editor, cx| {
1942 Self::open_project_transaction(
1943 &editor,
1944 workspace,
1945 transaction,
1946 "Rename".to_string(),
1947 cx,
1948 )
1949 .await
1950 .ok()
1951 })
1952 .detach();
1953 });
1954 }
1955 }
1956 }
1957
1958 _ => {}
1959 },
1960 ));
1961 if let Some(task_inventory) = project
1962 .read(cx)
1963 .task_store()
1964 .read(cx)
1965 .task_inventory()
1966 .cloned()
1967 {
1968 project_subscriptions.push(cx.observe_in(
1969 &task_inventory,
1970 window,
1971 |editor, _, window, cx| {
1972 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1973 },
1974 ));
1975 };
1976
1977 project_subscriptions.push(cx.subscribe_in(
1978 &project.read(cx).breakpoint_store(),
1979 window,
1980 |editor, _, event, window, cx| match event {
1981 BreakpointStoreEvent::ClearDebugLines => {
1982 editor.clear_row_highlights::<ActiveDebugLine>();
1983 editor.refresh_inline_values(cx);
1984 }
1985 BreakpointStoreEvent::SetDebugLine => {
1986 if editor.go_to_active_debug_line(window, cx) {
1987 cx.stop_propagation();
1988 }
1989
1990 editor.refresh_inline_values(cx);
1991 }
1992 _ => {}
1993 },
1994 ));
1995 let git_store = project.read(cx).git_store().clone();
1996 let project = project.clone();
1997 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1998 if let GitStoreEvent::RepositoryAdded = event {
1999 this.load_diff_task = Some(
2000 update_uncommitted_diff_for_buffer(
2001 cx.entity(),
2002 &project,
2003 this.buffer.read(cx).all_buffers(),
2004 this.buffer.clone(),
2005 cx,
2006 )
2007 .shared(),
2008 );
2009 }
2010 }));
2011 }
2012
2013 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2014
2015 let inlay_hint_settings =
2016 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2017 let focus_handle = cx.focus_handle();
2018 if !is_minimap {
2019 cx.on_focus(&focus_handle, window, Self::handle_focus)
2020 .detach();
2021 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2022 .detach();
2023 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2024 .detach();
2025 cx.on_blur(&focus_handle, window, Self::handle_blur)
2026 .detach();
2027 cx.observe_pending_input(window, Self::observe_pending_input)
2028 .detach();
2029 }
2030
2031 let show_indent_guides =
2032 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2033 Some(false)
2034 } else {
2035 None
2036 };
2037
2038 let breakpoint_store = match (&mode, project.as_ref()) {
2039 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2040 _ => None,
2041 };
2042
2043 let mut code_action_providers = Vec::new();
2044 let mut load_uncommitted_diff = None;
2045 if let Some(project) = project.clone() {
2046 load_uncommitted_diff = Some(
2047 update_uncommitted_diff_for_buffer(
2048 cx.entity(),
2049 &project,
2050 multi_buffer.read(cx).all_buffers(),
2051 multi_buffer.clone(),
2052 cx,
2053 )
2054 .shared(),
2055 );
2056 code_action_providers.push(Rc::new(project) as Rc<_>);
2057 }
2058
2059 let mut editor = Self {
2060 focus_handle,
2061 show_cursor_when_unfocused: false,
2062 last_focused_descendant: None,
2063 buffer: multi_buffer.clone(),
2064 display_map: display_map.clone(),
2065 placeholder_display_map: None,
2066 selections,
2067 scroll_manager: ScrollManager::new(cx),
2068 columnar_selection_state: None,
2069 add_selections_state: None,
2070 select_next_state: None,
2071 select_prev_state: None,
2072 selection_history: SelectionHistory::default(),
2073 defer_selection_effects: false,
2074 deferred_selection_effects_state: None,
2075 autoclose_regions: Vec::new(),
2076 snippet_stack: InvalidationStack::default(),
2077 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2078 ime_transaction: None,
2079 active_diagnostics: ActiveDiagnostic::None,
2080 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2081 inline_diagnostics_update: Task::ready(()),
2082 inline_diagnostics: Vec::new(),
2083 soft_wrap_mode_override,
2084 diagnostics_max_severity,
2085 hard_wrap: None,
2086 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2087 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2088 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2089 project,
2090 blink_manager: blink_manager.clone(),
2091 show_local_selections: true,
2092 show_scrollbars: ScrollbarAxes {
2093 horizontal: full_mode,
2094 vertical: full_mode,
2095 },
2096 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2097 offset_content: !matches!(mode, EditorMode::SingleLine),
2098 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2099 show_gutter: full_mode,
2100 show_line_numbers: (!full_mode).then_some(false),
2101 use_relative_line_numbers: None,
2102 disable_expand_excerpt_buttons: !full_mode,
2103 show_git_diff_gutter: None,
2104 show_code_actions: None,
2105 show_runnables: None,
2106 show_breakpoints: None,
2107 show_wrap_guides: None,
2108 show_indent_guides,
2109 highlight_order: 0,
2110 highlighted_rows: HashMap::default(),
2111 background_highlights: HashMap::default(),
2112 gutter_highlights: HashMap::default(),
2113 scrollbar_marker_state: ScrollbarMarkerState::default(),
2114 active_indent_guides_state: ActiveIndentGuidesState::default(),
2115 nav_history: None,
2116 context_menu: RefCell::new(None),
2117 context_menu_options: None,
2118 mouse_context_menu: None,
2119 completion_tasks: Vec::new(),
2120 inline_blame_popover: None,
2121 inline_blame_popover_show_task: None,
2122 signature_help_state: SignatureHelpState::default(),
2123 auto_signature_help: None,
2124 find_all_references_task_sources: Vec::new(),
2125 next_completion_id: 0,
2126 next_inlay_id: 0,
2127 code_action_providers,
2128 available_code_actions: None,
2129 code_actions_task: None,
2130 quick_selection_highlight_task: None,
2131 debounced_selection_highlight_task: None,
2132 document_highlights_task: None,
2133 linked_editing_range_task: None,
2134 pending_rename: None,
2135 searchable: !is_minimap,
2136 cursor_shape: EditorSettings::get_global(cx)
2137 .cursor_shape
2138 .unwrap_or_default(),
2139 current_line_highlight: None,
2140 autoindent_mode: Some(AutoindentMode::EachLine),
2141
2142 workspace: None,
2143 input_enabled: !is_minimap,
2144 use_modal_editing: full_mode,
2145 read_only: is_minimap,
2146 use_autoclose: true,
2147 use_auto_surround: true,
2148 auto_replace_emoji_shortcode: false,
2149 jsx_tag_auto_close_enabled_in_any_buffer: false,
2150 leader_id: None,
2151 remote_id: None,
2152 hover_state: HoverState::default(),
2153 pending_mouse_down: None,
2154 hovered_link_state: None,
2155 edit_prediction_provider: None,
2156 active_edit_prediction: None,
2157 stale_edit_prediction_in_menu: None,
2158 edit_prediction_preview: EditPredictionPreview::Inactive {
2159 released_too_fast: false,
2160 },
2161 inline_diagnostics_enabled: full_mode,
2162 diagnostics_enabled: full_mode,
2163 word_completions_enabled: full_mode,
2164 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2165 gutter_hovered: false,
2166 pixel_position_of_newest_cursor: None,
2167 last_bounds: None,
2168 last_position_map: None,
2169 expect_bounds_change: None,
2170 gutter_dimensions: GutterDimensions::default(),
2171 style: None,
2172 show_cursor_names: false,
2173 hovered_cursors: HashMap::default(),
2174 next_editor_action_id: EditorActionId::default(),
2175 editor_actions: Rc::default(),
2176 edit_predictions_hidden_for_vim_mode: false,
2177 show_edit_predictions_override: None,
2178 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2179 edit_prediction_settings: EditPredictionSettings::Disabled,
2180 edit_prediction_indent_conflict: false,
2181 edit_prediction_requires_modifier_in_indent_conflict: true,
2182 custom_context_menu: None,
2183 show_git_blame_gutter: false,
2184 show_git_blame_inline: false,
2185 show_selection_menu: None,
2186 show_git_blame_inline_delay_task: None,
2187 git_blame_inline_enabled: full_mode
2188 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2189 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2190 serialize_dirty_buffers: !is_minimap
2191 && ProjectSettings::get_global(cx)
2192 .session
2193 .restore_unsaved_buffers,
2194 blame: None,
2195 blame_subscription: None,
2196 tasks: BTreeMap::default(),
2197
2198 breakpoint_store,
2199 gutter_breakpoint_indicator: (None, None),
2200 hovered_diff_hunk_row: None,
2201 _subscriptions: (!is_minimap)
2202 .then(|| {
2203 vec![
2204 cx.observe(&multi_buffer, Self::on_buffer_changed),
2205 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2206 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2207 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2208 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2209 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2210 cx.observe_window_activation(window, |editor, window, cx| {
2211 let active = window.is_window_active();
2212 editor.blink_manager.update(cx, |blink_manager, cx| {
2213 if active {
2214 blink_manager.enable(cx);
2215 } else {
2216 blink_manager.disable(cx);
2217 }
2218 });
2219 if active {
2220 editor.show_mouse_cursor(cx);
2221 }
2222 }),
2223 ]
2224 })
2225 .unwrap_or_default(),
2226 tasks_update_task: None,
2227 pull_diagnostics_task: Task::ready(()),
2228 colors: None,
2229 refresh_colors_task: Task::ready(()),
2230 inlay_hints: None,
2231 next_color_inlay_id: 0,
2232 post_scroll_update: Task::ready(()),
2233 linked_edit_ranges: Default::default(),
2234 in_project_search: false,
2235 previous_search_ranges: None,
2236 breadcrumb_header: None,
2237 focused_block: None,
2238 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2239 addons: HashMap::default(),
2240 registered_buffers: HashMap::default(),
2241 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2242 selection_mark_mode: false,
2243 toggle_fold_multiple_buffers: Task::ready(()),
2244 serialize_selections: Task::ready(()),
2245 serialize_folds: Task::ready(()),
2246 text_style_refinement: None,
2247 load_diff_task: load_uncommitted_diff,
2248 temporary_diff_override: false,
2249 mouse_cursor_hidden: false,
2250 minimap: None,
2251 hide_mouse_mode: EditorSettings::get_global(cx)
2252 .hide_mouse
2253 .unwrap_or_default(),
2254 change_list: ChangeList::new(),
2255 mode,
2256 selection_drag_state: SelectionDragState::None,
2257 folding_newlines: Task::ready(()),
2258 lookup_key: None,
2259 };
2260
2261 if is_minimap {
2262 return editor;
2263 }
2264
2265 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2266 editor
2267 ._subscriptions
2268 .push(cx.observe(breakpoints, |_, _, cx| {
2269 cx.notify();
2270 }));
2271 }
2272 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2273 editor._subscriptions.extend(project_subscriptions);
2274
2275 editor._subscriptions.push(cx.subscribe_in(
2276 &cx.entity(),
2277 window,
2278 |editor, _, e: &EditorEvent, window, cx| match e {
2279 EditorEvent::ScrollPositionChanged { local, .. } => {
2280 if *local {
2281 let new_anchor = editor.scroll_manager.anchor();
2282 let snapshot = editor.snapshot(window, cx);
2283 editor.update_restoration_data(cx, move |data| {
2284 data.scroll_position = (
2285 new_anchor.top_row(snapshot.buffer_snapshot()),
2286 new_anchor.offset,
2287 );
2288 });
2289 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2290 editor.inline_blame_popover.take();
2291 }
2292 }
2293 EditorEvent::Edited { .. } => {
2294 if vim_flavor(cx).is_none() {
2295 let display_map = editor.display_snapshot(cx);
2296 let selections = editor.selections.all_adjusted_display(&display_map);
2297 let pop_state = editor
2298 .change_list
2299 .last()
2300 .map(|previous| {
2301 previous.len() == selections.len()
2302 && previous.iter().enumerate().all(|(ix, p)| {
2303 p.to_display_point(&display_map).row()
2304 == selections[ix].head().row()
2305 })
2306 })
2307 .unwrap_or(false);
2308 let new_positions = selections
2309 .into_iter()
2310 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2311 .collect();
2312 editor
2313 .change_list
2314 .push_to_change_list(pop_state, new_positions);
2315 }
2316 }
2317 _ => (),
2318 },
2319 ));
2320
2321 if let Some(dap_store) = editor
2322 .project
2323 .as_ref()
2324 .map(|project| project.read(cx).dap_store())
2325 {
2326 let weak_editor = cx.weak_entity();
2327
2328 editor
2329 ._subscriptions
2330 .push(
2331 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2332 let session_entity = cx.entity();
2333 weak_editor
2334 .update(cx, |editor, cx| {
2335 editor._subscriptions.push(
2336 cx.subscribe(&session_entity, Self::on_debug_session_event),
2337 );
2338 })
2339 .ok();
2340 }),
2341 );
2342
2343 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2344 editor
2345 ._subscriptions
2346 .push(cx.subscribe(&session, Self::on_debug_session_event));
2347 }
2348 }
2349
2350 // skip adding the initial selection to selection history
2351 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2352 editor.end_selection(window, cx);
2353 editor.selection_history.mode = SelectionHistoryMode::Normal;
2354
2355 editor.scroll_manager.show_scrollbars(window, cx);
2356 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2357
2358 if full_mode {
2359 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2360 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2361
2362 if editor.git_blame_inline_enabled {
2363 editor.start_git_blame_inline(false, window, cx);
2364 }
2365
2366 editor.go_to_active_debug_line(window, cx);
2367
2368 editor.minimap =
2369 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2370 editor.colors = Some(LspColorData::new(cx));
2371 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2372
2373 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2374 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2375 }
2376 editor.update_lsp_data(None, window, cx);
2377 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2378 }
2379
2380 editor
2381 }
2382
2383 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2384 self.selections.display_map(cx)
2385 }
2386
2387 pub fn deploy_mouse_context_menu(
2388 &mut self,
2389 position: gpui::Point<Pixels>,
2390 context_menu: Entity<ContextMenu>,
2391 window: &mut Window,
2392 cx: &mut Context<Self>,
2393 ) {
2394 self.mouse_context_menu = Some(MouseContextMenu::new(
2395 self,
2396 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2397 context_menu,
2398 window,
2399 cx,
2400 ));
2401 }
2402
2403 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2404 self.mouse_context_menu
2405 .as_ref()
2406 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2407 }
2408
2409 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2410 if self
2411 .selections
2412 .pending_anchor()
2413 .is_some_and(|pending_selection| {
2414 let snapshot = self.buffer().read(cx).snapshot(cx);
2415 pending_selection.range().includes(range, &snapshot)
2416 })
2417 {
2418 return true;
2419 }
2420
2421 self.selections
2422 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2423 .into_iter()
2424 .any(|selection| {
2425 // This is needed to cover a corner case, if we just check for an existing
2426 // selection in the fold range, having a cursor at the start of the fold
2427 // marks it as selected. Non-empty selections don't cause this.
2428 let length = selection.end - selection.start;
2429 length > 0
2430 })
2431 }
2432
2433 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2434 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2435 }
2436
2437 fn key_context_internal(
2438 &self,
2439 has_active_edit_prediction: bool,
2440 window: &mut Window,
2441 cx: &mut App,
2442 ) -> KeyContext {
2443 let mut key_context = KeyContext::new_with_defaults();
2444 key_context.add("Editor");
2445 let mode = match self.mode {
2446 EditorMode::SingleLine => "single_line",
2447 EditorMode::AutoHeight { .. } => "auto_height",
2448 EditorMode::Minimap { .. } => "minimap",
2449 EditorMode::Full { .. } => "full",
2450 };
2451
2452 if EditorSettings::jupyter_enabled(cx) {
2453 key_context.add("jupyter");
2454 }
2455
2456 key_context.set("mode", mode);
2457 if self.pending_rename.is_some() {
2458 key_context.add("renaming");
2459 }
2460
2461 if !self.snippet_stack.is_empty() {
2462 key_context.add("in_snippet");
2463 }
2464
2465 match self.context_menu.borrow().as_ref() {
2466 Some(CodeContextMenu::Completions(menu)) => {
2467 if menu.visible() {
2468 key_context.add("menu");
2469 key_context.add("showing_completions");
2470 }
2471 }
2472 Some(CodeContextMenu::CodeActions(menu)) => {
2473 if menu.visible() {
2474 key_context.add("menu");
2475 key_context.add("showing_code_actions")
2476 }
2477 }
2478 None => {}
2479 }
2480
2481 if self.signature_help_state.has_multiple_signatures() {
2482 key_context.add("showing_signature_help");
2483 }
2484
2485 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2486 if !self.focus_handle(cx).contains_focused(window, cx)
2487 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2488 {
2489 for addon in self.addons.values() {
2490 addon.extend_key_context(&mut key_context, cx)
2491 }
2492 }
2493
2494 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2495 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2496 Some(
2497 file.full_path(cx)
2498 .extension()?
2499 .to_string_lossy()
2500 .into_owned(),
2501 )
2502 }) {
2503 key_context.set("extension", extension);
2504 }
2505 } else {
2506 key_context.add("multibuffer");
2507 }
2508
2509 if has_active_edit_prediction {
2510 if self.edit_prediction_in_conflict() {
2511 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2512 } else {
2513 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2514 key_context.add("copilot_suggestion");
2515 }
2516 }
2517
2518 if self.selection_mark_mode {
2519 key_context.add("selection_mode");
2520 }
2521
2522 let disjoint = self.selections.disjoint_anchors();
2523 let snapshot = self.snapshot(window, cx);
2524 let snapshot = snapshot.buffer_snapshot();
2525 if self.mode == EditorMode::SingleLine
2526 && let [selection] = disjoint
2527 && selection.start == selection.end
2528 && selection.end.to_offset(snapshot) == snapshot.len()
2529 {
2530 key_context.add("end_of_input");
2531 }
2532
2533 key_context
2534 }
2535
2536 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2537 self.last_bounds.as_ref()
2538 }
2539
2540 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2541 if self.mouse_cursor_hidden {
2542 self.mouse_cursor_hidden = false;
2543 cx.notify();
2544 }
2545 }
2546
2547 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2548 let hide_mouse_cursor = match origin {
2549 HideMouseCursorOrigin::TypingAction => {
2550 matches!(
2551 self.hide_mouse_mode,
2552 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2553 )
2554 }
2555 HideMouseCursorOrigin::MovementAction => {
2556 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2557 }
2558 };
2559 if self.mouse_cursor_hidden != hide_mouse_cursor {
2560 self.mouse_cursor_hidden = hide_mouse_cursor;
2561 cx.notify();
2562 }
2563 }
2564
2565 pub fn edit_prediction_in_conflict(&self) -> bool {
2566 if !self.show_edit_predictions_in_menu() {
2567 return false;
2568 }
2569
2570 let showing_completions = self
2571 .context_menu
2572 .borrow()
2573 .as_ref()
2574 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2575
2576 showing_completions
2577 || self.edit_prediction_requires_modifier()
2578 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2579 // bindings to insert tab characters.
2580 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2581 }
2582
2583 pub fn accept_edit_prediction_keybind(
2584 &self,
2585 accept_partial: bool,
2586 window: &mut Window,
2587 cx: &mut App,
2588 ) -> AcceptEditPredictionBinding {
2589 let key_context = self.key_context_internal(true, window, cx);
2590 let in_conflict = self.edit_prediction_in_conflict();
2591
2592 let bindings = if accept_partial {
2593 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2594 } else {
2595 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2596 };
2597
2598 // TODO: if the binding contains multiple keystrokes, display all of them, not
2599 // just the first one.
2600 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2601 !in_conflict
2602 || binding
2603 .keystrokes()
2604 .first()
2605 .is_some_and(|keystroke| keystroke.modifiers().modified())
2606 }))
2607 }
2608
2609 pub fn new_file(
2610 workspace: &mut Workspace,
2611 _: &workspace::NewFile,
2612 window: &mut Window,
2613 cx: &mut Context<Workspace>,
2614 ) {
2615 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2616 "Failed to create buffer",
2617 window,
2618 cx,
2619 |e, _, _| match e.error_code() {
2620 ErrorCode::RemoteUpgradeRequired => Some(format!(
2621 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2622 e.error_tag("required").unwrap_or("the latest version")
2623 )),
2624 _ => None,
2625 },
2626 );
2627 }
2628
2629 pub fn new_in_workspace(
2630 workspace: &mut Workspace,
2631 window: &mut Window,
2632 cx: &mut Context<Workspace>,
2633 ) -> Task<Result<Entity<Editor>>> {
2634 let project = workspace.project().clone();
2635 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2636
2637 cx.spawn_in(window, async move |workspace, cx| {
2638 let buffer = create.await?;
2639 workspace.update_in(cx, |workspace, window, cx| {
2640 let editor =
2641 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2642 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2643 editor
2644 })
2645 })
2646 }
2647
2648 fn new_file_vertical(
2649 workspace: &mut Workspace,
2650 _: &workspace::NewFileSplitVertical,
2651 window: &mut Window,
2652 cx: &mut Context<Workspace>,
2653 ) {
2654 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2655 }
2656
2657 fn new_file_horizontal(
2658 workspace: &mut Workspace,
2659 _: &workspace::NewFileSplitHorizontal,
2660 window: &mut Window,
2661 cx: &mut Context<Workspace>,
2662 ) {
2663 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2664 }
2665
2666 fn new_file_split(
2667 workspace: &mut Workspace,
2668 action: &workspace::NewFileSplit,
2669 window: &mut Window,
2670 cx: &mut Context<Workspace>,
2671 ) {
2672 Self::new_file_in_direction(workspace, action.0, window, cx)
2673 }
2674
2675 fn new_file_in_direction(
2676 workspace: &mut Workspace,
2677 direction: SplitDirection,
2678 window: &mut Window,
2679 cx: &mut Context<Workspace>,
2680 ) {
2681 let project = workspace.project().clone();
2682 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2683
2684 cx.spawn_in(window, async move |workspace, cx| {
2685 let buffer = create.await?;
2686 workspace.update_in(cx, move |workspace, window, cx| {
2687 workspace.split_item(
2688 direction,
2689 Box::new(
2690 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2691 ),
2692 window,
2693 cx,
2694 )
2695 })?;
2696 anyhow::Ok(())
2697 })
2698 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2699 match e.error_code() {
2700 ErrorCode::RemoteUpgradeRequired => Some(format!(
2701 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2702 e.error_tag("required").unwrap_or("the latest version")
2703 )),
2704 _ => None,
2705 }
2706 });
2707 }
2708
2709 pub fn leader_id(&self) -> Option<CollaboratorId> {
2710 self.leader_id
2711 }
2712
2713 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2714 &self.buffer
2715 }
2716
2717 pub fn project(&self) -> Option<&Entity<Project>> {
2718 self.project.as_ref()
2719 }
2720
2721 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2722 self.workspace.as_ref()?.0.upgrade()
2723 }
2724
2725 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2726 self.buffer().read(cx).title(cx)
2727 }
2728
2729 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2730 let git_blame_gutter_max_author_length = self
2731 .render_git_blame_gutter(cx)
2732 .then(|| {
2733 if let Some(blame) = self.blame.as_ref() {
2734 let max_author_length =
2735 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2736 Some(max_author_length)
2737 } else {
2738 None
2739 }
2740 })
2741 .flatten();
2742
2743 EditorSnapshot {
2744 mode: self.mode.clone(),
2745 show_gutter: self.show_gutter,
2746 show_line_numbers: self.show_line_numbers,
2747 show_git_diff_gutter: self.show_git_diff_gutter,
2748 show_code_actions: self.show_code_actions,
2749 show_runnables: self.show_runnables,
2750 show_breakpoints: self.show_breakpoints,
2751 git_blame_gutter_max_author_length,
2752 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2753 placeholder_display_snapshot: self
2754 .placeholder_display_map
2755 .as_ref()
2756 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2757 scroll_anchor: self.scroll_manager.anchor(),
2758 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2759 is_focused: self.focus_handle.is_focused(window),
2760 current_line_highlight: self
2761 .current_line_highlight
2762 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2763 gutter_hovered: self.gutter_hovered,
2764 }
2765 }
2766
2767 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2768 self.buffer.read(cx).language_at(point, cx)
2769 }
2770
2771 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2772 self.buffer.read(cx).read(cx).file_at(point).cloned()
2773 }
2774
2775 pub fn active_excerpt(
2776 &self,
2777 cx: &App,
2778 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2779 self.buffer
2780 .read(cx)
2781 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2782 }
2783
2784 pub fn mode(&self) -> &EditorMode {
2785 &self.mode
2786 }
2787
2788 pub fn set_mode(&mut self, mode: EditorMode) {
2789 self.mode = mode;
2790 }
2791
2792 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2793 self.collaboration_hub.as_deref()
2794 }
2795
2796 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2797 self.collaboration_hub = Some(hub);
2798 }
2799
2800 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2801 self.in_project_search = in_project_search;
2802 }
2803
2804 pub fn set_custom_context_menu(
2805 &mut self,
2806 f: impl 'static
2807 + Fn(
2808 &mut Self,
2809 DisplayPoint,
2810 &mut Window,
2811 &mut Context<Self>,
2812 ) -> Option<Entity<ui::ContextMenu>>,
2813 ) {
2814 self.custom_context_menu = Some(Box::new(f))
2815 }
2816
2817 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2818 self.completion_provider = provider;
2819 }
2820
2821 #[cfg(any(test, feature = "test-support"))]
2822 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2823 self.completion_provider.clone()
2824 }
2825
2826 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2827 self.semantics_provider.clone()
2828 }
2829
2830 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2831 self.semantics_provider = provider;
2832 }
2833
2834 pub fn set_edit_prediction_provider<T>(
2835 &mut self,
2836 provider: Option<Entity<T>>,
2837 window: &mut Window,
2838 cx: &mut Context<Self>,
2839 ) where
2840 T: EditPredictionProvider,
2841 {
2842 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2843 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2844 if this.focus_handle.is_focused(window) {
2845 this.update_visible_edit_prediction(window, cx);
2846 }
2847 }),
2848 provider: Arc::new(provider),
2849 });
2850 self.update_edit_prediction_settings(cx);
2851 self.refresh_edit_prediction(false, false, window, cx);
2852 }
2853
2854 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2855 self.placeholder_display_map
2856 .as_ref()
2857 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2858 }
2859
2860 pub fn set_placeholder_text(
2861 &mut self,
2862 placeholder_text: &str,
2863 window: &mut Window,
2864 cx: &mut Context<Self>,
2865 ) {
2866 let multibuffer = cx
2867 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2868
2869 let style = window.text_style();
2870
2871 self.placeholder_display_map = Some(cx.new(|cx| {
2872 DisplayMap::new(
2873 multibuffer,
2874 style.font(),
2875 style.font_size.to_pixels(window.rem_size()),
2876 None,
2877 FILE_HEADER_HEIGHT,
2878 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2879 Default::default(),
2880 DiagnosticSeverity::Off,
2881 cx,
2882 )
2883 }));
2884 cx.notify();
2885 }
2886
2887 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2888 self.cursor_shape = cursor_shape;
2889
2890 // Disrupt blink for immediate user feedback that the cursor shape has changed
2891 self.blink_manager.update(cx, BlinkManager::show_cursor);
2892
2893 cx.notify();
2894 }
2895
2896 pub fn set_current_line_highlight(
2897 &mut self,
2898 current_line_highlight: Option<CurrentLineHighlight>,
2899 ) {
2900 self.current_line_highlight = current_line_highlight;
2901 }
2902
2903 pub fn range_for_match<T: std::marker::Copy>(
2904 &self,
2905 range: &Range<T>,
2906 collapse: bool,
2907 ) -> Range<T> {
2908 if collapse {
2909 return range.start..range.start;
2910 }
2911 range.clone()
2912 }
2913
2914 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2915 if self.display_map.read(cx).clip_at_line_ends != clip {
2916 self.display_map
2917 .update(cx, |map, _| map.clip_at_line_ends = clip);
2918 }
2919 }
2920
2921 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2922 self.input_enabled = input_enabled;
2923 }
2924
2925 pub fn set_edit_predictions_hidden_for_vim_mode(
2926 &mut self,
2927 hidden: bool,
2928 window: &mut Window,
2929 cx: &mut Context<Self>,
2930 ) {
2931 if hidden != self.edit_predictions_hidden_for_vim_mode {
2932 self.edit_predictions_hidden_for_vim_mode = hidden;
2933 if hidden {
2934 self.update_visible_edit_prediction(window, cx);
2935 } else {
2936 self.refresh_edit_prediction(true, false, window, cx);
2937 }
2938 }
2939 }
2940
2941 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2942 self.menu_edit_predictions_policy = value;
2943 }
2944
2945 pub fn set_autoindent(&mut self, autoindent: bool) {
2946 if autoindent {
2947 self.autoindent_mode = Some(AutoindentMode::EachLine);
2948 } else {
2949 self.autoindent_mode = None;
2950 }
2951 }
2952
2953 pub fn read_only(&self, cx: &App) -> bool {
2954 self.read_only || self.buffer.read(cx).read_only()
2955 }
2956
2957 pub fn set_read_only(&mut self, read_only: bool) {
2958 self.read_only = read_only;
2959 }
2960
2961 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2962 self.use_autoclose = autoclose;
2963 }
2964
2965 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2966 self.use_auto_surround = auto_surround;
2967 }
2968
2969 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2970 self.auto_replace_emoji_shortcode = auto_replace;
2971 }
2972
2973 pub fn toggle_edit_predictions(
2974 &mut self,
2975 _: &ToggleEditPrediction,
2976 window: &mut Window,
2977 cx: &mut Context<Self>,
2978 ) {
2979 if self.show_edit_predictions_override.is_some() {
2980 self.set_show_edit_predictions(None, window, cx);
2981 } else {
2982 let show_edit_predictions = !self.edit_predictions_enabled();
2983 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2984 }
2985 }
2986
2987 pub fn set_show_edit_predictions(
2988 &mut self,
2989 show_edit_predictions: Option<bool>,
2990 window: &mut Window,
2991 cx: &mut Context<Self>,
2992 ) {
2993 self.show_edit_predictions_override = show_edit_predictions;
2994 self.update_edit_prediction_settings(cx);
2995
2996 if let Some(false) = show_edit_predictions {
2997 self.discard_edit_prediction(false, cx);
2998 } else {
2999 self.refresh_edit_prediction(false, true, window, cx);
3000 }
3001 }
3002
3003 fn edit_predictions_disabled_in_scope(
3004 &self,
3005 buffer: &Entity<Buffer>,
3006 buffer_position: language::Anchor,
3007 cx: &App,
3008 ) -> bool {
3009 let snapshot = buffer.read(cx).snapshot();
3010 let settings = snapshot.settings_at(buffer_position, cx);
3011
3012 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3013 return false;
3014 };
3015
3016 scope.override_name().is_some_and(|scope_name| {
3017 settings
3018 .edit_predictions_disabled_in
3019 .iter()
3020 .any(|s| s == scope_name)
3021 })
3022 }
3023
3024 pub fn set_use_modal_editing(&mut self, to: bool) {
3025 self.use_modal_editing = to;
3026 }
3027
3028 pub fn use_modal_editing(&self) -> bool {
3029 self.use_modal_editing
3030 }
3031
3032 fn selections_did_change(
3033 &mut self,
3034 local: bool,
3035 old_cursor_position: &Anchor,
3036 effects: SelectionEffects,
3037 window: &mut Window,
3038 cx: &mut Context<Self>,
3039 ) {
3040 window.invalidate_character_coordinates();
3041
3042 // Copy selections to primary selection buffer
3043 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3044 if local {
3045 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3046 let buffer_handle = self.buffer.read(cx).read(cx);
3047
3048 let mut text = String::new();
3049 for (index, selection) in selections.iter().enumerate() {
3050 let text_for_selection = buffer_handle
3051 .text_for_range(selection.start..selection.end)
3052 .collect::<String>();
3053
3054 text.push_str(&text_for_selection);
3055 if index != selections.len() - 1 {
3056 text.push('\n');
3057 }
3058 }
3059
3060 if !text.is_empty() {
3061 cx.write_to_primary(ClipboardItem::new_string(text));
3062 }
3063 }
3064
3065 let selection_anchors = self.selections.disjoint_anchors_arc();
3066
3067 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3068 self.buffer.update(cx, |buffer, cx| {
3069 buffer.set_active_selections(
3070 &selection_anchors,
3071 self.selections.line_mode(),
3072 self.cursor_shape,
3073 cx,
3074 )
3075 });
3076 }
3077 let display_map = self
3078 .display_map
3079 .update(cx, |display_map, cx| display_map.snapshot(cx));
3080 let buffer = display_map.buffer_snapshot();
3081 if self.selections.count() == 1 {
3082 self.add_selections_state = None;
3083 }
3084 self.select_next_state = None;
3085 self.select_prev_state = None;
3086 self.select_syntax_node_history.try_clear();
3087 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3088 self.snippet_stack.invalidate(&selection_anchors, buffer);
3089 self.take_rename(false, window, cx);
3090
3091 let newest_selection = self.selections.newest_anchor();
3092 let new_cursor_position = newest_selection.head();
3093 let selection_start = newest_selection.start;
3094
3095 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3096 self.push_to_nav_history(
3097 *old_cursor_position,
3098 Some(new_cursor_position.to_point(buffer)),
3099 false,
3100 effects.nav_history == Some(true),
3101 cx,
3102 );
3103 }
3104
3105 if local {
3106 if let Some(buffer_id) = new_cursor_position.buffer_id {
3107 self.register_buffer(buffer_id, cx);
3108 }
3109
3110 let mut context_menu = self.context_menu.borrow_mut();
3111 let completion_menu = match context_menu.as_ref() {
3112 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3113 Some(CodeContextMenu::CodeActions(_)) => {
3114 *context_menu = None;
3115 None
3116 }
3117 None => None,
3118 };
3119 let completion_position = completion_menu.map(|menu| menu.initial_position);
3120 drop(context_menu);
3121
3122 if effects.completions
3123 && let Some(completion_position) = completion_position
3124 {
3125 let start_offset = selection_start.to_offset(buffer);
3126 let position_matches = start_offset == completion_position.to_offset(buffer);
3127 let continue_showing = if position_matches {
3128 if self.snippet_stack.is_empty() {
3129 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3130 == Some(CharKind::Word)
3131 } else {
3132 // Snippet choices can be shown even when the cursor is in whitespace.
3133 // Dismissing the menu with actions like backspace is handled by
3134 // invalidation regions.
3135 true
3136 }
3137 } else {
3138 false
3139 };
3140
3141 if continue_showing {
3142 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3143 } else {
3144 self.hide_context_menu(window, cx);
3145 }
3146 }
3147
3148 hide_hover(self, cx);
3149
3150 if old_cursor_position.to_display_point(&display_map).row()
3151 != new_cursor_position.to_display_point(&display_map).row()
3152 {
3153 self.available_code_actions.take();
3154 }
3155 self.refresh_code_actions(window, cx);
3156 self.refresh_document_highlights(cx);
3157 refresh_linked_ranges(self, window, cx);
3158
3159 self.refresh_selected_text_highlights(false, window, cx);
3160 self.refresh_matching_bracket_highlights(window, cx);
3161 self.update_visible_edit_prediction(window, cx);
3162 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3163 self.inline_blame_popover.take();
3164 if self.git_blame_inline_enabled {
3165 self.start_inline_blame_timer(window, cx);
3166 }
3167 }
3168
3169 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3170 cx.emit(EditorEvent::SelectionsChanged { local });
3171
3172 let selections = &self.selections.disjoint_anchors_arc();
3173 if selections.len() == 1 {
3174 cx.emit(SearchEvent::ActiveMatchChanged)
3175 }
3176 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3177 let inmemory_selections = selections
3178 .iter()
3179 .map(|s| {
3180 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3181 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3182 })
3183 .collect();
3184 self.update_restoration_data(cx, |data| {
3185 data.selections = inmemory_selections;
3186 });
3187
3188 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3189 && let Some(workspace_id) =
3190 self.workspace.as_ref().and_then(|workspace| workspace.1)
3191 {
3192 let snapshot = self.buffer().read(cx).snapshot(cx);
3193 let selections = selections.clone();
3194 let background_executor = cx.background_executor().clone();
3195 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3196 self.serialize_selections = cx.background_spawn(async move {
3197 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3198 let db_selections = selections
3199 .iter()
3200 .map(|selection| {
3201 (
3202 selection.start.to_offset(&snapshot),
3203 selection.end.to_offset(&snapshot),
3204 )
3205 })
3206 .collect();
3207
3208 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3209 .await
3210 .with_context(|| {
3211 format!(
3212 "persisting editor selections for editor {editor_id}, \
3213 workspace {workspace_id:?}"
3214 )
3215 })
3216 .log_err();
3217 });
3218 }
3219 }
3220
3221 cx.notify();
3222 }
3223
3224 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3225 use text::ToOffset as _;
3226 use text::ToPoint as _;
3227
3228 if self.mode.is_minimap()
3229 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3230 {
3231 return;
3232 }
3233
3234 if !self.buffer().read(cx).is_singleton() {
3235 return;
3236 }
3237
3238 let display_snapshot = self
3239 .display_map
3240 .update(cx, |display_map, cx| display_map.snapshot(cx));
3241 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3242 return;
3243 };
3244 let inmemory_folds = display_snapshot
3245 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3246 .map(|fold| {
3247 fold.range.start.text_anchor.to_point(&snapshot)
3248 ..fold.range.end.text_anchor.to_point(&snapshot)
3249 })
3250 .collect();
3251 self.update_restoration_data(cx, |data| {
3252 data.folds = inmemory_folds;
3253 });
3254
3255 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3256 return;
3257 };
3258 let background_executor = cx.background_executor().clone();
3259 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3260 let db_folds = display_snapshot
3261 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3262 .map(|fold| {
3263 (
3264 fold.range.start.text_anchor.to_offset(&snapshot),
3265 fold.range.end.text_anchor.to_offset(&snapshot),
3266 )
3267 })
3268 .collect();
3269 self.serialize_folds = cx.background_spawn(async move {
3270 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3271 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3272 .await
3273 .with_context(|| {
3274 format!(
3275 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3276 )
3277 })
3278 .log_err();
3279 });
3280 }
3281
3282 pub fn sync_selections(
3283 &mut self,
3284 other: Entity<Editor>,
3285 cx: &mut Context<Self>,
3286 ) -> gpui::Subscription {
3287 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3288 if !other_selections.is_empty() {
3289 self.selections.change_with(cx, |selections| {
3290 selections.select_anchors(other_selections);
3291 });
3292 }
3293
3294 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3295 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3296 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3297 if other_selections.is_empty() {
3298 return;
3299 }
3300 this.selections.change_with(cx, |selections| {
3301 selections.select_anchors(other_selections);
3302 });
3303 }
3304 });
3305
3306 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3307 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3308 let these_selections = this.selections.disjoint_anchors().to_vec();
3309 if these_selections.is_empty() {
3310 return;
3311 }
3312 other.update(cx, |other_editor, cx| {
3313 other_editor.selections.change_with(cx, |selections| {
3314 selections.select_anchors(these_selections);
3315 })
3316 });
3317 }
3318 });
3319
3320 Subscription::join(other_subscription, this_subscription)
3321 }
3322
3323 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3324 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3325 /// effects of selection change occur at the end of the transaction.
3326 pub fn change_selections<R>(
3327 &mut self,
3328 effects: SelectionEffects,
3329 window: &mut Window,
3330 cx: &mut Context<Self>,
3331 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3332 ) -> R {
3333 if let Some(state) = &mut self.deferred_selection_effects_state {
3334 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3335 state.effects.completions = effects.completions;
3336 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3337 let (changed, result) = self.selections.change_with(cx, change);
3338 state.changed |= changed;
3339 return result;
3340 }
3341 let mut state = DeferredSelectionEffectsState {
3342 changed: false,
3343 effects,
3344 old_cursor_position: self.selections.newest_anchor().head(),
3345 history_entry: SelectionHistoryEntry {
3346 selections: self.selections.disjoint_anchors_arc(),
3347 select_next_state: self.select_next_state.clone(),
3348 select_prev_state: self.select_prev_state.clone(),
3349 add_selections_state: self.add_selections_state.clone(),
3350 },
3351 };
3352 let (changed, result) = self.selections.change_with(cx, change);
3353 state.changed = state.changed || changed;
3354 if self.defer_selection_effects {
3355 self.deferred_selection_effects_state = Some(state);
3356 } else {
3357 self.apply_selection_effects(state, window, cx);
3358 }
3359 result
3360 }
3361
3362 /// Defers the effects of selection change, so that the effects of multiple calls to
3363 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3364 /// to selection history and the state of popovers based on selection position aren't
3365 /// erroneously updated.
3366 pub fn with_selection_effects_deferred<R>(
3367 &mut self,
3368 window: &mut Window,
3369 cx: &mut Context<Self>,
3370 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3371 ) -> R {
3372 let already_deferred = self.defer_selection_effects;
3373 self.defer_selection_effects = true;
3374 let result = update(self, window, cx);
3375 if !already_deferred {
3376 self.defer_selection_effects = false;
3377 if let Some(state) = self.deferred_selection_effects_state.take() {
3378 self.apply_selection_effects(state, window, cx);
3379 }
3380 }
3381 result
3382 }
3383
3384 fn apply_selection_effects(
3385 &mut self,
3386 state: DeferredSelectionEffectsState,
3387 window: &mut Window,
3388 cx: &mut Context<Self>,
3389 ) {
3390 if state.changed {
3391 self.selection_history.push(state.history_entry);
3392
3393 if let Some(autoscroll) = state.effects.scroll {
3394 self.request_autoscroll(autoscroll, cx);
3395 }
3396
3397 let old_cursor_position = &state.old_cursor_position;
3398
3399 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3400
3401 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3402 self.show_signature_help(&ShowSignatureHelp, window, cx);
3403 }
3404 }
3405 }
3406
3407 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3408 where
3409 I: IntoIterator<Item = (Range<S>, T)>,
3410 S: ToOffset,
3411 T: Into<Arc<str>>,
3412 {
3413 if self.read_only(cx) {
3414 return;
3415 }
3416
3417 self.buffer
3418 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3419 }
3420
3421 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3422 where
3423 I: IntoIterator<Item = (Range<S>, T)>,
3424 S: ToOffset,
3425 T: Into<Arc<str>>,
3426 {
3427 if self.read_only(cx) {
3428 return;
3429 }
3430
3431 self.buffer.update(cx, |buffer, cx| {
3432 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3433 });
3434 }
3435
3436 pub fn edit_with_block_indent<I, S, T>(
3437 &mut self,
3438 edits: I,
3439 original_indent_columns: Vec<Option<u32>>,
3440 cx: &mut Context<Self>,
3441 ) where
3442 I: IntoIterator<Item = (Range<S>, T)>,
3443 S: ToOffset,
3444 T: Into<Arc<str>>,
3445 {
3446 if self.read_only(cx) {
3447 return;
3448 }
3449
3450 self.buffer.update(cx, |buffer, cx| {
3451 buffer.edit(
3452 edits,
3453 Some(AutoindentMode::Block {
3454 original_indent_columns,
3455 }),
3456 cx,
3457 )
3458 });
3459 }
3460
3461 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3462 self.hide_context_menu(window, cx);
3463
3464 match phase {
3465 SelectPhase::Begin {
3466 position,
3467 add,
3468 click_count,
3469 } => self.begin_selection(position, add, click_count, window, cx),
3470 SelectPhase::BeginColumnar {
3471 position,
3472 goal_column,
3473 reset,
3474 mode,
3475 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3476 SelectPhase::Extend {
3477 position,
3478 click_count,
3479 } => self.extend_selection(position, click_count, window, cx),
3480 SelectPhase::Update {
3481 position,
3482 goal_column,
3483 scroll_delta,
3484 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3485 SelectPhase::End => self.end_selection(window, cx),
3486 }
3487 }
3488
3489 fn extend_selection(
3490 &mut self,
3491 position: DisplayPoint,
3492 click_count: usize,
3493 window: &mut Window,
3494 cx: &mut Context<Self>,
3495 ) {
3496 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3497 let tail = self.selections.newest::<usize>(&display_map).tail();
3498 let click_count = click_count.max(match self.selections.select_mode() {
3499 SelectMode::Character => 1,
3500 SelectMode::Word(_) => 2,
3501 SelectMode::Line(_) => 3,
3502 SelectMode::All => 4,
3503 });
3504 self.begin_selection(position, false, click_count, window, cx);
3505
3506 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3507
3508 let current_selection = match self.selections.select_mode() {
3509 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3510 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3511 };
3512
3513 let mut pending_selection = self
3514 .selections
3515 .pending_anchor()
3516 .cloned()
3517 .expect("extend_selection not called with pending selection");
3518
3519 if pending_selection
3520 .start
3521 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3522 == Ordering::Greater
3523 {
3524 pending_selection.start = current_selection.start;
3525 }
3526 if pending_selection
3527 .end
3528 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3529 == Ordering::Less
3530 {
3531 pending_selection.end = current_selection.end;
3532 pending_selection.reversed = true;
3533 }
3534
3535 let mut pending_mode = self.selections.pending_mode().unwrap();
3536 match &mut pending_mode {
3537 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3538 _ => {}
3539 }
3540
3541 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3542 SelectionEffects::scroll(Autoscroll::fit())
3543 } else {
3544 SelectionEffects::no_scroll()
3545 };
3546
3547 self.change_selections(effects, window, cx, |s| {
3548 s.set_pending(pending_selection.clone(), pending_mode);
3549 s.set_is_extending(true);
3550 });
3551 }
3552
3553 fn begin_selection(
3554 &mut self,
3555 position: DisplayPoint,
3556 add: bool,
3557 click_count: usize,
3558 window: &mut Window,
3559 cx: &mut Context<Self>,
3560 ) {
3561 if !self.focus_handle.is_focused(window) {
3562 self.last_focused_descendant = None;
3563 window.focus(&self.focus_handle);
3564 }
3565
3566 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3567 let buffer = display_map.buffer_snapshot();
3568 let position = display_map.clip_point(position, Bias::Left);
3569
3570 let start;
3571 let end;
3572 let mode;
3573 let mut auto_scroll;
3574 match click_count {
3575 1 => {
3576 start = buffer.anchor_before(position.to_point(&display_map));
3577 end = start;
3578 mode = SelectMode::Character;
3579 auto_scroll = true;
3580 }
3581 2 => {
3582 let position = display_map
3583 .clip_point(position, Bias::Left)
3584 .to_offset(&display_map, Bias::Left);
3585 let (range, _) = buffer.surrounding_word(position, None);
3586 start = buffer.anchor_before(range.start);
3587 end = buffer.anchor_before(range.end);
3588 mode = SelectMode::Word(start..end);
3589 auto_scroll = true;
3590 }
3591 3 => {
3592 let position = display_map
3593 .clip_point(position, Bias::Left)
3594 .to_point(&display_map);
3595 let line_start = display_map.prev_line_boundary(position).0;
3596 let next_line_start = buffer.clip_point(
3597 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3598 Bias::Left,
3599 );
3600 start = buffer.anchor_before(line_start);
3601 end = buffer.anchor_before(next_line_start);
3602 mode = SelectMode::Line(start..end);
3603 auto_scroll = true;
3604 }
3605 _ => {
3606 start = buffer.anchor_before(0);
3607 end = buffer.anchor_before(buffer.len());
3608 mode = SelectMode::All;
3609 auto_scroll = false;
3610 }
3611 }
3612 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3613
3614 let point_to_delete: Option<usize> = {
3615 let selected_points: Vec<Selection<Point>> =
3616 self.selections.disjoint_in_range(start..end, &display_map);
3617
3618 if !add || click_count > 1 {
3619 None
3620 } else if !selected_points.is_empty() {
3621 Some(selected_points[0].id)
3622 } else {
3623 let clicked_point_already_selected =
3624 self.selections.disjoint_anchors().iter().find(|selection| {
3625 selection.start.to_point(buffer) == start.to_point(buffer)
3626 || selection.end.to_point(buffer) == end.to_point(buffer)
3627 });
3628
3629 clicked_point_already_selected.map(|selection| selection.id)
3630 }
3631 };
3632
3633 let selections_count = self.selections.count();
3634 let effects = if auto_scroll {
3635 SelectionEffects::default()
3636 } else {
3637 SelectionEffects::no_scroll()
3638 };
3639
3640 self.change_selections(effects, window, cx, |s| {
3641 if let Some(point_to_delete) = point_to_delete {
3642 s.delete(point_to_delete);
3643
3644 if selections_count == 1 {
3645 s.set_pending_anchor_range(start..end, mode);
3646 }
3647 } else {
3648 if !add {
3649 s.clear_disjoint();
3650 }
3651
3652 s.set_pending_anchor_range(start..end, mode);
3653 }
3654 });
3655 }
3656
3657 fn begin_columnar_selection(
3658 &mut self,
3659 position: DisplayPoint,
3660 goal_column: u32,
3661 reset: bool,
3662 mode: ColumnarMode,
3663 window: &mut Window,
3664 cx: &mut Context<Self>,
3665 ) {
3666 if !self.focus_handle.is_focused(window) {
3667 self.last_focused_descendant = None;
3668 window.focus(&self.focus_handle);
3669 }
3670
3671 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3672
3673 if reset {
3674 let pointer_position = display_map
3675 .buffer_snapshot()
3676 .anchor_before(position.to_point(&display_map));
3677
3678 self.change_selections(
3679 SelectionEffects::scroll(Autoscroll::newest()),
3680 window,
3681 cx,
3682 |s| {
3683 s.clear_disjoint();
3684 s.set_pending_anchor_range(
3685 pointer_position..pointer_position,
3686 SelectMode::Character,
3687 );
3688 },
3689 );
3690 };
3691
3692 let tail = self.selections.newest::<Point>(&display_map).tail();
3693 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3694 self.columnar_selection_state = match mode {
3695 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3696 selection_tail: selection_anchor,
3697 display_point: if reset {
3698 if position.column() != goal_column {
3699 Some(DisplayPoint::new(position.row(), goal_column))
3700 } else {
3701 None
3702 }
3703 } else {
3704 None
3705 },
3706 }),
3707 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3708 selection_tail: selection_anchor,
3709 }),
3710 };
3711
3712 if !reset {
3713 self.select_columns(position, goal_column, &display_map, window, cx);
3714 }
3715 }
3716
3717 fn update_selection(
3718 &mut self,
3719 position: DisplayPoint,
3720 goal_column: u32,
3721 scroll_delta: gpui::Point<f32>,
3722 window: &mut Window,
3723 cx: &mut Context<Self>,
3724 ) {
3725 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3726
3727 if self.columnar_selection_state.is_some() {
3728 self.select_columns(position, goal_column, &display_map, window, cx);
3729 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3730 let buffer = display_map.buffer_snapshot();
3731 let head;
3732 let tail;
3733 let mode = self.selections.pending_mode().unwrap();
3734 match &mode {
3735 SelectMode::Character => {
3736 head = position.to_point(&display_map);
3737 tail = pending.tail().to_point(buffer);
3738 }
3739 SelectMode::Word(original_range) => {
3740 let offset = display_map
3741 .clip_point(position, Bias::Left)
3742 .to_offset(&display_map, Bias::Left);
3743 let original_range = original_range.to_offset(buffer);
3744
3745 let head_offset = if buffer.is_inside_word(offset, None)
3746 || original_range.contains(&offset)
3747 {
3748 let (word_range, _) = buffer.surrounding_word(offset, None);
3749 if word_range.start < original_range.start {
3750 word_range.start
3751 } else {
3752 word_range.end
3753 }
3754 } else {
3755 offset
3756 };
3757
3758 head = head_offset.to_point(buffer);
3759 if head_offset <= original_range.start {
3760 tail = original_range.end.to_point(buffer);
3761 } else {
3762 tail = original_range.start.to_point(buffer);
3763 }
3764 }
3765 SelectMode::Line(original_range) => {
3766 let original_range = original_range.to_point(display_map.buffer_snapshot());
3767
3768 let position = display_map
3769 .clip_point(position, Bias::Left)
3770 .to_point(&display_map);
3771 let line_start = display_map.prev_line_boundary(position).0;
3772 let next_line_start = buffer.clip_point(
3773 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3774 Bias::Left,
3775 );
3776
3777 if line_start < original_range.start {
3778 head = line_start
3779 } else {
3780 head = next_line_start
3781 }
3782
3783 if head <= original_range.start {
3784 tail = original_range.end;
3785 } else {
3786 tail = original_range.start;
3787 }
3788 }
3789 SelectMode::All => {
3790 return;
3791 }
3792 };
3793
3794 if head < tail {
3795 pending.start = buffer.anchor_before(head);
3796 pending.end = buffer.anchor_before(tail);
3797 pending.reversed = true;
3798 } else {
3799 pending.start = buffer.anchor_before(tail);
3800 pending.end = buffer.anchor_before(head);
3801 pending.reversed = false;
3802 }
3803
3804 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3805 s.set_pending(pending.clone(), mode);
3806 });
3807 } else {
3808 log::error!("update_selection dispatched with no pending selection");
3809 return;
3810 }
3811
3812 self.apply_scroll_delta(scroll_delta, window, cx);
3813 cx.notify();
3814 }
3815
3816 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3817 self.columnar_selection_state.take();
3818 if let Some(pending_mode) = self.selections.pending_mode() {
3819 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3820 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3821 s.select(selections);
3822 s.clear_pending();
3823 if s.is_extending() {
3824 s.set_is_extending(false);
3825 } else {
3826 s.set_select_mode(pending_mode);
3827 }
3828 });
3829 }
3830 }
3831
3832 fn select_columns(
3833 &mut self,
3834 head: DisplayPoint,
3835 goal_column: u32,
3836 display_map: &DisplaySnapshot,
3837 window: &mut Window,
3838 cx: &mut Context<Self>,
3839 ) {
3840 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3841 return;
3842 };
3843
3844 let tail = match columnar_state {
3845 ColumnarSelectionState::FromMouse {
3846 selection_tail,
3847 display_point,
3848 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3849 ColumnarSelectionState::FromSelection { selection_tail } => {
3850 selection_tail.to_display_point(display_map)
3851 }
3852 };
3853
3854 let start_row = cmp::min(tail.row(), head.row());
3855 let end_row = cmp::max(tail.row(), head.row());
3856 let start_column = cmp::min(tail.column(), goal_column);
3857 let end_column = cmp::max(tail.column(), goal_column);
3858 let reversed = start_column < tail.column();
3859
3860 let selection_ranges = (start_row.0..=end_row.0)
3861 .map(DisplayRow)
3862 .filter_map(|row| {
3863 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3864 || start_column <= display_map.line_len(row))
3865 && !display_map.is_block_line(row)
3866 {
3867 let start = display_map
3868 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3869 .to_point(display_map);
3870 let end = display_map
3871 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3872 .to_point(display_map);
3873 if reversed {
3874 Some(end..start)
3875 } else {
3876 Some(start..end)
3877 }
3878 } else {
3879 None
3880 }
3881 })
3882 .collect::<Vec<_>>();
3883 if selection_ranges.is_empty() {
3884 return;
3885 }
3886
3887 let ranges = match columnar_state {
3888 ColumnarSelectionState::FromMouse { .. } => {
3889 let mut non_empty_ranges = selection_ranges
3890 .iter()
3891 .filter(|selection_range| selection_range.start != selection_range.end)
3892 .peekable();
3893 if non_empty_ranges.peek().is_some() {
3894 non_empty_ranges.cloned().collect()
3895 } else {
3896 selection_ranges
3897 }
3898 }
3899 _ => selection_ranges,
3900 };
3901
3902 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3903 s.select_ranges(ranges);
3904 });
3905 cx.notify();
3906 }
3907
3908 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3909 self.selections
3910 .all_adjusted(snapshot)
3911 .iter()
3912 .any(|selection| !selection.is_empty())
3913 }
3914
3915 pub fn has_pending_nonempty_selection(&self) -> bool {
3916 let pending_nonempty_selection = match self.selections.pending_anchor() {
3917 Some(Selection { start, end, .. }) => start != end,
3918 None => false,
3919 };
3920
3921 pending_nonempty_selection
3922 || (self.columnar_selection_state.is_some()
3923 && self.selections.disjoint_anchors().len() > 1)
3924 }
3925
3926 pub fn has_pending_selection(&self) -> bool {
3927 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3928 }
3929
3930 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3931 self.selection_mark_mode = false;
3932 self.selection_drag_state = SelectionDragState::None;
3933
3934 if self.clear_expanded_diff_hunks(cx) {
3935 cx.notify();
3936 return;
3937 }
3938 if self.dismiss_menus_and_popups(true, window, cx) {
3939 return;
3940 }
3941
3942 if self.mode.is_full()
3943 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3944 {
3945 return;
3946 }
3947
3948 cx.propagate();
3949 }
3950
3951 pub fn dismiss_menus_and_popups(
3952 &mut self,
3953 is_user_requested: bool,
3954 window: &mut Window,
3955 cx: &mut Context<Self>,
3956 ) -> bool {
3957 if self.take_rename(false, window, cx).is_some() {
3958 return true;
3959 }
3960
3961 if self.hide_blame_popover(true, cx) {
3962 return true;
3963 }
3964
3965 if hide_hover(self, cx) {
3966 return true;
3967 }
3968
3969 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3970 return true;
3971 }
3972
3973 if self.hide_context_menu(window, cx).is_some() {
3974 return true;
3975 }
3976
3977 if self.mouse_context_menu.take().is_some() {
3978 return true;
3979 }
3980
3981 if is_user_requested && self.discard_edit_prediction(true, cx) {
3982 return true;
3983 }
3984
3985 if self.snippet_stack.pop().is_some() {
3986 return true;
3987 }
3988
3989 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3990 self.dismiss_diagnostics(cx);
3991 return true;
3992 }
3993
3994 false
3995 }
3996
3997 fn linked_editing_ranges_for(
3998 &self,
3999 selection: Range<text::Anchor>,
4000 cx: &App,
4001 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4002 if self.linked_edit_ranges.is_empty() {
4003 return None;
4004 }
4005 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4006 selection.end.buffer_id.and_then(|end_buffer_id| {
4007 if selection.start.buffer_id != Some(end_buffer_id) {
4008 return None;
4009 }
4010 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4011 let snapshot = buffer.read(cx).snapshot();
4012 self.linked_edit_ranges
4013 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4014 .map(|ranges| (ranges, snapshot, buffer))
4015 })?;
4016 use text::ToOffset as TO;
4017 // find offset from the start of current range to current cursor position
4018 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4019
4020 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4021 let start_difference = start_offset - start_byte_offset;
4022 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4023 let end_difference = end_offset - start_byte_offset;
4024 // Current range has associated linked ranges.
4025 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4026 for range in linked_ranges.iter() {
4027 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4028 let end_offset = start_offset + end_difference;
4029 let start_offset = start_offset + start_difference;
4030 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4031 continue;
4032 }
4033 if self.selections.disjoint_anchor_ranges().any(|s| {
4034 if s.start.buffer_id != selection.start.buffer_id
4035 || s.end.buffer_id != selection.end.buffer_id
4036 {
4037 return false;
4038 }
4039 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4040 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4041 }) {
4042 continue;
4043 }
4044 let start = buffer_snapshot.anchor_after(start_offset);
4045 let end = buffer_snapshot.anchor_after(end_offset);
4046 linked_edits
4047 .entry(buffer.clone())
4048 .or_default()
4049 .push(start..end);
4050 }
4051 Some(linked_edits)
4052 }
4053
4054 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4055 let text: Arc<str> = text.into();
4056
4057 if self.read_only(cx) {
4058 return;
4059 }
4060
4061 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4062
4063 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4064 let mut bracket_inserted = false;
4065 let mut edits = Vec::new();
4066 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4067 let mut new_selections = Vec::with_capacity(selections.len());
4068 let mut new_autoclose_regions = Vec::new();
4069 let snapshot = self.buffer.read(cx).read(cx);
4070 let mut clear_linked_edit_ranges = false;
4071
4072 for (selection, autoclose_region) in
4073 self.selections_with_autoclose_regions(selections, &snapshot)
4074 {
4075 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4076 // Determine if the inserted text matches the opening or closing
4077 // bracket of any of this language's bracket pairs.
4078 let mut bracket_pair = None;
4079 let mut is_bracket_pair_start = false;
4080 let mut is_bracket_pair_end = false;
4081 if !text.is_empty() {
4082 let mut bracket_pair_matching_end = None;
4083 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4084 // and they are removing the character that triggered IME popup.
4085 for (pair, enabled) in scope.brackets() {
4086 if !pair.close && !pair.surround {
4087 continue;
4088 }
4089
4090 if enabled && pair.start.ends_with(text.as_ref()) {
4091 let prefix_len = pair.start.len() - text.len();
4092 let preceding_text_matches_prefix = prefix_len == 0
4093 || (selection.start.column >= (prefix_len as u32)
4094 && snapshot.contains_str_at(
4095 Point::new(
4096 selection.start.row,
4097 selection.start.column - (prefix_len as u32),
4098 ),
4099 &pair.start[..prefix_len],
4100 ));
4101 if preceding_text_matches_prefix {
4102 bracket_pair = Some(pair.clone());
4103 is_bracket_pair_start = true;
4104 break;
4105 }
4106 }
4107 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4108 {
4109 // take first bracket pair matching end, but don't break in case a later bracket
4110 // pair matches start
4111 bracket_pair_matching_end = Some(pair.clone());
4112 }
4113 }
4114 if let Some(end) = bracket_pair_matching_end
4115 && bracket_pair.is_none()
4116 {
4117 bracket_pair = Some(end);
4118 is_bracket_pair_end = true;
4119 }
4120 }
4121
4122 if let Some(bracket_pair) = bracket_pair {
4123 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4124 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4125 let auto_surround =
4126 self.use_auto_surround && snapshot_settings.use_auto_surround;
4127 if selection.is_empty() {
4128 if is_bracket_pair_start {
4129 // If the inserted text is a suffix of an opening bracket and the
4130 // selection is preceded by the rest of the opening bracket, then
4131 // insert the closing bracket.
4132 let following_text_allows_autoclose = snapshot
4133 .chars_at(selection.start)
4134 .next()
4135 .is_none_or(|c| scope.should_autoclose_before(c));
4136
4137 let preceding_text_allows_autoclose = selection.start.column == 0
4138 || snapshot
4139 .reversed_chars_at(selection.start)
4140 .next()
4141 .is_none_or(|c| {
4142 bracket_pair.start != bracket_pair.end
4143 || !snapshot
4144 .char_classifier_at(selection.start)
4145 .is_word(c)
4146 });
4147
4148 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4149 && bracket_pair.start.len() == 1
4150 {
4151 let target = bracket_pair.start.chars().next().unwrap();
4152 let current_line_count = snapshot
4153 .reversed_chars_at(selection.start)
4154 .take_while(|&c| c != '\n')
4155 .filter(|&c| c == target)
4156 .count();
4157 current_line_count % 2 == 1
4158 } else {
4159 false
4160 };
4161
4162 if autoclose
4163 && bracket_pair.close
4164 && following_text_allows_autoclose
4165 && preceding_text_allows_autoclose
4166 && !is_closing_quote
4167 {
4168 let anchor = snapshot.anchor_before(selection.end);
4169 new_selections.push((selection.map(|_| anchor), text.len()));
4170 new_autoclose_regions.push((
4171 anchor,
4172 text.len(),
4173 selection.id,
4174 bracket_pair.clone(),
4175 ));
4176 edits.push((
4177 selection.range(),
4178 format!("{}{}", text, bracket_pair.end).into(),
4179 ));
4180 bracket_inserted = true;
4181 continue;
4182 }
4183 }
4184
4185 if let Some(region) = autoclose_region {
4186 // If the selection is followed by an auto-inserted closing bracket,
4187 // then don't insert that closing bracket again; just move the selection
4188 // past the closing bracket.
4189 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4190 && text.as_ref() == region.pair.end.as_str()
4191 && snapshot.contains_str_at(region.range.end, text.as_ref());
4192 if should_skip {
4193 let anchor = snapshot.anchor_after(selection.end);
4194 new_selections
4195 .push((selection.map(|_| anchor), region.pair.end.len()));
4196 continue;
4197 }
4198 }
4199
4200 let always_treat_brackets_as_autoclosed = snapshot
4201 .language_settings_at(selection.start, cx)
4202 .always_treat_brackets_as_autoclosed;
4203 if always_treat_brackets_as_autoclosed
4204 && is_bracket_pair_end
4205 && snapshot.contains_str_at(selection.end, text.as_ref())
4206 {
4207 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4208 // and the inserted text is a closing bracket and the selection is followed
4209 // by the closing bracket then move the selection past the closing bracket.
4210 let anchor = snapshot.anchor_after(selection.end);
4211 new_selections.push((selection.map(|_| anchor), text.len()));
4212 continue;
4213 }
4214 }
4215 // If an opening bracket is 1 character long and is typed while
4216 // text is selected, then surround that text with the bracket pair.
4217 else if auto_surround
4218 && bracket_pair.surround
4219 && is_bracket_pair_start
4220 && bracket_pair.start.chars().count() == 1
4221 {
4222 edits.push((selection.start..selection.start, text.clone()));
4223 edits.push((
4224 selection.end..selection.end,
4225 bracket_pair.end.as_str().into(),
4226 ));
4227 bracket_inserted = true;
4228 new_selections.push((
4229 Selection {
4230 id: selection.id,
4231 start: snapshot.anchor_after(selection.start),
4232 end: snapshot.anchor_before(selection.end),
4233 reversed: selection.reversed,
4234 goal: selection.goal,
4235 },
4236 0,
4237 ));
4238 continue;
4239 }
4240 }
4241 }
4242
4243 if self.auto_replace_emoji_shortcode
4244 && selection.is_empty()
4245 && text.as_ref().ends_with(':')
4246 && let Some(possible_emoji_short_code) =
4247 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4248 && !possible_emoji_short_code.is_empty()
4249 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4250 {
4251 let emoji_shortcode_start = Point::new(
4252 selection.start.row,
4253 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4254 );
4255
4256 // Remove shortcode from buffer
4257 edits.push((
4258 emoji_shortcode_start..selection.start,
4259 "".to_string().into(),
4260 ));
4261 new_selections.push((
4262 Selection {
4263 id: selection.id,
4264 start: snapshot.anchor_after(emoji_shortcode_start),
4265 end: snapshot.anchor_before(selection.start),
4266 reversed: selection.reversed,
4267 goal: selection.goal,
4268 },
4269 0,
4270 ));
4271
4272 // Insert emoji
4273 let selection_start_anchor = snapshot.anchor_after(selection.start);
4274 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4275 edits.push((selection.start..selection.end, emoji.to_string().into()));
4276
4277 continue;
4278 }
4279
4280 // If not handling any auto-close operation, then just replace the selected
4281 // text with the given input and move the selection to the end of the
4282 // newly inserted text.
4283 let anchor = snapshot.anchor_after(selection.end);
4284 if !self.linked_edit_ranges.is_empty() {
4285 let start_anchor = snapshot.anchor_before(selection.start);
4286
4287 let is_word_char = text.chars().next().is_none_or(|char| {
4288 let classifier = snapshot
4289 .char_classifier_at(start_anchor.to_offset(&snapshot))
4290 .scope_context(Some(CharScopeContext::LinkedEdit));
4291 classifier.is_word(char)
4292 });
4293
4294 if is_word_char {
4295 if let Some(ranges) = self
4296 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4297 {
4298 for (buffer, edits) in ranges {
4299 linked_edits
4300 .entry(buffer.clone())
4301 .or_default()
4302 .extend(edits.into_iter().map(|range| (range, text.clone())));
4303 }
4304 }
4305 } else {
4306 clear_linked_edit_ranges = true;
4307 }
4308 }
4309
4310 new_selections.push((selection.map(|_| anchor), 0));
4311 edits.push((selection.start..selection.end, text.clone()));
4312 }
4313
4314 drop(snapshot);
4315
4316 self.transact(window, cx, |this, window, cx| {
4317 if clear_linked_edit_ranges {
4318 this.linked_edit_ranges.clear();
4319 }
4320 let initial_buffer_versions =
4321 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4322
4323 this.buffer.update(cx, |buffer, cx| {
4324 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4325 });
4326 for (buffer, edits) in linked_edits {
4327 buffer.update(cx, |buffer, cx| {
4328 let snapshot = buffer.snapshot();
4329 let edits = edits
4330 .into_iter()
4331 .map(|(range, text)| {
4332 use text::ToPoint as TP;
4333 let end_point = TP::to_point(&range.end, &snapshot);
4334 let start_point = TP::to_point(&range.start, &snapshot);
4335 (start_point..end_point, text)
4336 })
4337 .sorted_by_key(|(range, _)| range.start);
4338 buffer.edit(edits, None, cx);
4339 })
4340 }
4341 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4342 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4343 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4344 let new_selections =
4345 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4346 .zip(new_selection_deltas)
4347 .map(|(selection, delta)| Selection {
4348 id: selection.id,
4349 start: selection.start + delta,
4350 end: selection.end + delta,
4351 reversed: selection.reversed,
4352 goal: SelectionGoal::None,
4353 })
4354 .collect::<Vec<_>>();
4355
4356 let mut i = 0;
4357 for (position, delta, selection_id, pair) in new_autoclose_regions {
4358 let position = position.to_offset(map.buffer_snapshot()) + delta;
4359 let start = map.buffer_snapshot().anchor_before(position);
4360 let end = map.buffer_snapshot().anchor_after(position);
4361 while let Some(existing_state) = this.autoclose_regions.get(i) {
4362 match existing_state
4363 .range
4364 .start
4365 .cmp(&start, map.buffer_snapshot())
4366 {
4367 Ordering::Less => i += 1,
4368 Ordering::Greater => break,
4369 Ordering::Equal => {
4370 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4371 Ordering::Less => i += 1,
4372 Ordering::Equal => break,
4373 Ordering::Greater => break,
4374 }
4375 }
4376 }
4377 }
4378 this.autoclose_regions.insert(
4379 i,
4380 AutocloseRegion {
4381 selection_id,
4382 range: start..end,
4383 pair,
4384 },
4385 );
4386 }
4387
4388 let had_active_edit_prediction = this.has_active_edit_prediction();
4389 this.change_selections(
4390 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4391 window,
4392 cx,
4393 |s| s.select(new_selections),
4394 );
4395
4396 if !bracket_inserted
4397 && let Some(on_type_format_task) =
4398 this.trigger_on_type_formatting(text.to_string(), window, cx)
4399 {
4400 on_type_format_task.detach_and_log_err(cx);
4401 }
4402
4403 let editor_settings = EditorSettings::get_global(cx);
4404 if bracket_inserted
4405 && (editor_settings.auto_signature_help
4406 || editor_settings.show_signature_help_after_edits)
4407 {
4408 this.show_signature_help(&ShowSignatureHelp, window, cx);
4409 }
4410
4411 let trigger_in_words =
4412 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4413 if this.hard_wrap.is_some() {
4414 let latest: Range<Point> = this.selections.newest(&map).range();
4415 if latest.is_empty()
4416 && this
4417 .buffer()
4418 .read(cx)
4419 .snapshot(cx)
4420 .line_len(MultiBufferRow(latest.start.row))
4421 == latest.start.column
4422 {
4423 this.rewrap_impl(
4424 RewrapOptions {
4425 override_language_settings: true,
4426 preserve_existing_whitespace: true,
4427 },
4428 cx,
4429 )
4430 }
4431 }
4432 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4433 refresh_linked_ranges(this, window, cx);
4434 this.refresh_edit_prediction(true, false, window, cx);
4435 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4436 });
4437 }
4438
4439 fn find_possible_emoji_shortcode_at_position(
4440 snapshot: &MultiBufferSnapshot,
4441 position: Point,
4442 ) -> Option<String> {
4443 let mut chars = Vec::new();
4444 let mut found_colon = false;
4445 for char in snapshot.reversed_chars_at(position).take(100) {
4446 // Found a possible emoji shortcode in the middle of the buffer
4447 if found_colon {
4448 if char.is_whitespace() {
4449 chars.reverse();
4450 return Some(chars.iter().collect());
4451 }
4452 // If the previous character is not a whitespace, we are in the middle of a word
4453 // and we only want to complete the shortcode if the word is made up of other emojis
4454 let mut containing_word = String::new();
4455 for ch in snapshot
4456 .reversed_chars_at(position)
4457 .skip(chars.len() + 1)
4458 .take(100)
4459 {
4460 if ch.is_whitespace() {
4461 break;
4462 }
4463 containing_word.push(ch);
4464 }
4465 let containing_word = containing_word.chars().rev().collect::<String>();
4466 if util::word_consists_of_emojis(containing_word.as_str()) {
4467 chars.reverse();
4468 return Some(chars.iter().collect());
4469 }
4470 }
4471
4472 if char.is_whitespace() || !char.is_ascii() {
4473 return None;
4474 }
4475 if char == ':' {
4476 found_colon = true;
4477 } else {
4478 chars.push(char);
4479 }
4480 }
4481 // Found a possible emoji shortcode at the beginning of the buffer
4482 chars.reverse();
4483 Some(chars.iter().collect())
4484 }
4485
4486 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4487 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4488 self.transact(window, cx, |this, window, cx| {
4489 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4490 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4491 let multi_buffer = this.buffer.read(cx);
4492 let buffer = multi_buffer.snapshot(cx);
4493 selections
4494 .iter()
4495 .map(|selection| {
4496 let start_point = selection.start.to_point(&buffer);
4497 let mut existing_indent =
4498 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4499 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4500 let start = selection.start;
4501 let end = selection.end;
4502 let selection_is_empty = start == end;
4503 let language_scope = buffer.language_scope_at(start);
4504 let (
4505 comment_delimiter,
4506 doc_delimiter,
4507 insert_extra_newline,
4508 indent_on_newline,
4509 indent_on_extra_newline,
4510 ) = if let Some(language) = &language_scope {
4511 let mut insert_extra_newline =
4512 insert_extra_newline_brackets(&buffer, start..end, language)
4513 || insert_extra_newline_tree_sitter(&buffer, start..end);
4514
4515 // Comment extension on newline is allowed only for cursor selections
4516 let comment_delimiter = maybe!({
4517 if !selection_is_empty {
4518 return None;
4519 }
4520
4521 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4522 return None;
4523 }
4524
4525 let delimiters = language.line_comment_prefixes();
4526 let max_len_of_delimiter =
4527 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4528 let (snapshot, range) =
4529 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4530
4531 let num_of_whitespaces = snapshot
4532 .chars_for_range(range.clone())
4533 .take_while(|c| c.is_whitespace())
4534 .count();
4535 let comment_candidate = snapshot
4536 .chars_for_range(range.clone())
4537 .skip(num_of_whitespaces)
4538 .take(max_len_of_delimiter)
4539 .collect::<String>();
4540 let (delimiter, trimmed_len) = delimiters
4541 .iter()
4542 .filter_map(|delimiter| {
4543 let prefix = delimiter.trim_end();
4544 if comment_candidate.starts_with(prefix) {
4545 Some((delimiter, prefix.len()))
4546 } else {
4547 None
4548 }
4549 })
4550 .max_by_key(|(_, len)| *len)?;
4551
4552 if let Some(BlockCommentConfig {
4553 start: block_start, ..
4554 }) = language.block_comment()
4555 {
4556 let block_start_trimmed = block_start.trim_end();
4557 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4558 let line_content = snapshot
4559 .chars_for_range(range)
4560 .skip(num_of_whitespaces)
4561 .take(block_start_trimmed.len())
4562 .collect::<String>();
4563
4564 if line_content.starts_with(block_start_trimmed) {
4565 return None;
4566 }
4567 }
4568 }
4569
4570 let cursor_is_placed_after_comment_marker =
4571 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4572 if cursor_is_placed_after_comment_marker {
4573 Some(delimiter.clone())
4574 } else {
4575 None
4576 }
4577 });
4578
4579 let mut indent_on_newline = IndentSize::spaces(0);
4580 let mut indent_on_extra_newline = IndentSize::spaces(0);
4581
4582 let doc_delimiter = maybe!({
4583 if !selection_is_empty {
4584 return None;
4585 }
4586
4587 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4588 return None;
4589 }
4590
4591 let BlockCommentConfig {
4592 start: start_tag,
4593 end: end_tag,
4594 prefix: delimiter,
4595 tab_size: len,
4596 } = language.documentation_comment()?;
4597 let is_within_block_comment = buffer
4598 .language_scope_at(start_point)
4599 .is_some_and(|scope| scope.override_name() == Some("comment"));
4600 if !is_within_block_comment {
4601 return None;
4602 }
4603
4604 let (snapshot, range) =
4605 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4606
4607 let num_of_whitespaces = snapshot
4608 .chars_for_range(range.clone())
4609 .take_while(|c| c.is_whitespace())
4610 .count();
4611
4612 // 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.
4613 let column = start_point.column;
4614 let cursor_is_after_start_tag = {
4615 let start_tag_len = start_tag.len();
4616 let start_tag_line = snapshot
4617 .chars_for_range(range.clone())
4618 .skip(num_of_whitespaces)
4619 .take(start_tag_len)
4620 .collect::<String>();
4621 if start_tag_line.starts_with(start_tag.as_ref()) {
4622 num_of_whitespaces + start_tag_len <= column as usize
4623 } else {
4624 false
4625 }
4626 };
4627
4628 let cursor_is_after_delimiter = {
4629 let delimiter_trim = delimiter.trim_end();
4630 let delimiter_line = snapshot
4631 .chars_for_range(range.clone())
4632 .skip(num_of_whitespaces)
4633 .take(delimiter_trim.len())
4634 .collect::<String>();
4635 if delimiter_line.starts_with(delimiter_trim) {
4636 num_of_whitespaces + delimiter_trim.len() <= column as usize
4637 } else {
4638 false
4639 }
4640 };
4641
4642 let cursor_is_before_end_tag_if_exists = {
4643 let mut char_position = 0u32;
4644 let mut end_tag_offset = None;
4645
4646 'outer: for chunk in snapshot.text_for_range(range) {
4647 if let Some(byte_pos) = chunk.find(&**end_tag) {
4648 let chars_before_match =
4649 chunk[..byte_pos].chars().count() as u32;
4650 end_tag_offset =
4651 Some(char_position + chars_before_match);
4652 break 'outer;
4653 }
4654 char_position += chunk.chars().count() as u32;
4655 }
4656
4657 if let Some(end_tag_offset) = end_tag_offset {
4658 let cursor_is_before_end_tag = column <= end_tag_offset;
4659 if cursor_is_after_start_tag {
4660 if cursor_is_before_end_tag {
4661 insert_extra_newline = true;
4662 }
4663 let cursor_is_at_start_of_end_tag =
4664 column == end_tag_offset;
4665 if cursor_is_at_start_of_end_tag {
4666 indent_on_extra_newline.len = *len;
4667 }
4668 }
4669 cursor_is_before_end_tag
4670 } else {
4671 true
4672 }
4673 };
4674
4675 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4676 && cursor_is_before_end_tag_if_exists
4677 {
4678 if cursor_is_after_start_tag {
4679 indent_on_newline.len = *len;
4680 }
4681 Some(delimiter.clone())
4682 } else {
4683 None
4684 }
4685 });
4686
4687 (
4688 comment_delimiter,
4689 doc_delimiter,
4690 insert_extra_newline,
4691 indent_on_newline,
4692 indent_on_extra_newline,
4693 )
4694 } else {
4695 (
4696 None,
4697 None,
4698 false,
4699 IndentSize::default(),
4700 IndentSize::default(),
4701 )
4702 };
4703
4704 let prevent_auto_indent = doc_delimiter.is_some();
4705 let delimiter = comment_delimiter.or(doc_delimiter);
4706
4707 let capacity_for_delimiter =
4708 delimiter.as_deref().map(str::len).unwrap_or_default();
4709 let mut new_text = String::with_capacity(
4710 1 + capacity_for_delimiter
4711 + existing_indent.len as usize
4712 + indent_on_newline.len as usize
4713 + indent_on_extra_newline.len as usize,
4714 );
4715 new_text.push('\n');
4716 new_text.extend(existing_indent.chars());
4717 new_text.extend(indent_on_newline.chars());
4718
4719 if let Some(delimiter) = &delimiter {
4720 new_text.push_str(delimiter);
4721 }
4722
4723 if insert_extra_newline {
4724 new_text.push('\n');
4725 new_text.extend(existing_indent.chars());
4726 new_text.extend(indent_on_extra_newline.chars());
4727 }
4728
4729 let anchor = buffer.anchor_after(end);
4730 let new_selection = selection.map(|_| anchor);
4731 (
4732 ((start..end, new_text), prevent_auto_indent),
4733 (insert_extra_newline, new_selection),
4734 )
4735 })
4736 .unzip()
4737 };
4738
4739 let mut auto_indent_edits = Vec::new();
4740 let mut edits = Vec::new();
4741 for (edit, prevent_auto_indent) in edits_with_flags {
4742 if prevent_auto_indent {
4743 edits.push(edit);
4744 } else {
4745 auto_indent_edits.push(edit);
4746 }
4747 }
4748 if !edits.is_empty() {
4749 this.edit(edits, cx);
4750 }
4751 if !auto_indent_edits.is_empty() {
4752 this.edit_with_autoindent(auto_indent_edits, cx);
4753 }
4754
4755 let buffer = this.buffer.read(cx).snapshot(cx);
4756 let new_selections = selection_info
4757 .into_iter()
4758 .map(|(extra_newline_inserted, new_selection)| {
4759 let mut cursor = new_selection.end.to_point(&buffer);
4760 if extra_newline_inserted {
4761 cursor.row -= 1;
4762 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4763 }
4764 new_selection.map(|_| cursor)
4765 })
4766 .collect();
4767
4768 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4769 this.refresh_edit_prediction(true, false, window, cx);
4770 });
4771 }
4772
4773 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4774 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4775
4776 let buffer = self.buffer.read(cx);
4777 let snapshot = buffer.snapshot(cx);
4778
4779 let mut edits = Vec::new();
4780 let mut rows = Vec::new();
4781
4782 for (rows_inserted, selection) in self
4783 .selections
4784 .all_adjusted(&self.display_snapshot(cx))
4785 .into_iter()
4786 .enumerate()
4787 {
4788 let cursor = selection.head();
4789 let row = cursor.row;
4790
4791 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4792
4793 let newline = "\n".to_string();
4794 edits.push((start_of_line..start_of_line, newline));
4795
4796 rows.push(row + rows_inserted as u32);
4797 }
4798
4799 self.transact(window, cx, |editor, window, cx| {
4800 editor.edit(edits, cx);
4801
4802 editor.change_selections(Default::default(), window, cx, |s| {
4803 let mut index = 0;
4804 s.move_cursors_with(|map, _, _| {
4805 let row = rows[index];
4806 index += 1;
4807
4808 let point = Point::new(row, 0);
4809 let boundary = map.next_line_boundary(point).1;
4810 let clipped = map.clip_point(boundary, Bias::Left);
4811
4812 (clipped, SelectionGoal::None)
4813 });
4814 });
4815
4816 let mut indent_edits = Vec::new();
4817 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4818 for row in rows {
4819 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4820 for (row, indent) in indents {
4821 if indent.len == 0 {
4822 continue;
4823 }
4824
4825 let text = match indent.kind {
4826 IndentKind::Space => " ".repeat(indent.len as usize),
4827 IndentKind::Tab => "\t".repeat(indent.len as usize),
4828 };
4829 let point = Point::new(row.0, 0);
4830 indent_edits.push((point..point, text));
4831 }
4832 }
4833 editor.edit(indent_edits, cx);
4834 });
4835 }
4836
4837 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4838 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4839
4840 let buffer = self.buffer.read(cx);
4841 let snapshot = buffer.snapshot(cx);
4842
4843 let mut edits = Vec::new();
4844 let mut rows = Vec::new();
4845 let mut rows_inserted = 0;
4846
4847 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4848 let cursor = selection.head();
4849 let row = cursor.row;
4850
4851 let point = Point::new(row + 1, 0);
4852 let start_of_line = snapshot.clip_point(point, Bias::Left);
4853
4854 let newline = "\n".to_string();
4855 edits.push((start_of_line..start_of_line, newline));
4856
4857 rows_inserted += 1;
4858 rows.push(row + rows_inserted);
4859 }
4860
4861 self.transact(window, cx, |editor, window, cx| {
4862 editor.edit(edits, cx);
4863
4864 editor.change_selections(Default::default(), window, cx, |s| {
4865 let mut index = 0;
4866 s.move_cursors_with(|map, _, _| {
4867 let row = rows[index];
4868 index += 1;
4869
4870 let point = Point::new(row, 0);
4871 let boundary = map.next_line_boundary(point).1;
4872 let clipped = map.clip_point(boundary, Bias::Left);
4873
4874 (clipped, SelectionGoal::None)
4875 });
4876 });
4877
4878 let mut indent_edits = Vec::new();
4879 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4880 for row in rows {
4881 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4882 for (row, indent) in indents {
4883 if indent.len == 0 {
4884 continue;
4885 }
4886
4887 let text = match indent.kind {
4888 IndentKind::Space => " ".repeat(indent.len as usize),
4889 IndentKind::Tab => "\t".repeat(indent.len as usize),
4890 };
4891 let point = Point::new(row.0, 0);
4892 indent_edits.push((point..point, text));
4893 }
4894 }
4895 editor.edit(indent_edits, cx);
4896 });
4897 }
4898
4899 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4900 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4901 original_indent_columns: Vec::new(),
4902 });
4903 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4904 }
4905
4906 fn insert_with_autoindent_mode(
4907 &mut self,
4908 text: &str,
4909 autoindent_mode: Option<AutoindentMode>,
4910 window: &mut Window,
4911 cx: &mut Context<Self>,
4912 ) {
4913 if self.read_only(cx) {
4914 return;
4915 }
4916
4917 let text: Arc<str> = text.into();
4918 self.transact(window, cx, |this, window, cx| {
4919 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
4920 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4921 let anchors = {
4922 let snapshot = buffer.read(cx);
4923 old_selections
4924 .iter()
4925 .map(|s| {
4926 let anchor = snapshot.anchor_after(s.head());
4927 s.map(|_| anchor)
4928 })
4929 .collect::<Vec<_>>()
4930 };
4931 buffer.edit(
4932 old_selections
4933 .iter()
4934 .map(|s| (s.start..s.end, text.clone())),
4935 autoindent_mode,
4936 cx,
4937 );
4938 anchors
4939 });
4940
4941 this.change_selections(Default::default(), window, cx, |s| {
4942 s.select_anchors(selection_anchors);
4943 });
4944
4945 cx.notify();
4946 });
4947 }
4948
4949 fn trigger_completion_on_input(
4950 &mut self,
4951 text: &str,
4952 trigger_in_words: bool,
4953 window: &mut Window,
4954 cx: &mut Context<Self>,
4955 ) {
4956 let completions_source = self
4957 .context_menu
4958 .borrow()
4959 .as_ref()
4960 .and_then(|menu| match menu {
4961 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4962 CodeContextMenu::CodeActions(_) => None,
4963 });
4964
4965 match completions_source {
4966 Some(CompletionsMenuSource::Words { .. }) => {
4967 self.open_or_update_completions_menu(
4968 Some(CompletionsMenuSource::Words {
4969 ignore_threshold: false,
4970 }),
4971 None,
4972 window,
4973 cx,
4974 );
4975 }
4976 Some(CompletionsMenuSource::Normal)
4977 | Some(CompletionsMenuSource::SnippetChoices)
4978 | None
4979 if self.is_completion_trigger(
4980 text,
4981 trigger_in_words,
4982 completions_source.is_some(),
4983 cx,
4984 ) =>
4985 {
4986 self.show_completions(
4987 &ShowCompletions {
4988 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4989 },
4990 window,
4991 cx,
4992 )
4993 }
4994 _ => {
4995 self.hide_context_menu(window, cx);
4996 }
4997 }
4998 }
4999
5000 fn is_completion_trigger(
5001 &self,
5002 text: &str,
5003 trigger_in_words: bool,
5004 menu_is_open: bool,
5005 cx: &mut Context<Self>,
5006 ) -> bool {
5007 let position = self.selections.newest_anchor().head();
5008 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
5009 return false;
5010 };
5011
5012 if let Some(completion_provider) = &self.completion_provider {
5013 completion_provider.is_completion_trigger(
5014 &buffer,
5015 position.text_anchor,
5016 text,
5017 trigger_in_words,
5018 menu_is_open,
5019 cx,
5020 )
5021 } else {
5022 false
5023 }
5024 }
5025
5026 /// If any empty selections is touching the start of its innermost containing autoclose
5027 /// region, expand it to select the brackets.
5028 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5029 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5030 let buffer = self.buffer.read(cx).read(cx);
5031 let new_selections = self
5032 .selections_with_autoclose_regions(selections, &buffer)
5033 .map(|(mut selection, region)| {
5034 if !selection.is_empty() {
5035 return selection;
5036 }
5037
5038 if let Some(region) = region {
5039 let mut range = region.range.to_offset(&buffer);
5040 if selection.start == range.start && range.start >= region.pair.start.len() {
5041 range.start -= region.pair.start.len();
5042 if buffer.contains_str_at(range.start, ®ion.pair.start)
5043 && buffer.contains_str_at(range.end, ®ion.pair.end)
5044 {
5045 range.end += region.pair.end.len();
5046 selection.start = range.start;
5047 selection.end = range.end;
5048
5049 return selection;
5050 }
5051 }
5052 }
5053
5054 let always_treat_brackets_as_autoclosed = buffer
5055 .language_settings_at(selection.start, cx)
5056 .always_treat_brackets_as_autoclosed;
5057
5058 if !always_treat_brackets_as_autoclosed {
5059 return selection;
5060 }
5061
5062 if let Some(scope) = buffer.language_scope_at(selection.start) {
5063 for (pair, enabled) in scope.brackets() {
5064 if !enabled || !pair.close {
5065 continue;
5066 }
5067
5068 if buffer.contains_str_at(selection.start, &pair.end) {
5069 let pair_start_len = pair.start.len();
5070 if buffer.contains_str_at(
5071 selection.start.saturating_sub(pair_start_len),
5072 &pair.start,
5073 ) {
5074 selection.start -= pair_start_len;
5075 selection.end += pair.end.len();
5076
5077 return selection;
5078 }
5079 }
5080 }
5081 }
5082
5083 selection
5084 })
5085 .collect();
5086
5087 drop(buffer);
5088 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5089 selections.select(new_selections)
5090 });
5091 }
5092
5093 /// Iterate the given selections, and for each one, find the smallest surrounding
5094 /// autoclose region. This uses the ordering of the selections and the autoclose
5095 /// regions to avoid repeated comparisons.
5096 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5097 &'a self,
5098 selections: impl IntoIterator<Item = Selection<D>>,
5099 buffer: &'a MultiBufferSnapshot,
5100 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5101 let mut i = 0;
5102 let mut regions = self.autoclose_regions.as_slice();
5103 selections.into_iter().map(move |selection| {
5104 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5105
5106 let mut enclosing = None;
5107 while let Some(pair_state) = regions.get(i) {
5108 if pair_state.range.end.to_offset(buffer) < range.start {
5109 regions = ®ions[i + 1..];
5110 i = 0;
5111 } else if pair_state.range.start.to_offset(buffer) > range.end {
5112 break;
5113 } else {
5114 if pair_state.selection_id == selection.id {
5115 enclosing = Some(pair_state);
5116 }
5117 i += 1;
5118 }
5119 }
5120
5121 (selection, enclosing)
5122 })
5123 }
5124
5125 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5126 fn invalidate_autoclose_regions(
5127 &mut self,
5128 mut selections: &[Selection<Anchor>],
5129 buffer: &MultiBufferSnapshot,
5130 ) {
5131 self.autoclose_regions.retain(|state| {
5132 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5133 return false;
5134 }
5135
5136 let mut i = 0;
5137 while let Some(selection) = selections.get(i) {
5138 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5139 selections = &selections[1..];
5140 continue;
5141 }
5142 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5143 break;
5144 }
5145 if selection.id == state.selection_id {
5146 return true;
5147 } else {
5148 i += 1;
5149 }
5150 }
5151 false
5152 });
5153 }
5154
5155 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5156 let offset = position.to_offset(buffer);
5157 let (word_range, kind) =
5158 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5159 if offset > word_range.start && kind == Some(CharKind::Word) {
5160 Some(
5161 buffer
5162 .text_for_range(word_range.start..offset)
5163 .collect::<String>(),
5164 )
5165 } else {
5166 None
5167 }
5168 }
5169
5170 pub fn visible_excerpts(
5171 &self,
5172 cx: &mut Context<Editor>,
5173 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5174 let Some(project) = self.project() else {
5175 return HashMap::default();
5176 };
5177 let project = project.read(cx);
5178 let multi_buffer = self.buffer().read(cx);
5179 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5180 let multi_buffer_visible_start = self
5181 .scroll_manager
5182 .anchor()
5183 .anchor
5184 .to_point(&multi_buffer_snapshot);
5185 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5186 multi_buffer_visible_start
5187 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5188 Bias::Left,
5189 );
5190 multi_buffer_snapshot
5191 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5192 .into_iter()
5193 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5194 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5195 let buffer_file = project::File::from_dyn(buffer.file())?;
5196 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5197 let worktree_entry = buffer_worktree
5198 .read(cx)
5199 .entry_for_id(buffer_file.project_entry_id()?)?;
5200 if worktree_entry.is_ignored {
5201 None
5202 } else {
5203 Some((
5204 excerpt_id,
5205 (
5206 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5207 buffer.version().clone(),
5208 excerpt_visible_range,
5209 ),
5210 ))
5211 }
5212 })
5213 .collect()
5214 }
5215
5216 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5217 TextLayoutDetails {
5218 text_system: window.text_system().clone(),
5219 editor_style: self.style.clone().unwrap(),
5220 rem_size: window.rem_size(),
5221 scroll_anchor: self.scroll_manager.anchor(),
5222 visible_rows: self.visible_line_count(),
5223 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5224 }
5225 }
5226
5227 fn trigger_on_type_formatting(
5228 &self,
5229 input: String,
5230 window: &mut Window,
5231 cx: &mut Context<Self>,
5232 ) -> Option<Task<Result<()>>> {
5233 if input.len() != 1 {
5234 return None;
5235 }
5236
5237 let project = self.project()?;
5238 let position = self.selections.newest_anchor().head();
5239 let (buffer, buffer_position) = self
5240 .buffer
5241 .read(cx)
5242 .text_anchor_for_position(position, cx)?;
5243
5244 let settings = language_settings::language_settings(
5245 buffer
5246 .read(cx)
5247 .language_at(buffer_position)
5248 .map(|l| l.name()),
5249 buffer.read(cx).file(),
5250 cx,
5251 );
5252 if !settings.use_on_type_format {
5253 return None;
5254 }
5255
5256 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5257 // hence we do LSP request & edit on host side only — add formats to host's history.
5258 let push_to_lsp_host_history = true;
5259 // If this is not the host, append its history with new edits.
5260 let push_to_client_history = project.read(cx).is_via_collab();
5261
5262 let on_type_formatting = project.update(cx, |project, cx| {
5263 project.on_type_format(
5264 buffer.clone(),
5265 buffer_position,
5266 input,
5267 push_to_lsp_host_history,
5268 cx,
5269 )
5270 });
5271 Some(cx.spawn_in(window, async move |editor, cx| {
5272 if let Some(transaction) = on_type_formatting.await? {
5273 if push_to_client_history {
5274 buffer
5275 .update(cx, |buffer, _| {
5276 buffer.push_transaction(transaction, Instant::now());
5277 buffer.finalize_last_transaction();
5278 })
5279 .ok();
5280 }
5281 editor.update(cx, |editor, cx| {
5282 editor.refresh_document_highlights(cx);
5283 })?;
5284 }
5285 Ok(())
5286 }))
5287 }
5288
5289 pub fn show_word_completions(
5290 &mut self,
5291 _: &ShowWordCompletions,
5292 window: &mut Window,
5293 cx: &mut Context<Self>,
5294 ) {
5295 self.open_or_update_completions_menu(
5296 Some(CompletionsMenuSource::Words {
5297 ignore_threshold: true,
5298 }),
5299 None,
5300 window,
5301 cx,
5302 );
5303 }
5304
5305 pub fn show_completions(
5306 &mut self,
5307 options: &ShowCompletions,
5308 window: &mut Window,
5309 cx: &mut Context<Self>,
5310 ) {
5311 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5312 }
5313
5314 fn open_or_update_completions_menu(
5315 &mut self,
5316 requested_source: Option<CompletionsMenuSource>,
5317 trigger: Option<&str>,
5318 window: &mut Window,
5319 cx: &mut Context<Self>,
5320 ) {
5321 if self.pending_rename.is_some() {
5322 return;
5323 }
5324
5325 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5326
5327 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5328 // inserted and selected. To handle that case, the start of the selection is used so that
5329 // the menu starts with all choices.
5330 let position = self
5331 .selections
5332 .newest_anchor()
5333 .start
5334 .bias_right(&multibuffer_snapshot);
5335 if position.diff_base_anchor.is_some() {
5336 return;
5337 }
5338 let buffer_position = multibuffer_snapshot.anchor_before(position);
5339 let Some(buffer) = buffer_position
5340 .buffer_id
5341 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5342 else {
5343 return;
5344 };
5345 let buffer_snapshot = buffer.read(cx).snapshot();
5346
5347 let query: Option<Arc<String>> =
5348 Self::completion_query(&multibuffer_snapshot, buffer_position)
5349 .map(|query| query.into());
5350
5351 drop(multibuffer_snapshot);
5352
5353 // Hide the current completions menu when query is empty. Without this, cached
5354 // completions from before the trigger char may be reused (#32774).
5355 if query.is_none() {
5356 let menu_is_open = matches!(
5357 self.context_menu.borrow().as_ref(),
5358 Some(CodeContextMenu::Completions(_))
5359 );
5360 if menu_is_open {
5361 self.hide_context_menu(window, cx);
5362 }
5363 }
5364
5365 let mut ignore_word_threshold = false;
5366 let provider = match requested_source {
5367 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5368 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5369 ignore_word_threshold = ignore_threshold;
5370 None
5371 }
5372 Some(CompletionsMenuSource::SnippetChoices) => {
5373 log::error!("bug: SnippetChoices requested_source is not handled");
5374 None
5375 }
5376 };
5377
5378 let sort_completions = provider
5379 .as_ref()
5380 .is_some_and(|provider| provider.sort_completions());
5381
5382 let filter_completions = provider
5383 .as_ref()
5384 .is_none_or(|provider| provider.filter_completions());
5385
5386 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5387 if filter_completions {
5388 menu.filter(query.clone(), provider.clone(), window, cx);
5389 }
5390 // When `is_incomplete` is false, no need to re-query completions when the current query
5391 // is a suffix of the initial query.
5392 if !menu.is_incomplete {
5393 // If the new query is a suffix of the old query (typing more characters) and
5394 // the previous result was complete, the existing completions can be filtered.
5395 //
5396 // Note that this is always true for snippet completions.
5397 let query_matches = match (&menu.initial_query, &query) {
5398 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5399 (None, _) => true,
5400 _ => false,
5401 };
5402 if query_matches {
5403 let position_matches = if menu.initial_position == position {
5404 true
5405 } else {
5406 let snapshot = self.buffer.read(cx).read(cx);
5407 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5408 };
5409 if position_matches {
5410 return;
5411 }
5412 }
5413 }
5414 };
5415
5416 let trigger_kind = match trigger {
5417 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5418 CompletionTriggerKind::TRIGGER_CHARACTER
5419 }
5420 _ => CompletionTriggerKind::INVOKED,
5421 };
5422 let completion_context = CompletionContext {
5423 trigger_character: trigger.and_then(|trigger| {
5424 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5425 Some(String::from(trigger))
5426 } else {
5427 None
5428 }
5429 }),
5430 trigger_kind,
5431 };
5432
5433 let Anchor {
5434 excerpt_id: buffer_excerpt_id,
5435 text_anchor: buffer_position,
5436 ..
5437 } = buffer_position;
5438
5439 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5440 buffer_snapshot.surrounding_word(buffer_position, None)
5441 {
5442 let word_to_exclude = buffer_snapshot
5443 .text_for_range(word_range.clone())
5444 .collect::<String>();
5445 (
5446 buffer_snapshot.anchor_before(word_range.start)
5447 ..buffer_snapshot.anchor_after(buffer_position),
5448 Some(word_to_exclude),
5449 )
5450 } else {
5451 (buffer_position..buffer_position, None)
5452 };
5453
5454 let language = buffer_snapshot
5455 .language_at(buffer_position)
5456 .map(|language| language.name());
5457
5458 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5459 .completions
5460 .clone();
5461
5462 let show_completion_documentation = buffer_snapshot
5463 .settings_at(buffer_position, cx)
5464 .show_completion_documentation;
5465
5466 // The document can be large, so stay in reasonable bounds when searching for words,
5467 // otherwise completion pop-up might be slow to appear.
5468 const WORD_LOOKUP_ROWS: u32 = 5_000;
5469 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5470 let min_word_search = buffer_snapshot.clip_point(
5471 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5472 Bias::Left,
5473 );
5474 let max_word_search = buffer_snapshot.clip_point(
5475 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5476 Bias::Right,
5477 );
5478 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5479 ..buffer_snapshot.point_to_offset(max_word_search);
5480
5481 let skip_digits = query
5482 .as_ref()
5483 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5484
5485 let omit_word_completions = !self.word_completions_enabled
5486 || (!ignore_word_threshold
5487 && match &query {
5488 Some(query) => query.chars().count() < completion_settings.words_min_length,
5489 None => completion_settings.words_min_length != 0,
5490 });
5491
5492 let (mut words, provider_responses) = match &provider {
5493 Some(provider) => {
5494 let provider_responses = provider.completions(
5495 buffer_excerpt_id,
5496 &buffer,
5497 buffer_position,
5498 completion_context,
5499 window,
5500 cx,
5501 );
5502
5503 let words = match (omit_word_completions, completion_settings.words) {
5504 (true, _) | (_, WordsCompletionMode::Disabled) => {
5505 Task::ready(BTreeMap::default())
5506 }
5507 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5508 .background_spawn(async move {
5509 buffer_snapshot.words_in_range(WordsQuery {
5510 fuzzy_contents: None,
5511 range: word_search_range,
5512 skip_digits,
5513 })
5514 }),
5515 };
5516
5517 (words, provider_responses)
5518 }
5519 None => {
5520 let words = if omit_word_completions {
5521 Task::ready(BTreeMap::default())
5522 } else {
5523 cx.background_spawn(async move {
5524 buffer_snapshot.words_in_range(WordsQuery {
5525 fuzzy_contents: None,
5526 range: word_search_range,
5527 skip_digits,
5528 })
5529 })
5530 };
5531 (words, Task::ready(Ok(Vec::new())))
5532 }
5533 };
5534
5535 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5536
5537 let id = post_inc(&mut self.next_completion_id);
5538 let task = cx.spawn_in(window, async move |editor, cx| {
5539 let Ok(()) = editor.update(cx, |this, _| {
5540 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5541 }) else {
5542 return;
5543 };
5544
5545 // TODO: Ideally completions from different sources would be selectively re-queried, so
5546 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5547 let mut completions = Vec::new();
5548 let mut is_incomplete = false;
5549 let mut display_options: Option<CompletionDisplayOptions> = None;
5550 if let Some(provider_responses) = provider_responses.await.log_err()
5551 && !provider_responses.is_empty()
5552 {
5553 for response in provider_responses {
5554 completions.extend(response.completions);
5555 is_incomplete = is_incomplete || response.is_incomplete;
5556 match display_options.as_mut() {
5557 None => {
5558 display_options = Some(response.display_options);
5559 }
5560 Some(options) => options.merge(&response.display_options),
5561 }
5562 }
5563 if completion_settings.words == WordsCompletionMode::Fallback {
5564 words = Task::ready(BTreeMap::default());
5565 }
5566 }
5567 let display_options = display_options.unwrap_or_default();
5568
5569 let mut words = words.await;
5570 if let Some(word_to_exclude) = &word_to_exclude {
5571 words.remove(word_to_exclude);
5572 }
5573 for lsp_completion in &completions {
5574 words.remove(&lsp_completion.new_text);
5575 }
5576 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5577 replace_range: word_replace_range.clone(),
5578 new_text: word.clone(),
5579 label: CodeLabel::plain(word, None),
5580 icon_path: None,
5581 documentation: None,
5582 source: CompletionSource::BufferWord {
5583 word_range,
5584 resolved: false,
5585 },
5586 insert_text_mode: Some(InsertTextMode::AS_IS),
5587 confirm: None,
5588 }));
5589
5590 let menu = if completions.is_empty() {
5591 None
5592 } else {
5593 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5594 let languages = editor
5595 .workspace
5596 .as_ref()
5597 .and_then(|(workspace, _)| workspace.upgrade())
5598 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5599 let menu = CompletionsMenu::new(
5600 id,
5601 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5602 sort_completions,
5603 show_completion_documentation,
5604 position,
5605 query.clone(),
5606 is_incomplete,
5607 buffer.clone(),
5608 completions.into(),
5609 display_options,
5610 snippet_sort_order,
5611 languages,
5612 language,
5613 cx,
5614 );
5615
5616 let query = if filter_completions { query } else { None };
5617 let matches_task = if let Some(query) = query {
5618 menu.do_async_filtering(query, cx)
5619 } else {
5620 Task::ready(menu.unfiltered_matches())
5621 };
5622 (menu, matches_task)
5623 }) else {
5624 return;
5625 };
5626
5627 let matches = matches_task.await;
5628
5629 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5630 // Newer menu already set, so exit.
5631 if let Some(CodeContextMenu::Completions(prev_menu)) =
5632 editor.context_menu.borrow().as_ref()
5633 && prev_menu.id > id
5634 {
5635 return;
5636 };
5637
5638 // Only valid to take prev_menu because it the new menu is immediately set
5639 // below, or the menu is hidden.
5640 if let Some(CodeContextMenu::Completions(prev_menu)) =
5641 editor.context_menu.borrow_mut().take()
5642 {
5643 let position_matches =
5644 if prev_menu.initial_position == menu.initial_position {
5645 true
5646 } else {
5647 let snapshot = editor.buffer.read(cx).read(cx);
5648 prev_menu.initial_position.to_offset(&snapshot)
5649 == menu.initial_position.to_offset(&snapshot)
5650 };
5651 if position_matches {
5652 // Preserve markdown cache before `set_filter_results` because it will
5653 // try to populate the documentation cache.
5654 menu.preserve_markdown_cache(prev_menu);
5655 }
5656 };
5657
5658 menu.set_filter_results(matches, provider, window, cx);
5659 }) else {
5660 return;
5661 };
5662
5663 menu.visible().then_some(menu)
5664 };
5665
5666 editor
5667 .update_in(cx, |editor, window, cx| {
5668 if editor.focus_handle.is_focused(window)
5669 && let Some(menu) = menu
5670 {
5671 *editor.context_menu.borrow_mut() =
5672 Some(CodeContextMenu::Completions(menu));
5673
5674 crate::hover_popover::hide_hover(editor, cx);
5675 if editor.show_edit_predictions_in_menu() {
5676 editor.update_visible_edit_prediction(window, cx);
5677 } else {
5678 editor.discard_edit_prediction(false, cx);
5679 }
5680
5681 cx.notify();
5682 return;
5683 }
5684
5685 if editor.completion_tasks.len() <= 1 {
5686 // If there are no more completion tasks and the last menu was empty, we should hide it.
5687 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5688 // If it was already hidden and we don't show edit predictions in the menu,
5689 // we should also show the edit prediction when available.
5690 if was_hidden && editor.show_edit_predictions_in_menu() {
5691 editor.update_visible_edit_prediction(window, cx);
5692 }
5693 }
5694 })
5695 .ok();
5696 });
5697
5698 self.completion_tasks.push((id, task));
5699 }
5700
5701 #[cfg(feature = "test-support")]
5702 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5703 let menu = self.context_menu.borrow();
5704 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5705 let completions = menu.completions.borrow();
5706 Some(completions.to_vec())
5707 } else {
5708 None
5709 }
5710 }
5711
5712 pub fn with_completions_menu_matching_id<R>(
5713 &self,
5714 id: CompletionId,
5715 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5716 ) -> R {
5717 let mut context_menu = self.context_menu.borrow_mut();
5718 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5719 return f(None);
5720 };
5721 if completions_menu.id != id {
5722 return f(None);
5723 }
5724 f(Some(completions_menu))
5725 }
5726
5727 pub fn confirm_completion(
5728 &mut self,
5729 action: &ConfirmCompletion,
5730 window: &mut Window,
5731 cx: &mut Context<Self>,
5732 ) -> Option<Task<Result<()>>> {
5733 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5734 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5735 }
5736
5737 pub fn confirm_completion_insert(
5738 &mut self,
5739 _: &ConfirmCompletionInsert,
5740 window: &mut Window,
5741 cx: &mut Context<Self>,
5742 ) -> Option<Task<Result<()>>> {
5743 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5744 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5745 }
5746
5747 pub fn confirm_completion_replace(
5748 &mut self,
5749 _: &ConfirmCompletionReplace,
5750 window: &mut Window,
5751 cx: &mut Context<Self>,
5752 ) -> Option<Task<Result<()>>> {
5753 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5754 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5755 }
5756
5757 pub fn compose_completion(
5758 &mut self,
5759 action: &ComposeCompletion,
5760 window: &mut Window,
5761 cx: &mut Context<Self>,
5762 ) -> Option<Task<Result<()>>> {
5763 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5764 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5765 }
5766
5767 fn do_completion(
5768 &mut self,
5769 item_ix: Option<usize>,
5770 intent: CompletionIntent,
5771 window: &mut Window,
5772 cx: &mut Context<Editor>,
5773 ) -> Option<Task<Result<()>>> {
5774 use language::ToOffset as _;
5775
5776 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5777 else {
5778 return None;
5779 };
5780
5781 let candidate_id = {
5782 let entries = completions_menu.entries.borrow();
5783 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5784 if self.show_edit_predictions_in_menu() {
5785 self.discard_edit_prediction(true, cx);
5786 }
5787 mat.candidate_id
5788 };
5789
5790 let completion = completions_menu
5791 .completions
5792 .borrow()
5793 .get(candidate_id)?
5794 .clone();
5795 cx.stop_propagation();
5796
5797 let buffer_handle = completions_menu.buffer.clone();
5798
5799 let CompletionEdit {
5800 new_text,
5801 snippet,
5802 replace_range,
5803 } = process_completion_for_edit(
5804 &completion,
5805 intent,
5806 &buffer_handle,
5807 &completions_menu.initial_position.text_anchor,
5808 cx,
5809 );
5810
5811 let buffer = buffer_handle.read(cx);
5812 let snapshot = self.buffer.read(cx).snapshot(cx);
5813 let newest_anchor = self.selections.newest_anchor();
5814 let replace_range_multibuffer = {
5815 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5816 excerpt.map_range_from_buffer(replace_range.clone())
5817 };
5818 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5819 return None;
5820 }
5821
5822 let old_text = buffer
5823 .text_for_range(replace_range.clone())
5824 .collect::<String>();
5825 let lookbehind = newest_anchor
5826 .start
5827 .text_anchor
5828 .to_offset(buffer)
5829 .saturating_sub(replace_range.start);
5830 let lookahead = replace_range
5831 .end
5832 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5833 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5834 let suffix = &old_text[lookbehind.min(old_text.len())..];
5835
5836 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5837 let mut ranges = Vec::new();
5838 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5839
5840 for selection in &selections {
5841 let range = if selection.id == newest_anchor.id {
5842 replace_range_multibuffer.clone()
5843 } else {
5844 let mut range = selection.range();
5845
5846 // if prefix is present, don't duplicate it
5847 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5848 range.start = range.start.saturating_sub(lookbehind);
5849
5850 // if suffix is also present, mimic the newest cursor and replace it
5851 if selection.id != newest_anchor.id
5852 && snapshot.contains_str_at(range.end, suffix)
5853 {
5854 range.end += lookahead;
5855 }
5856 }
5857 range
5858 };
5859
5860 ranges.push(range.clone());
5861
5862 if !self.linked_edit_ranges.is_empty() {
5863 let start_anchor = snapshot.anchor_before(range.start);
5864 let end_anchor = snapshot.anchor_after(range.end);
5865 if let Some(ranges) = self
5866 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5867 {
5868 for (buffer, edits) in ranges {
5869 linked_edits
5870 .entry(buffer.clone())
5871 .or_default()
5872 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5873 }
5874 }
5875 }
5876 }
5877
5878 let common_prefix_len = old_text
5879 .chars()
5880 .zip(new_text.chars())
5881 .take_while(|(a, b)| a == b)
5882 .map(|(a, _)| a.len_utf8())
5883 .sum::<usize>();
5884
5885 cx.emit(EditorEvent::InputHandled {
5886 utf16_range_to_replace: None,
5887 text: new_text[common_prefix_len..].into(),
5888 });
5889
5890 self.transact(window, cx, |editor, window, cx| {
5891 if let Some(mut snippet) = snippet {
5892 snippet.text = new_text.to_string();
5893 editor
5894 .insert_snippet(&ranges, snippet, window, cx)
5895 .log_err();
5896 } else {
5897 editor.buffer.update(cx, |multi_buffer, cx| {
5898 let auto_indent = match completion.insert_text_mode {
5899 Some(InsertTextMode::AS_IS) => None,
5900 _ => editor.autoindent_mode.clone(),
5901 };
5902 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5903 multi_buffer.edit(edits, auto_indent, cx);
5904 });
5905 }
5906 for (buffer, edits) in linked_edits {
5907 buffer.update(cx, |buffer, cx| {
5908 let snapshot = buffer.snapshot();
5909 let edits = edits
5910 .into_iter()
5911 .map(|(range, text)| {
5912 use text::ToPoint as TP;
5913 let end_point = TP::to_point(&range.end, &snapshot);
5914 let start_point = TP::to_point(&range.start, &snapshot);
5915 (start_point..end_point, text)
5916 })
5917 .sorted_by_key(|(range, _)| range.start);
5918 buffer.edit(edits, None, cx);
5919 })
5920 }
5921
5922 editor.refresh_edit_prediction(true, false, window, cx);
5923 });
5924 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
5925
5926 let show_new_completions_on_confirm = completion
5927 .confirm
5928 .as_ref()
5929 .is_some_and(|confirm| confirm(intent, window, cx));
5930 if show_new_completions_on_confirm {
5931 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5932 }
5933
5934 let provider = self.completion_provider.as_ref()?;
5935 drop(completion);
5936 let apply_edits = provider.apply_additional_edits_for_completion(
5937 buffer_handle,
5938 completions_menu.completions.clone(),
5939 candidate_id,
5940 true,
5941 cx,
5942 );
5943
5944 let editor_settings = EditorSettings::get_global(cx);
5945 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5946 // After the code completion is finished, users often want to know what signatures are needed.
5947 // so we should automatically call signature_help
5948 self.show_signature_help(&ShowSignatureHelp, window, cx);
5949 }
5950
5951 Some(cx.foreground_executor().spawn(async move {
5952 apply_edits.await?;
5953 Ok(())
5954 }))
5955 }
5956
5957 pub fn toggle_code_actions(
5958 &mut self,
5959 action: &ToggleCodeActions,
5960 window: &mut Window,
5961 cx: &mut Context<Self>,
5962 ) {
5963 let quick_launch = action.quick_launch;
5964 let mut context_menu = self.context_menu.borrow_mut();
5965 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5966 if code_actions.deployed_from == action.deployed_from {
5967 // Toggle if we're selecting the same one
5968 *context_menu = None;
5969 cx.notify();
5970 return;
5971 } else {
5972 // Otherwise, clear it and start a new one
5973 *context_menu = None;
5974 cx.notify();
5975 }
5976 }
5977 drop(context_menu);
5978 let snapshot = self.snapshot(window, cx);
5979 let deployed_from = action.deployed_from.clone();
5980 let action = action.clone();
5981 self.completion_tasks.clear();
5982 self.discard_edit_prediction(false, cx);
5983
5984 let multibuffer_point = match &action.deployed_from {
5985 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5986 DisplayPoint::new(*row, 0).to_point(&snapshot)
5987 }
5988 _ => self
5989 .selections
5990 .newest::<Point>(&snapshot.display_snapshot)
5991 .head(),
5992 };
5993 let Some((buffer, buffer_row)) = snapshot
5994 .buffer_snapshot()
5995 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5996 .and_then(|(buffer_snapshot, range)| {
5997 self.buffer()
5998 .read(cx)
5999 .buffer(buffer_snapshot.remote_id())
6000 .map(|buffer| (buffer, range.start.row))
6001 })
6002 else {
6003 return;
6004 };
6005 let buffer_id = buffer.read(cx).remote_id();
6006 let tasks = self
6007 .tasks
6008 .get(&(buffer_id, buffer_row))
6009 .map(|t| Arc::new(t.to_owned()));
6010
6011 if !self.focus_handle.is_focused(window) {
6012 return;
6013 }
6014 let project = self.project.clone();
6015
6016 let code_actions_task = match deployed_from {
6017 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6018 _ => self.code_actions(buffer_row, window, cx),
6019 };
6020
6021 let runnable_task = match deployed_from {
6022 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6023 _ => {
6024 let mut task_context_task = Task::ready(None);
6025 if let Some(tasks) = &tasks
6026 && let Some(project) = project
6027 {
6028 task_context_task =
6029 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6030 }
6031
6032 cx.spawn_in(window, {
6033 let buffer = buffer.clone();
6034 async move |editor, cx| {
6035 let task_context = task_context_task.await;
6036
6037 let resolved_tasks =
6038 tasks
6039 .zip(task_context.clone())
6040 .map(|(tasks, task_context)| ResolvedTasks {
6041 templates: tasks.resolve(&task_context).collect(),
6042 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6043 multibuffer_point.row,
6044 tasks.column,
6045 )),
6046 });
6047 let debug_scenarios = editor
6048 .update(cx, |editor, cx| {
6049 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6050 })?
6051 .await;
6052 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6053 }
6054 })
6055 }
6056 };
6057
6058 cx.spawn_in(window, async move |editor, cx| {
6059 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6060 let code_actions = code_actions_task.await;
6061 let spawn_straight_away = quick_launch
6062 && resolved_tasks
6063 .as_ref()
6064 .is_some_and(|tasks| tasks.templates.len() == 1)
6065 && code_actions
6066 .as_ref()
6067 .is_none_or(|actions| actions.is_empty())
6068 && debug_scenarios.is_empty();
6069
6070 editor.update_in(cx, |editor, window, cx| {
6071 crate::hover_popover::hide_hover(editor, cx);
6072 let actions = CodeActionContents::new(
6073 resolved_tasks,
6074 code_actions,
6075 debug_scenarios,
6076 task_context.unwrap_or_default(),
6077 );
6078
6079 // Don't show the menu if there are no actions available
6080 if actions.is_empty() {
6081 cx.notify();
6082 return Task::ready(Ok(()));
6083 }
6084
6085 *editor.context_menu.borrow_mut() =
6086 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6087 buffer,
6088 actions,
6089 selected_item: Default::default(),
6090 scroll_handle: UniformListScrollHandle::default(),
6091 deployed_from,
6092 }));
6093 cx.notify();
6094 if spawn_straight_away
6095 && let Some(task) = editor.confirm_code_action(
6096 &ConfirmCodeAction { item_ix: Some(0) },
6097 window,
6098 cx,
6099 )
6100 {
6101 return task;
6102 }
6103
6104 Task::ready(Ok(()))
6105 })
6106 })
6107 .detach_and_log_err(cx);
6108 }
6109
6110 fn debug_scenarios(
6111 &mut self,
6112 resolved_tasks: &Option<ResolvedTasks>,
6113 buffer: &Entity<Buffer>,
6114 cx: &mut App,
6115 ) -> Task<Vec<task::DebugScenario>> {
6116 maybe!({
6117 let project = self.project()?;
6118 let dap_store = project.read(cx).dap_store();
6119 let mut scenarios = vec![];
6120 let resolved_tasks = resolved_tasks.as_ref()?;
6121 let buffer = buffer.read(cx);
6122 let language = buffer.language()?;
6123 let file = buffer.file();
6124 let debug_adapter = language_settings(language.name().into(), file, cx)
6125 .debuggers
6126 .first()
6127 .map(SharedString::from)
6128 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6129
6130 dap_store.update(cx, |dap_store, cx| {
6131 for (_, task) in &resolved_tasks.templates {
6132 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6133 task.original_task().clone(),
6134 debug_adapter.clone().into(),
6135 task.display_label().to_owned().into(),
6136 cx,
6137 );
6138 scenarios.push(maybe_scenario);
6139 }
6140 });
6141 Some(cx.background_spawn(async move {
6142 futures::future::join_all(scenarios)
6143 .await
6144 .into_iter()
6145 .flatten()
6146 .collect::<Vec<_>>()
6147 }))
6148 })
6149 .unwrap_or_else(|| Task::ready(vec![]))
6150 }
6151
6152 fn code_actions(
6153 &mut self,
6154 buffer_row: u32,
6155 window: &mut Window,
6156 cx: &mut Context<Self>,
6157 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6158 let mut task = self.code_actions_task.take();
6159 cx.spawn_in(window, async move |editor, cx| {
6160 while let Some(prev_task) = task {
6161 prev_task.await.log_err();
6162 task = editor
6163 .update(cx, |this, _| this.code_actions_task.take())
6164 .ok()?;
6165 }
6166
6167 editor
6168 .update(cx, |editor, cx| {
6169 editor
6170 .available_code_actions
6171 .clone()
6172 .and_then(|(location, code_actions)| {
6173 let snapshot = location.buffer.read(cx).snapshot();
6174 let point_range = location.range.to_point(&snapshot);
6175 let point_range = point_range.start.row..=point_range.end.row;
6176 if point_range.contains(&buffer_row) {
6177 Some(code_actions)
6178 } else {
6179 None
6180 }
6181 })
6182 })
6183 .ok()
6184 .flatten()
6185 })
6186 }
6187
6188 pub fn confirm_code_action(
6189 &mut self,
6190 action: &ConfirmCodeAction,
6191 window: &mut Window,
6192 cx: &mut Context<Self>,
6193 ) -> Option<Task<Result<()>>> {
6194 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6195
6196 let actions_menu =
6197 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6198 menu
6199 } else {
6200 return None;
6201 };
6202
6203 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6204 let action = actions_menu.actions.get(action_ix)?;
6205 let title = action.label();
6206 let buffer = actions_menu.buffer;
6207 let workspace = self.workspace()?;
6208
6209 match action {
6210 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6211 workspace.update(cx, |workspace, cx| {
6212 workspace.schedule_resolved_task(
6213 task_source_kind,
6214 resolved_task,
6215 false,
6216 window,
6217 cx,
6218 );
6219
6220 Some(Task::ready(Ok(())))
6221 })
6222 }
6223 CodeActionsItem::CodeAction {
6224 excerpt_id,
6225 action,
6226 provider,
6227 } => {
6228 let apply_code_action =
6229 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6230 let workspace = workspace.downgrade();
6231 Some(cx.spawn_in(window, async move |editor, cx| {
6232 let project_transaction = apply_code_action.await?;
6233 Self::open_project_transaction(
6234 &editor,
6235 workspace,
6236 project_transaction,
6237 title,
6238 cx,
6239 )
6240 .await
6241 }))
6242 }
6243 CodeActionsItem::DebugScenario(scenario) => {
6244 let context = actions_menu.actions.context;
6245
6246 workspace.update(cx, |workspace, cx| {
6247 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6248 workspace.start_debug_session(
6249 scenario,
6250 context,
6251 Some(buffer),
6252 None,
6253 window,
6254 cx,
6255 );
6256 });
6257 Some(Task::ready(Ok(())))
6258 }
6259 }
6260 }
6261
6262 pub async fn open_project_transaction(
6263 editor: &WeakEntity<Editor>,
6264 workspace: WeakEntity<Workspace>,
6265 transaction: ProjectTransaction,
6266 title: String,
6267 cx: &mut AsyncWindowContext,
6268 ) -> Result<()> {
6269 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6270 cx.update(|_, cx| {
6271 entries.sort_unstable_by_key(|(buffer, _)| {
6272 buffer.read(cx).file().map(|f| f.path().clone())
6273 });
6274 })?;
6275 if entries.is_empty() {
6276 return Ok(());
6277 }
6278
6279 // If the project transaction's edits are all contained within this editor, then
6280 // avoid opening a new editor to display them.
6281
6282 if let [(buffer, transaction)] = &*entries {
6283 let excerpt = editor.update(cx, |editor, cx| {
6284 editor
6285 .buffer()
6286 .read(cx)
6287 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6288 })?;
6289 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6290 && excerpted_buffer == *buffer
6291 {
6292 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6293 let excerpt_range = excerpt_range.to_offset(buffer);
6294 buffer
6295 .edited_ranges_for_transaction::<usize>(transaction)
6296 .all(|range| {
6297 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6298 })
6299 })?;
6300
6301 if all_edits_within_excerpt {
6302 return Ok(());
6303 }
6304 }
6305 }
6306
6307 let mut ranges_to_highlight = Vec::new();
6308 let excerpt_buffer = cx.new(|cx| {
6309 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6310 for (buffer_handle, transaction) in &entries {
6311 let edited_ranges = buffer_handle
6312 .read(cx)
6313 .edited_ranges_for_transaction::<Point>(transaction)
6314 .collect::<Vec<_>>();
6315 let (ranges, _) = multibuffer.set_excerpts_for_path(
6316 PathKey::for_buffer(buffer_handle, cx),
6317 buffer_handle.clone(),
6318 edited_ranges,
6319 multibuffer_context_lines(cx),
6320 cx,
6321 );
6322
6323 ranges_to_highlight.extend(ranges);
6324 }
6325 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6326 multibuffer
6327 })?;
6328
6329 workspace.update_in(cx, |workspace, window, cx| {
6330 let project = workspace.project().clone();
6331 let editor =
6332 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6333 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6334 editor.update(cx, |editor, cx| {
6335 editor.highlight_background::<Self>(
6336 &ranges_to_highlight,
6337 |theme| theme.colors().editor_highlighted_line_background,
6338 cx,
6339 );
6340 });
6341 })?;
6342
6343 Ok(())
6344 }
6345
6346 pub fn clear_code_action_providers(&mut self) {
6347 self.code_action_providers.clear();
6348 self.available_code_actions.take();
6349 }
6350
6351 pub fn add_code_action_provider(
6352 &mut self,
6353 provider: Rc<dyn CodeActionProvider>,
6354 window: &mut Window,
6355 cx: &mut Context<Self>,
6356 ) {
6357 if self
6358 .code_action_providers
6359 .iter()
6360 .any(|existing_provider| existing_provider.id() == provider.id())
6361 {
6362 return;
6363 }
6364
6365 self.code_action_providers.push(provider);
6366 self.refresh_code_actions(window, cx);
6367 }
6368
6369 pub fn remove_code_action_provider(
6370 &mut self,
6371 id: Arc<str>,
6372 window: &mut Window,
6373 cx: &mut Context<Self>,
6374 ) {
6375 self.code_action_providers
6376 .retain(|provider| provider.id() != id);
6377 self.refresh_code_actions(window, cx);
6378 }
6379
6380 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6381 !self.code_action_providers.is_empty()
6382 && EditorSettings::get_global(cx).toolbar.code_actions
6383 }
6384
6385 pub fn has_available_code_actions(&self) -> bool {
6386 self.available_code_actions
6387 .as_ref()
6388 .is_some_and(|(_, actions)| !actions.is_empty())
6389 }
6390
6391 fn render_inline_code_actions(
6392 &self,
6393 icon_size: ui::IconSize,
6394 display_row: DisplayRow,
6395 is_active: bool,
6396 cx: &mut Context<Self>,
6397 ) -> AnyElement {
6398 let show_tooltip = !self.context_menu_visible();
6399 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6400 .icon_size(icon_size)
6401 .shape(ui::IconButtonShape::Square)
6402 .icon_color(ui::Color::Hidden)
6403 .toggle_state(is_active)
6404 .when(show_tooltip, |this| {
6405 this.tooltip({
6406 let focus_handle = self.focus_handle.clone();
6407 move |_window, cx| {
6408 Tooltip::for_action_in(
6409 "Toggle Code Actions",
6410 &ToggleCodeActions {
6411 deployed_from: None,
6412 quick_launch: false,
6413 },
6414 &focus_handle,
6415 cx,
6416 )
6417 }
6418 })
6419 })
6420 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6421 window.focus(&editor.focus_handle(cx));
6422 editor.toggle_code_actions(
6423 &crate::actions::ToggleCodeActions {
6424 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6425 display_row,
6426 )),
6427 quick_launch: false,
6428 },
6429 window,
6430 cx,
6431 );
6432 }))
6433 .into_any_element()
6434 }
6435
6436 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6437 &self.context_menu
6438 }
6439
6440 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6441 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6442 cx.background_executor()
6443 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6444 .await;
6445
6446 let (start_buffer, start, _, end, newest_selection) = this
6447 .update(cx, |this, cx| {
6448 let newest_selection = this.selections.newest_anchor().clone();
6449 if newest_selection.head().diff_base_anchor.is_some() {
6450 return None;
6451 }
6452 let display_snapshot = this.display_snapshot(cx);
6453 let newest_selection_adjusted =
6454 this.selections.newest_adjusted(&display_snapshot);
6455 let buffer = this.buffer.read(cx);
6456
6457 let (start_buffer, start) =
6458 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6459 let (end_buffer, end) =
6460 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6461
6462 Some((start_buffer, start, end_buffer, end, newest_selection))
6463 })?
6464 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6465 .context(
6466 "Expected selection to lie in a single buffer when refreshing code actions",
6467 )?;
6468 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6469 let providers = this.code_action_providers.clone();
6470 let tasks = this
6471 .code_action_providers
6472 .iter()
6473 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6474 .collect::<Vec<_>>();
6475 (providers, tasks)
6476 })?;
6477
6478 let mut actions = Vec::new();
6479 for (provider, provider_actions) in
6480 providers.into_iter().zip(future::join_all(tasks).await)
6481 {
6482 if let Some(provider_actions) = provider_actions.log_err() {
6483 actions.extend(provider_actions.into_iter().map(|action| {
6484 AvailableCodeAction {
6485 excerpt_id: newest_selection.start.excerpt_id,
6486 action,
6487 provider: provider.clone(),
6488 }
6489 }));
6490 }
6491 }
6492
6493 this.update(cx, |this, cx| {
6494 this.available_code_actions = if actions.is_empty() {
6495 None
6496 } else {
6497 Some((
6498 Location {
6499 buffer: start_buffer,
6500 range: start..end,
6501 },
6502 actions.into(),
6503 ))
6504 };
6505 cx.notify();
6506 })
6507 }));
6508 }
6509
6510 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6511 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6512 self.show_git_blame_inline = false;
6513
6514 self.show_git_blame_inline_delay_task =
6515 Some(cx.spawn_in(window, async move |this, cx| {
6516 cx.background_executor().timer(delay).await;
6517
6518 this.update(cx, |this, cx| {
6519 this.show_git_blame_inline = true;
6520 cx.notify();
6521 })
6522 .log_err();
6523 }));
6524 }
6525 }
6526
6527 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6528 let snapshot = self.snapshot(window, cx);
6529 let cursor = self
6530 .selections
6531 .newest::<Point>(&snapshot.display_snapshot)
6532 .head();
6533 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6534 else {
6535 return;
6536 };
6537
6538 let Some(blame) = self.blame.as_ref() else {
6539 return;
6540 };
6541
6542 let row_info = RowInfo {
6543 buffer_id: Some(buffer.remote_id()),
6544 buffer_row: Some(point.row),
6545 ..Default::default()
6546 };
6547 let Some((buffer, blame_entry)) = blame
6548 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6549 .flatten()
6550 else {
6551 return;
6552 };
6553
6554 let anchor = self.selections.newest_anchor().head();
6555 let position = self.to_pixel_point(anchor, &snapshot, window);
6556 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6557 self.show_blame_popover(
6558 buffer,
6559 &blame_entry,
6560 position + last_bounds.origin,
6561 true,
6562 cx,
6563 );
6564 };
6565 }
6566
6567 fn show_blame_popover(
6568 &mut self,
6569 buffer: BufferId,
6570 blame_entry: &BlameEntry,
6571 position: gpui::Point<Pixels>,
6572 ignore_timeout: bool,
6573 cx: &mut Context<Self>,
6574 ) {
6575 if let Some(state) = &mut self.inline_blame_popover {
6576 state.hide_task.take();
6577 } else {
6578 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6579 let blame_entry = blame_entry.clone();
6580 let show_task = cx.spawn(async move |editor, cx| {
6581 if !ignore_timeout {
6582 cx.background_executor()
6583 .timer(std::time::Duration::from_millis(blame_popover_delay))
6584 .await;
6585 }
6586 editor
6587 .update(cx, |editor, cx| {
6588 editor.inline_blame_popover_show_task.take();
6589 let Some(blame) = editor.blame.as_ref() else {
6590 return;
6591 };
6592 let blame = blame.read(cx);
6593 let details = blame.details_for_entry(buffer, &blame_entry);
6594 let markdown = cx.new(|cx| {
6595 Markdown::new(
6596 details
6597 .as_ref()
6598 .map(|message| message.message.clone())
6599 .unwrap_or_default(),
6600 None,
6601 None,
6602 cx,
6603 )
6604 });
6605 editor.inline_blame_popover = Some(InlineBlamePopover {
6606 position,
6607 hide_task: None,
6608 popover_bounds: None,
6609 popover_state: InlineBlamePopoverState {
6610 scroll_handle: ScrollHandle::new(),
6611 commit_message: details,
6612 markdown,
6613 },
6614 keyboard_grace: ignore_timeout,
6615 });
6616 cx.notify();
6617 })
6618 .ok();
6619 });
6620 self.inline_blame_popover_show_task = Some(show_task);
6621 }
6622 }
6623
6624 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6625 self.inline_blame_popover_show_task.take();
6626 if let Some(state) = &mut self.inline_blame_popover {
6627 let hide_task = cx.spawn(async move |editor, cx| {
6628 if !ignore_timeout {
6629 cx.background_executor()
6630 .timer(std::time::Duration::from_millis(100))
6631 .await;
6632 }
6633 editor
6634 .update(cx, |editor, cx| {
6635 editor.inline_blame_popover.take();
6636 cx.notify();
6637 })
6638 .ok();
6639 });
6640 state.hide_task = Some(hide_task);
6641 true
6642 } else {
6643 false
6644 }
6645 }
6646
6647 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6648 if self.pending_rename.is_some() {
6649 return None;
6650 }
6651
6652 let provider = self.semantics_provider.clone()?;
6653 let buffer = self.buffer.read(cx);
6654 let newest_selection = self.selections.newest_anchor().clone();
6655 let cursor_position = newest_selection.head();
6656 let (cursor_buffer, cursor_buffer_position) =
6657 buffer.text_anchor_for_position(cursor_position, cx)?;
6658 let (tail_buffer, tail_buffer_position) =
6659 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6660 if cursor_buffer != tail_buffer {
6661 return None;
6662 }
6663
6664 let snapshot = cursor_buffer.read(cx).snapshot();
6665 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6666 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6667 if start_word_range != end_word_range {
6668 self.document_highlights_task.take();
6669 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6670 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6671 return None;
6672 }
6673
6674 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6675 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6676 cx.background_executor()
6677 .timer(Duration::from_millis(debounce))
6678 .await;
6679
6680 let highlights = if let Some(highlights) = cx
6681 .update(|cx| {
6682 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6683 })
6684 .ok()
6685 .flatten()
6686 {
6687 highlights.await.log_err()
6688 } else {
6689 None
6690 };
6691
6692 if let Some(highlights) = highlights {
6693 this.update(cx, |this, cx| {
6694 if this.pending_rename.is_some() {
6695 return;
6696 }
6697
6698 let buffer = this.buffer.read(cx);
6699 if buffer
6700 .text_anchor_for_position(cursor_position, cx)
6701 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6702 {
6703 return;
6704 }
6705
6706 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6707 let mut write_ranges = Vec::new();
6708 let mut read_ranges = Vec::new();
6709 for highlight in highlights {
6710 let buffer_id = cursor_buffer.read(cx).remote_id();
6711 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6712 {
6713 let start = highlight
6714 .range
6715 .start
6716 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6717 let end = highlight
6718 .range
6719 .end
6720 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6721 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6722 continue;
6723 }
6724
6725 let range =
6726 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6727 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6728 write_ranges.push(range);
6729 } else {
6730 read_ranges.push(range);
6731 }
6732 }
6733 }
6734
6735 this.highlight_background::<DocumentHighlightRead>(
6736 &read_ranges,
6737 |theme| theme.colors().editor_document_highlight_read_background,
6738 cx,
6739 );
6740 this.highlight_background::<DocumentHighlightWrite>(
6741 &write_ranges,
6742 |theme| theme.colors().editor_document_highlight_write_background,
6743 cx,
6744 );
6745 cx.notify();
6746 })
6747 .log_err();
6748 }
6749 }));
6750 None
6751 }
6752
6753 fn prepare_highlight_query_from_selection(
6754 &mut self,
6755 window: &Window,
6756 cx: &mut Context<Editor>,
6757 ) -> Option<(String, Range<Anchor>)> {
6758 if matches!(self.mode, EditorMode::SingleLine) {
6759 return None;
6760 }
6761 if !EditorSettings::get_global(cx).selection_highlight {
6762 return None;
6763 }
6764 if self.selections.count() != 1 || self.selections.line_mode() {
6765 return None;
6766 }
6767 let snapshot = self.snapshot(window, cx);
6768 let selection = self.selections.newest::<Point>(&snapshot);
6769 // If the selection spans multiple rows OR it is empty
6770 if selection.start.row != selection.end.row
6771 || selection.start.column == selection.end.column
6772 {
6773 return None;
6774 }
6775 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6776 let query = snapshot
6777 .buffer_snapshot()
6778 .text_for_range(selection_anchor_range.clone())
6779 .collect::<String>();
6780 if query.trim().is_empty() {
6781 return None;
6782 }
6783 Some((query, selection_anchor_range))
6784 }
6785
6786 fn update_selection_occurrence_highlights(
6787 &mut self,
6788 query_text: String,
6789 query_range: Range<Anchor>,
6790 multi_buffer_range_to_query: Range<Point>,
6791 use_debounce: bool,
6792 window: &mut Window,
6793 cx: &mut Context<Editor>,
6794 ) -> Task<()> {
6795 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6796 cx.spawn_in(window, async move |editor, cx| {
6797 if use_debounce {
6798 cx.background_executor()
6799 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6800 .await;
6801 }
6802 let match_task = cx.background_spawn(async move {
6803 let buffer_ranges = multi_buffer_snapshot
6804 .range_to_buffer_ranges(multi_buffer_range_to_query)
6805 .into_iter()
6806 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6807 let mut match_ranges = Vec::new();
6808 let Ok(regex) = project::search::SearchQuery::text(
6809 query_text.clone(),
6810 false,
6811 false,
6812 false,
6813 Default::default(),
6814 Default::default(),
6815 false,
6816 None,
6817 ) else {
6818 return Vec::default();
6819 };
6820 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6821 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6822 match_ranges.extend(
6823 regex
6824 .search(buffer_snapshot, Some(search_range.clone()))
6825 .await
6826 .into_iter()
6827 .filter_map(|match_range| {
6828 let match_start = buffer_snapshot
6829 .anchor_after(search_range.start + match_range.start);
6830 let match_end = buffer_snapshot
6831 .anchor_before(search_range.start + match_range.end);
6832 let match_anchor_range = Anchor::range_in_buffer(
6833 excerpt_id,
6834 buffer_snapshot.remote_id(),
6835 match_start..match_end,
6836 );
6837 (match_anchor_range != query_range).then_some(match_anchor_range)
6838 }),
6839 );
6840 }
6841 match_ranges
6842 });
6843 let match_ranges = match_task.await;
6844 editor
6845 .update_in(cx, |editor, _, cx| {
6846 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6847 if !match_ranges.is_empty() {
6848 editor.highlight_background::<SelectedTextHighlight>(
6849 &match_ranges,
6850 |theme| theme.colors().editor_document_highlight_bracket_background,
6851 cx,
6852 )
6853 }
6854 })
6855 .log_err();
6856 })
6857 }
6858
6859 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6860 struct NewlineFold;
6861 let type_id = std::any::TypeId::of::<NewlineFold>();
6862 if !self.mode.is_single_line() {
6863 return;
6864 }
6865 let snapshot = self.snapshot(window, cx);
6866 if snapshot.buffer_snapshot().max_point().row == 0 {
6867 return;
6868 }
6869 let task = cx.background_spawn(async move {
6870 let new_newlines = snapshot
6871 .buffer_chars_at(0)
6872 .filter_map(|(c, i)| {
6873 if c == '\n' {
6874 Some(
6875 snapshot.buffer_snapshot().anchor_after(i)
6876 ..snapshot.buffer_snapshot().anchor_before(i + 1),
6877 )
6878 } else {
6879 None
6880 }
6881 })
6882 .collect::<Vec<_>>();
6883 let existing_newlines = snapshot
6884 .folds_in_range(0..snapshot.buffer_snapshot().len())
6885 .filter_map(|fold| {
6886 if fold.placeholder.type_tag == Some(type_id) {
6887 Some(fold.range.start..fold.range.end)
6888 } else {
6889 None
6890 }
6891 })
6892 .collect::<Vec<_>>();
6893
6894 (new_newlines, existing_newlines)
6895 });
6896 self.folding_newlines = cx.spawn(async move |this, cx| {
6897 let (new_newlines, existing_newlines) = task.await;
6898 if new_newlines == existing_newlines {
6899 return;
6900 }
6901 let placeholder = FoldPlaceholder {
6902 render: Arc::new(move |_, _, cx| {
6903 div()
6904 .bg(cx.theme().status().hint_background)
6905 .border_b_1()
6906 .size_full()
6907 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6908 .border_color(cx.theme().status().hint)
6909 .child("\\n")
6910 .into_any()
6911 }),
6912 constrain_width: false,
6913 merge_adjacent: false,
6914 type_tag: Some(type_id),
6915 };
6916 let creases = new_newlines
6917 .into_iter()
6918 .map(|range| Crease::simple(range, placeholder.clone()))
6919 .collect();
6920 this.update(cx, |this, cx| {
6921 this.display_map.update(cx, |display_map, cx| {
6922 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6923 display_map.fold(creases, cx);
6924 });
6925 })
6926 .ok();
6927 });
6928 }
6929
6930 fn refresh_selected_text_highlights(
6931 &mut self,
6932 on_buffer_edit: bool,
6933 window: &mut Window,
6934 cx: &mut Context<Editor>,
6935 ) {
6936 let Some((query_text, query_range)) =
6937 self.prepare_highlight_query_from_selection(window, cx)
6938 else {
6939 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6940 self.quick_selection_highlight_task.take();
6941 self.debounced_selection_highlight_task.take();
6942 return;
6943 };
6944 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6945 if on_buffer_edit
6946 || self
6947 .quick_selection_highlight_task
6948 .as_ref()
6949 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6950 {
6951 let multi_buffer_visible_start = self
6952 .scroll_manager
6953 .anchor()
6954 .anchor
6955 .to_point(&multi_buffer_snapshot);
6956 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6957 multi_buffer_visible_start
6958 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6959 Bias::Left,
6960 );
6961 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6962 self.quick_selection_highlight_task = Some((
6963 query_range.clone(),
6964 self.update_selection_occurrence_highlights(
6965 query_text.clone(),
6966 query_range.clone(),
6967 multi_buffer_visible_range,
6968 false,
6969 window,
6970 cx,
6971 ),
6972 ));
6973 }
6974 if on_buffer_edit
6975 || self
6976 .debounced_selection_highlight_task
6977 .as_ref()
6978 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6979 {
6980 let multi_buffer_start = multi_buffer_snapshot
6981 .anchor_before(0)
6982 .to_point(&multi_buffer_snapshot);
6983 let multi_buffer_end = multi_buffer_snapshot
6984 .anchor_after(multi_buffer_snapshot.len())
6985 .to_point(&multi_buffer_snapshot);
6986 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6987 self.debounced_selection_highlight_task = Some((
6988 query_range.clone(),
6989 self.update_selection_occurrence_highlights(
6990 query_text,
6991 query_range,
6992 multi_buffer_full_range,
6993 true,
6994 window,
6995 cx,
6996 ),
6997 ));
6998 }
6999 }
7000
7001 pub fn refresh_edit_prediction(
7002 &mut self,
7003 debounce: bool,
7004 user_requested: bool,
7005 window: &mut Window,
7006 cx: &mut Context<Self>,
7007 ) -> Option<()> {
7008 if DisableAiSettings::get_global(cx).disable_ai {
7009 return None;
7010 }
7011
7012 let provider = self.edit_prediction_provider()?;
7013 let cursor = self.selections.newest_anchor().head();
7014 let (buffer, cursor_buffer_position) =
7015 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7016
7017 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7018 self.discard_edit_prediction(false, cx);
7019 return None;
7020 }
7021
7022 self.update_visible_edit_prediction(window, cx);
7023
7024 if !user_requested
7025 && (!self.should_show_edit_predictions()
7026 || !self.is_focused(window)
7027 || buffer.read(cx).is_empty())
7028 {
7029 self.discard_edit_prediction(false, cx);
7030 return None;
7031 }
7032
7033 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7034 Some(())
7035 }
7036
7037 fn show_edit_predictions_in_menu(&self) -> bool {
7038 match self.edit_prediction_settings {
7039 EditPredictionSettings::Disabled => false,
7040 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7041 }
7042 }
7043
7044 pub fn edit_predictions_enabled(&self) -> bool {
7045 match self.edit_prediction_settings {
7046 EditPredictionSettings::Disabled => false,
7047 EditPredictionSettings::Enabled { .. } => true,
7048 }
7049 }
7050
7051 fn edit_prediction_requires_modifier(&self) -> bool {
7052 match self.edit_prediction_settings {
7053 EditPredictionSettings::Disabled => false,
7054 EditPredictionSettings::Enabled {
7055 preview_requires_modifier,
7056 ..
7057 } => preview_requires_modifier,
7058 }
7059 }
7060
7061 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7062 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7063 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7064 self.discard_edit_prediction(false, cx);
7065 } else {
7066 let selection = self.selections.newest_anchor();
7067 let cursor = selection.head();
7068
7069 if let Some((buffer, cursor_buffer_position)) =
7070 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7071 {
7072 self.edit_prediction_settings =
7073 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7074 }
7075 }
7076 }
7077
7078 fn edit_prediction_settings_at_position(
7079 &self,
7080 buffer: &Entity<Buffer>,
7081 buffer_position: language::Anchor,
7082 cx: &App,
7083 ) -> EditPredictionSettings {
7084 if !self.mode.is_full()
7085 || !self.show_edit_predictions_override.unwrap_or(true)
7086 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7087 {
7088 return EditPredictionSettings::Disabled;
7089 }
7090
7091 let buffer = buffer.read(cx);
7092
7093 let file = buffer.file();
7094
7095 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7096 return EditPredictionSettings::Disabled;
7097 };
7098
7099 let by_provider = matches!(
7100 self.menu_edit_predictions_policy,
7101 MenuEditPredictionsPolicy::ByProvider
7102 );
7103
7104 let show_in_menu = by_provider
7105 && self
7106 .edit_prediction_provider
7107 .as_ref()
7108 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7109
7110 let preview_requires_modifier =
7111 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7112
7113 EditPredictionSettings::Enabled {
7114 show_in_menu,
7115 preview_requires_modifier,
7116 }
7117 }
7118
7119 fn should_show_edit_predictions(&self) -> bool {
7120 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7121 }
7122
7123 pub fn edit_prediction_preview_is_active(&self) -> bool {
7124 matches!(
7125 self.edit_prediction_preview,
7126 EditPredictionPreview::Active { .. }
7127 )
7128 }
7129
7130 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7131 let cursor = self.selections.newest_anchor().head();
7132 if let Some((buffer, cursor_position)) =
7133 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7134 {
7135 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7136 } else {
7137 false
7138 }
7139 }
7140
7141 pub fn supports_minimap(&self, cx: &App) -> bool {
7142 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7143 }
7144
7145 fn edit_predictions_enabled_in_buffer(
7146 &self,
7147 buffer: &Entity<Buffer>,
7148 buffer_position: language::Anchor,
7149 cx: &App,
7150 ) -> bool {
7151 maybe!({
7152 if self.read_only(cx) {
7153 return Some(false);
7154 }
7155 let provider = self.edit_prediction_provider()?;
7156 if !provider.is_enabled(buffer, buffer_position, cx) {
7157 return Some(false);
7158 }
7159 let buffer = buffer.read(cx);
7160 let Some(file) = buffer.file() else {
7161 return Some(true);
7162 };
7163 let settings = all_language_settings(Some(file), cx);
7164 Some(settings.edit_predictions_enabled_for_file(file, cx))
7165 })
7166 .unwrap_or(false)
7167 }
7168
7169 fn cycle_edit_prediction(
7170 &mut self,
7171 direction: Direction,
7172 window: &mut Window,
7173 cx: &mut Context<Self>,
7174 ) -> Option<()> {
7175 let provider = self.edit_prediction_provider()?;
7176 let cursor = self.selections.newest_anchor().head();
7177 let (buffer, cursor_buffer_position) =
7178 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7179 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7180 return None;
7181 }
7182
7183 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7184 self.update_visible_edit_prediction(window, cx);
7185
7186 Some(())
7187 }
7188
7189 pub fn show_edit_prediction(
7190 &mut self,
7191 _: &ShowEditPrediction,
7192 window: &mut Window,
7193 cx: &mut Context<Self>,
7194 ) {
7195 if !self.has_active_edit_prediction() {
7196 self.refresh_edit_prediction(false, true, window, cx);
7197 return;
7198 }
7199
7200 self.update_visible_edit_prediction(window, cx);
7201 }
7202
7203 pub fn display_cursor_names(
7204 &mut self,
7205 _: &DisplayCursorNames,
7206 window: &mut Window,
7207 cx: &mut Context<Self>,
7208 ) {
7209 self.show_cursor_names(window, cx);
7210 }
7211
7212 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7213 self.show_cursor_names = true;
7214 cx.notify();
7215 cx.spawn_in(window, async move |this, cx| {
7216 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7217 this.update(cx, |this, cx| {
7218 this.show_cursor_names = false;
7219 cx.notify()
7220 })
7221 .ok()
7222 })
7223 .detach();
7224 }
7225
7226 pub fn next_edit_prediction(
7227 &mut self,
7228 _: &NextEditPrediction,
7229 window: &mut Window,
7230 cx: &mut Context<Self>,
7231 ) {
7232 if self.has_active_edit_prediction() {
7233 self.cycle_edit_prediction(Direction::Next, window, cx);
7234 } else {
7235 let is_copilot_disabled = self
7236 .refresh_edit_prediction(false, true, window, cx)
7237 .is_none();
7238 if is_copilot_disabled {
7239 cx.propagate();
7240 }
7241 }
7242 }
7243
7244 pub fn previous_edit_prediction(
7245 &mut self,
7246 _: &PreviousEditPrediction,
7247 window: &mut Window,
7248 cx: &mut Context<Self>,
7249 ) {
7250 if self.has_active_edit_prediction() {
7251 self.cycle_edit_prediction(Direction::Prev, window, cx);
7252 } else {
7253 let is_copilot_disabled = self
7254 .refresh_edit_prediction(false, true, window, cx)
7255 .is_none();
7256 if is_copilot_disabled {
7257 cx.propagate();
7258 }
7259 }
7260 }
7261
7262 pub fn accept_edit_prediction(
7263 &mut self,
7264 _: &AcceptEditPrediction,
7265 window: &mut Window,
7266 cx: &mut Context<Self>,
7267 ) {
7268 if self.show_edit_predictions_in_menu() {
7269 self.hide_context_menu(window, cx);
7270 }
7271
7272 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7273 return;
7274 };
7275
7276 match &active_edit_prediction.completion {
7277 EditPrediction::MoveWithin { target, .. } => {
7278 let target = *target;
7279
7280 if let Some(position_map) = &self.last_position_map {
7281 if position_map
7282 .visible_row_range
7283 .contains(&target.to_display_point(&position_map.snapshot).row())
7284 || !self.edit_prediction_requires_modifier()
7285 {
7286 self.unfold_ranges(&[target..target], true, false, cx);
7287 // Note that this is also done in vim's handler of the Tab action.
7288 self.change_selections(
7289 SelectionEffects::scroll(Autoscroll::newest()),
7290 window,
7291 cx,
7292 |selections| {
7293 selections.select_anchor_ranges([target..target]);
7294 },
7295 );
7296 self.clear_row_highlights::<EditPredictionPreview>();
7297
7298 self.edit_prediction_preview
7299 .set_previous_scroll_position(None);
7300 } else {
7301 self.edit_prediction_preview
7302 .set_previous_scroll_position(Some(
7303 position_map.snapshot.scroll_anchor,
7304 ));
7305
7306 self.highlight_rows::<EditPredictionPreview>(
7307 target..target,
7308 cx.theme().colors().editor_highlighted_line_background,
7309 RowHighlightOptions {
7310 autoscroll: true,
7311 ..Default::default()
7312 },
7313 cx,
7314 );
7315 self.request_autoscroll(Autoscroll::fit(), cx);
7316 }
7317 }
7318 }
7319 EditPrediction::MoveOutside { snapshot, target } => {
7320 if let Some(workspace) = self.workspace() {
7321 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7322 .detach_and_log_err(cx);
7323 }
7324 }
7325 EditPrediction::Edit { edits, .. } => {
7326 self.report_edit_prediction_event(
7327 active_edit_prediction.completion_id.clone(),
7328 true,
7329 cx,
7330 );
7331
7332 if let Some(provider) = self.edit_prediction_provider() {
7333 provider.accept(cx);
7334 }
7335
7336 // Store the transaction ID and selections before applying the edit
7337 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7338
7339 let snapshot = self.buffer.read(cx).snapshot(cx);
7340 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7341
7342 self.buffer.update(cx, |buffer, cx| {
7343 buffer.edit(edits.iter().cloned(), None, cx)
7344 });
7345
7346 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7347 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7348 });
7349
7350 let selections = self.selections.disjoint_anchors_arc();
7351 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7352 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7353 if has_new_transaction {
7354 self.selection_history
7355 .insert_transaction(transaction_id_now, selections);
7356 }
7357 }
7358
7359 self.update_visible_edit_prediction(window, cx);
7360 if self.active_edit_prediction.is_none() {
7361 self.refresh_edit_prediction(true, true, window, cx);
7362 }
7363
7364 cx.notify();
7365 }
7366 }
7367
7368 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7369 }
7370
7371 pub fn accept_partial_edit_prediction(
7372 &mut self,
7373 _: &AcceptPartialEditPrediction,
7374 window: &mut Window,
7375 cx: &mut Context<Self>,
7376 ) {
7377 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7378 return;
7379 };
7380 if self.selections.count() != 1 {
7381 return;
7382 }
7383
7384 match &active_edit_prediction.completion {
7385 EditPrediction::MoveWithin { target, .. } => {
7386 let target = *target;
7387 self.change_selections(
7388 SelectionEffects::scroll(Autoscroll::newest()),
7389 window,
7390 cx,
7391 |selections| {
7392 selections.select_anchor_ranges([target..target]);
7393 },
7394 );
7395 }
7396 EditPrediction::MoveOutside { snapshot, target } => {
7397 if let Some(workspace) = self.workspace() {
7398 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7399 .detach_and_log_err(cx);
7400 }
7401 }
7402 EditPrediction::Edit { edits, .. } => {
7403 self.report_edit_prediction_event(
7404 active_edit_prediction.completion_id.clone(),
7405 true,
7406 cx,
7407 );
7408
7409 // Find an insertion that starts at the cursor position.
7410 let snapshot = self.buffer.read(cx).snapshot(cx);
7411 let cursor_offset = self
7412 .selections
7413 .newest::<usize>(&self.display_snapshot(cx))
7414 .head();
7415 let insertion = edits.iter().find_map(|(range, text)| {
7416 let range = range.to_offset(&snapshot);
7417 if range.is_empty() && range.start == cursor_offset {
7418 Some(text)
7419 } else {
7420 None
7421 }
7422 });
7423
7424 if let Some(text) = insertion {
7425 let mut partial_completion = text
7426 .chars()
7427 .by_ref()
7428 .take_while(|c| c.is_alphabetic())
7429 .collect::<String>();
7430 if partial_completion.is_empty() {
7431 partial_completion = text
7432 .chars()
7433 .by_ref()
7434 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7435 .collect::<String>();
7436 }
7437
7438 cx.emit(EditorEvent::InputHandled {
7439 utf16_range_to_replace: None,
7440 text: partial_completion.clone().into(),
7441 });
7442
7443 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7444
7445 self.refresh_edit_prediction(true, true, window, cx);
7446 cx.notify();
7447 } else {
7448 self.accept_edit_prediction(&Default::default(), window, cx);
7449 }
7450 }
7451 }
7452 }
7453
7454 fn discard_edit_prediction(
7455 &mut self,
7456 should_report_edit_prediction_event: bool,
7457 cx: &mut Context<Self>,
7458 ) -> bool {
7459 if should_report_edit_prediction_event {
7460 let completion_id = self
7461 .active_edit_prediction
7462 .as_ref()
7463 .and_then(|active_completion| active_completion.completion_id.clone());
7464
7465 self.report_edit_prediction_event(completion_id, false, cx);
7466 }
7467
7468 if let Some(provider) = self.edit_prediction_provider() {
7469 provider.discard(cx);
7470 }
7471
7472 self.take_active_edit_prediction(cx)
7473 }
7474
7475 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7476 let Some(provider) = self.edit_prediction_provider() else {
7477 return;
7478 };
7479
7480 let Some((_, buffer, _)) = self
7481 .buffer
7482 .read(cx)
7483 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7484 else {
7485 return;
7486 };
7487
7488 let extension = buffer
7489 .read(cx)
7490 .file()
7491 .and_then(|file| Some(file.path().extension()?.to_string()));
7492
7493 let event_type = match accepted {
7494 true => "Edit Prediction Accepted",
7495 false => "Edit Prediction Discarded",
7496 };
7497 telemetry::event!(
7498 event_type,
7499 provider = provider.name(),
7500 prediction_id = id,
7501 suggestion_accepted = accepted,
7502 file_extension = extension,
7503 );
7504 }
7505
7506 fn open_editor_at_anchor(
7507 snapshot: &language::BufferSnapshot,
7508 target: language::Anchor,
7509 workspace: &Entity<Workspace>,
7510 window: &mut Window,
7511 cx: &mut App,
7512 ) -> Task<Result<()>> {
7513 workspace.update(cx, |workspace, cx| {
7514 let path = snapshot.file().map(|file| file.full_path(cx));
7515 let Some(path) =
7516 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7517 else {
7518 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7519 };
7520 let target = text::ToPoint::to_point(&target, snapshot);
7521 let item = workspace.open_path(path, None, true, window, cx);
7522 window.spawn(cx, async move |cx| {
7523 let Some(editor) = item.await?.downcast::<Editor>() else {
7524 return Ok(());
7525 };
7526 editor
7527 .update_in(cx, |editor, window, cx| {
7528 editor.go_to_singleton_buffer_point(target, window, cx);
7529 })
7530 .ok();
7531 anyhow::Ok(())
7532 })
7533 })
7534 }
7535
7536 pub fn has_active_edit_prediction(&self) -> bool {
7537 self.active_edit_prediction.is_some()
7538 }
7539
7540 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7541 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7542 return false;
7543 };
7544
7545 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7546 self.clear_highlights::<EditPredictionHighlight>(cx);
7547 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7548 true
7549 }
7550
7551 /// Returns true when we're displaying the edit prediction popover below the cursor
7552 /// like we are not previewing and the LSP autocomplete menu is visible
7553 /// or we are in `when_holding_modifier` mode.
7554 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7555 if self.edit_prediction_preview_is_active()
7556 || !self.show_edit_predictions_in_menu()
7557 || !self.edit_predictions_enabled()
7558 {
7559 return false;
7560 }
7561
7562 if self.has_visible_completions_menu() {
7563 return true;
7564 }
7565
7566 has_completion && self.edit_prediction_requires_modifier()
7567 }
7568
7569 fn handle_modifiers_changed(
7570 &mut self,
7571 modifiers: Modifiers,
7572 position_map: &PositionMap,
7573 window: &mut Window,
7574 cx: &mut Context<Self>,
7575 ) {
7576 if self.show_edit_predictions_in_menu() {
7577 self.update_edit_prediction_preview(&modifiers, window, cx);
7578 }
7579
7580 self.update_selection_mode(&modifiers, position_map, window, cx);
7581
7582 let mouse_position = window.mouse_position();
7583 if !position_map.text_hitbox.is_hovered(window) {
7584 return;
7585 }
7586
7587 self.update_hovered_link(
7588 position_map.point_for_position(mouse_position),
7589 &position_map.snapshot,
7590 modifiers,
7591 window,
7592 cx,
7593 )
7594 }
7595
7596 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7597 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7598 if invert {
7599 match multi_cursor_setting {
7600 MultiCursorModifier::Alt => modifiers.alt,
7601 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7602 }
7603 } else {
7604 match multi_cursor_setting {
7605 MultiCursorModifier::Alt => modifiers.secondary(),
7606 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7607 }
7608 }
7609 }
7610
7611 fn columnar_selection_mode(
7612 modifiers: &Modifiers,
7613 cx: &mut Context<Self>,
7614 ) -> Option<ColumnarMode> {
7615 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7616 if Self::multi_cursor_modifier(false, modifiers, cx) {
7617 Some(ColumnarMode::FromMouse)
7618 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7619 Some(ColumnarMode::FromSelection)
7620 } else {
7621 None
7622 }
7623 } else {
7624 None
7625 }
7626 }
7627
7628 fn update_selection_mode(
7629 &mut self,
7630 modifiers: &Modifiers,
7631 position_map: &PositionMap,
7632 window: &mut Window,
7633 cx: &mut Context<Self>,
7634 ) {
7635 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7636 return;
7637 };
7638 if self.selections.pending_anchor().is_none() {
7639 return;
7640 }
7641
7642 let mouse_position = window.mouse_position();
7643 let point_for_position = position_map.point_for_position(mouse_position);
7644 let position = point_for_position.previous_valid;
7645
7646 self.select(
7647 SelectPhase::BeginColumnar {
7648 position,
7649 reset: false,
7650 mode,
7651 goal_column: point_for_position.exact_unclipped.column(),
7652 },
7653 window,
7654 cx,
7655 );
7656 }
7657
7658 fn update_edit_prediction_preview(
7659 &mut self,
7660 modifiers: &Modifiers,
7661 window: &mut Window,
7662 cx: &mut Context<Self>,
7663 ) {
7664 let mut modifiers_held = false;
7665 if let Some(accept_keystroke) = self
7666 .accept_edit_prediction_keybind(false, window, cx)
7667 .keystroke()
7668 {
7669 modifiers_held = modifiers_held
7670 || (accept_keystroke.modifiers() == modifiers
7671 && accept_keystroke.modifiers().modified());
7672 };
7673 if let Some(accept_partial_keystroke) = self
7674 .accept_edit_prediction_keybind(true, window, cx)
7675 .keystroke()
7676 {
7677 modifiers_held = modifiers_held
7678 || (accept_partial_keystroke.modifiers() == modifiers
7679 && accept_partial_keystroke.modifiers().modified());
7680 }
7681
7682 if modifiers_held {
7683 if matches!(
7684 self.edit_prediction_preview,
7685 EditPredictionPreview::Inactive { .. }
7686 ) {
7687 self.edit_prediction_preview = EditPredictionPreview::Active {
7688 previous_scroll_position: None,
7689 since: Instant::now(),
7690 };
7691
7692 self.update_visible_edit_prediction(window, cx);
7693 cx.notify();
7694 }
7695 } else if let EditPredictionPreview::Active {
7696 previous_scroll_position,
7697 since,
7698 } = self.edit_prediction_preview
7699 {
7700 if let (Some(previous_scroll_position), Some(position_map)) =
7701 (previous_scroll_position, self.last_position_map.as_ref())
7702 {
7703 self.set_scroll_position(
7704 previous_scroll_position
7705 .scroll_position(&position_map.snapshot.display_snapshot),
7706 window,
7707 cx,
7708 );
7709 }
7710
7711 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7712 released_too_fast: since.elapsed() < Duration::from_millis(200),
7713 };
7714 self.clear_row_highlights::<EditPredictionPreview>();
7715 self.update_visible_edit_prediction(window, cx);
7716 cx.notify();
7717 }
7718 }
7719
7720 fn update_visible_edit_prediction(
7721 &mut self,
7722 _window: &mut Window,
7723 cx: &mut Context<Self>,
7724 ) -> Option<()> {
7725 if DisableAiSettings::get_global(cx).disable_ai {
7726 return None;
7727 }
7728
7729 if self.ime_transaction.is_some() {
7730 self.discard_edit_prediction(false, cx);
7731 return None;
7732 }
7733
7734 let selection = self.selections.newest_anchor();
7735 let cursor = selection.head();
7736 let multibuffer = self.buffer.read(cx).snapshot(cx);
7737 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7738 let excerpt_id = cursor.excerpt_id;
7739
7740 let show_in_menu = self.show_edit_predictions_in_menu();
7741 let completions_menu_has_precedence = !show_in_menu
7742 && (self.context_menu.borrow().is_some()
7743 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7744
7745 if completions_menu_has_precedence
7746 || !offset_selection.is_empty()
7747 || self
7748 .active_edit_prediction
7749 .as_ref()
7750 .is_some_and(|completion| {
7751 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7752 return false;
7753 };
7754 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7755 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7756 !invalidation_range.contains(&offset_selection.head())
7757 })
7758 {
7759 self.discard_edit_prediction(false, cx);
7760 return None;
7761 }
7762
7763 self.take_active_edit_prediction(cx);
7764 let Some(provider) = self.edit_prediction_provider() else {
7765 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7766 return None;
7767 };
7768
7769 let (buffer, cursor_buffer_position) =
7770 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7771
7772 self.edit_prediction_settings =
7773 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7774
7775 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7776
7777 if self.edit_prediction_indent_conflict {
7778 let cursor_point = cursor.to_point(&multibuffer);
7779
7780 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7781
7782 if let Some((_, indent)) = indents.iter().next()
7783 && indent.len == cursor_point.column
7784 {
7785 self.edit_prediction_indent_conflict = false;
7786 }
7787 }
7788
7789 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7790
7791 let (completion_id, edits, edit_preview) = match edit_prediction {
7792 edit_prediction::EditPrediction::Local {
7793 id,
7794 edits,
7795 edit_preview,
7796 } => (id, edits, edit_preview),
7797 edit_prediction::EditPrediction::Jump {
7798 id,
7799 snapshot,
7800 target,
7801 } => {
7802 self.stale_edit_prediction_in_menu = None;
7803 self.active_edit_prediction = Some(EditPredictionState {
7804 inlay_ids: vec![],
7805 completion: EditPrediction::MoveOutside { snapshot, target },
7806 completion_id: id,
7807 invalidation_range: None,
7808 });
7809 cx.notify();
7810 return Some(());
7811 }
7812 };
7813
7814 let edits = edits
7815 .into_iter()
7816 .flat_map(|(range, new_text)| {
7817 Some((
7818 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7819 new_text,
7820 ))
7821 })
7822 .collect::<Vec<_>>();
7823 if edits.is_empty() {
7824 return None;
7825 }
7826
7827 let first_edit_start = edits.first().unwrap().0.start;
7828 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7829 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7830
7831 let last_edit_end = edits.last().unwrap().0.end;
7832 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7833 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7834
7835 let cursor_row = cursor.to_point(&multibuffer).row;
7836
7837 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7838
7839 let mut inlay_ids = Vec::new();
7840 let invalidation_row_range;
7841 let move_invalidation_row_range = if cursor_row < edit_start_row {
7842 Some(cursor_row..edit_end_row)
7843 } else if cursor_row > edit_end_row {
7844 Some(edit_start_row..cursor_row)
7845 } else {
7846 None
7847 };
7848 let supports_jump = self
7849 .edit_prediction_provider
7850 .as_ref()
7851 .map(|provider| provider.provider.supports_jump_to_edit())
7852 .unwrap_or(true);
7853
7854 let is_move = supports_jump
7855 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7856 let completion = if is_move {
7857 invalidation_row_range =
7858 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7859 let target = first_edit_start;
7860 EditPrediction::MoveWithin { target, snapshot }
7861 } else {
7862 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7863 && !self.edit_predictions_hidden_for_vim_mode;
7864
7865 if show_completions_in_buffer {
7866 if edits
7867 .iter()
7868 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7869 {
7870 let mut inlays = Vec::new();
7871 for (range, new_text) in &edits {
7872 let inlay = Inlay::edit_prediction(
7873 post_inc(&mut self.next_inlay_id),
7874 range.start,
7875 new_text.as_str(),
7876 );
7877 inlay_ids.push(inlay.id);
7878 inlays.push(inlay);
7879 }
7880
7881 self.splice_inlays(&[], inlays, cx);
7882 } else {
7883 let background_color = cx.theme().status().deleted_background;
7884 self.highlight_text::<EditPredictionHighlight>(
7885 edits.iter().map(|(range, _)| range.clone()).collect(),
7886 HighlightStyle {
7887 background_color: Some(background_color),
7888 ..Default::default()
7889 },
7890 cx,
7891 );
7892 }
7893 }
7894
7895 invalidation_row_range = edit_start_row..edit_end_row;
7896
7897 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7898 if provider.show_tab_accept_marker() {
7899 EditDisplayMode::TabAccept
7900 } else {
7901 EditDisplayMode::Inline
7902 }
7903 } else {
7904 EditDisplayMode::DiffPopover
7905 };
7906
7907 EditPrediction::Edit {
7908 edits,
7909 edit_preview,
7910 display_mode,
7911 snapshot,
7912 }
7913 };
7914
7915 let invalidation_range = multibuffer
7916 .anchor_before(Point::new(invalidation_row_range.start, 0))
7917 ..multibuffer.anchor_after(Point::new(
7918 invalidation_row_range.end,
7919 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7920 ));
7921
7922 self.stale_edit_prediction_in_menu = None;
7923 self.active_edit_prediction = Some(EditPredictionState {
7924 inlay_ids,
7925 completion,
7926 completion_id,
7927 invalidation_range: Some(invalidation_range),
7928 });
7929
7930 cx.notify();
7931
7932 Some(())
7933 }
7934
7935 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7936 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7937 }
7938
7939 fn clear_tasks(&mut self) {
7940 self.tasks.clear()
7941 }
7942
7943 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7944 if self.tasks.insert(key, value).is_some() {
7945 // This case should hopefully be rare, but just in case...
7946 log::error!(
7947 "multiple different run targets found on a single line, only the last target will be rendered"
7948 )
7949 }
7950 }
7951
7952 /// Get all display points of breakpoints that will be rendered within editor
7953 ///
7954 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7955 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7956 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7957 fn active_breakpoints(
7958 &self,
7959 range: Range<DisplayRow>,
7960 window: &mut Window,
7961 cx: &mut Context<Self>,
7962 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7963 let mut breakpoint_display_points = HashMap::default();
7964
7965 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7966 return breakpoint_display_points;
7967 };
7968
7969 let snapshot = self.snapshot(window, cx);
7970
7971 let multi_buffer_snapshot = snapshot.buffer_snapshot();
7972 let Some(project) = self.project() else {
7973 return breakpoint_display_points;
7974 };
7975
7976 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7977 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7978
7979 for (buffer_snapshot, range, excerpt_id) in
7980 multi_buffer_snapshot.range_to_buffer_ranges(range)
7981 {
7982 let Some(buffer) = project
7983 .read(cx)
7984 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7985 else {
7986 continue;
7987 };
7988 let breakpoints = breakpoint_store.read(cx).breakpoints(
7989 &buffer,
7990 Some(
7991 buffer_snapshot.anchor_before(range.start)
7992 ..buffer_snapshot.anchor_after(range.end),
7993 ),
7994 buffer_snapshot,
7995 cx,
7996 );
7997 for (breakpoint, state) in breakpoints {
7998 let multi_buffer_anchor =
7999 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8000 let position = multi_buffer_anchor
8001 .to_point(&multi_buffer_snapshot)
8002 .to_display_point(&snapshot);
8003
8004 breakpoint_display_points.insert(
8005 position.row(),
8006 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8007 );
8008 }
8009 }
8010
8011 breakpoint_display_points
8012 }
8013
8014 fn breakpoint_context_menu(
8015 &self,
8016 anchor: Anchor,
8017 window: &mut Window,
8018 cx: &mut Context<Self>,
8019 ) -> Entity<ui::ContextMenu> {
8020 let weak_editor = cx.weak_entity();
8021 let focus_handle = self.focus_handle(cx);
8022
8023 let row = self
8024 .buffer
8025 .read(cx)
8026 .snapshot(cx)
8027 .summary_for_anchor::<Point>(&anchor)
8028 .row;
8029
8030 let breakpoint = self
8031 .breakpoint_at_row(row, window, cx)
8032 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8033
8034 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8035 "Edit Log Breakpoint"
8036 } else {
8037 "Set Log Breakpoint"
8038 };
8039
8040 let condition_breakpoint_msg = if breakpoint
8041 .as_ref()
8042 .is_some_and(|bp| bp.1.condition.is_some())
8043 {
8044 "Edit Condition Breakpoint"
8045 } else {
8046 "Set Condition Breakpoint"
8047 };
8048
8049 let hit_condition_breakpoint_msg = if breakpoint
8050 .as_ref()
8051 .is_some_and(|bp| bp.1.hit_condition.is_some())
8052 {
8053 "Edit Hit Condition Breakpoint"
8054 } else {
8055 "Set Hit Condition Breakpoint"
8056 };
8057
8058 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8059 "Unset Breakpoint"
8060 } else {
8061 "Set Breakpoint"
8062 };
8063
8064 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8065
8066 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8067 BreakpointState::Enabled => Some("Disable"),
8068 BreakpointState::Disabled => Some("Enable"),
8069 });
8070
8071 let (anchor, breakpoint) =
8072 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8073
8074 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8075 menu.on_blur_subscription(Subscription::new(|| {}))
8076 .context(focus_handle)
8077 .when(run_to_cursor, |this| {
8078 let weak_editor = weak_editor.clone();
8079 this.entry("Run to cursor", None, move |window, cx| {
8080 weak_editor
8081 .update(cx, |editor, cx| {
8082 editor.change_selections(
8083 SelectionEffects::no_scroll(),
8084 window,
8085 cx,
8086 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8087 );
8088 })
8089 .ok();
8090
8091 window.dispatch_action(Box::new(RunToCursor), cx);
8092 })
8093 .separator()
8094 })
8095 .when_some(toggle_state_msg, |this, msg| {
8096 this.entry(msg, None, {
8097 let weak_editor = weak_editor.clone();
8098 let breakpoint = breakpoint.clone();
8099 move |_window, cx| {
8100 weak_editor
8101 .update(cx, |this, cx| {
8102 this.edit_breakpoint_at_anchor(
8103 anchor,
8104 breakpoint.as_ref().clone(),
8105 BreakpointEditAction::InvertState,
8106 cx,
8107 );
8108 })
8109 .log_err();
8110 }
8111 })
8112 })
8113 .entry(set_breakpoint_msg, None, {
8114 let weak_editor = weak_editor.clone();
8115 let breakpoint = breakpoint.clone();
8116 move |_window, cx| {
8117 weak_editor
8118 .update(cx, |this, cx| {
8119 this.edit_breakpoint_at_anchor(
8120 anchor,
8121 breakpoint.as_ref().clone(),
8122 BreakpointEditAction::Toggle,
8123 cx,
8124 );
8125 })
8126 .log_err();
8127 }
8128 })
8129 .entry(log_breakpoint_msg, None, {
8130 let breakpoint = breakpoint.clone();
8131 let weak_editor = weak_editor.clone();
8132 move |window, cx| {
8133 weak_editor
8134 .update(cx, |this, cx| {
8135 this.add_edit_breakpoint_block(
8136 anchor,
8137 breakpoint.as_ref(),
8138 BreakpointPromptEditAction::Log,
8139 window,
8140 cx,
8141 );
8142 })
8143 .log_err();
8144 }
8145 })
8146 .entry(condition_breakpoint_msg, None, {
8147 let breakpoint = breakpoint.clone();
8148 let weak_editor = weak_editor.clone();
8149 move |window, cx| {
8150 weak_editor
8151 .update(cx, |this, cx| {
8152 this.add_edit_breakpoint_block(
8153 anchor,
8154 breakpoint.as_ref(),
8155 BreakpointPromptEditAction::Condition,
8156 window,
8157 cx,
8158 );
8159 })
8160 .log_err();
8161 }
8162 })
8163 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8164 weak_editor
8165 .update(cx, |this, cx| {
8166 this.add_edit_breakpoint_block(
8167 anchor,
8168 breakpoint.as_ref(),
8169 BreakpointPromptEditAction::HitCondition,
8170 window,
8171 cx,
8172 );
8173 })
8174 .log_err();
8175 })
8176 })
8177 }
8178
8179 fn render_breakpoint(
8180 &self,
8181 position: Anchor,
8182 row: DisplayRow,
8183 breakpoint: &Breakpoint,
8184 state: Option<BreakpointSessionState>,
8185 cx: &mut Context<Self>,
8186 ) -> IconButton {
8187 let is_rejected = state.is_some_and(|s| !s.verified);
8188 // Is it a breakpoint that shows up when hovering over gutter?
8189 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8190 (false, false),
8191 |PhantomBreakpointIndicator {
8192 is_active,
8193 display_row,
8194 collides_with_existing_breakpoint,
8195 }| {
8196 (
8197 is_active && display_row == row,
8198 collides_with_existing_breakpoint,
8199 )
8200 },
8201 );
8202
8203 let (color, icon) = {
8204 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8205 (false, false) => ui::IconName::DebugBreakpoint,
8206 (true, false) => ui::IconName::DebugLogBreakpoint,
8207 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8208 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8209 };
8210
8211 let color = if is_phantom {
8212 Color::Hint
8213 } else if is_rejected {
8214 Color::Disabled
8215 } else {
8216 Color::Debugger
8217 };
8218
8219 (color, icon)
8220 };
8221
8222 let breakpoint = Arc::from(breakpoint.clone());
8223
8224 let alt_as_text = gpui::Keystroke {
8225 modifiers: Modifiers::secondary_key(),
8226 ..Default::default()
8227 };
8228 let primary_action_text = if breakpoint.is_disabled() {
8229 "Enable breakpoint"
8230 } else if is_phantom && !collides_with_existing {
8231 "Set breakpoint"
8232 } else {
8233 "Unset breakpoint"
8234 };
8235 let focus_handle = self.focus_handle.clone();
8236
8237 let meta = if is_rejected {
8238 SharedString::from("No executable code is associated with this line.")
8239 } else if collides_with_existing && !breakpoint.is_disabled() {
8240 SharedString::from(format!(
8241 "{alt_as_text}-click to disable,\nright-click for more options."
8242 ))
8243 } else {
8244 SharedString::from("Right-click for more options.")
8245 };
8246 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8247 .icon_size(IconSize::XSmall)
8248 .size(ui::ButtonSize::None)
8249 .when(is_rejected, |this| {
8250 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8251 })
8252 .icon_color(color)
8253 .style(ButtonStyle::Transparent)
8254 .on_click(cx.listener({
8255 move |editor, event: &ClickEvent, window, cx| {
8256 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8257 BreakpointEditAction::InvertState
8258 } else {
8259 BreakpointEditAction::Toggle
8260 };
8261
8262 window.focus(&editor.focus_handle(cx));
8263 editor.edit_breakpoint_at_anchor(
8264 position,
8265 breakpoint.as_ref().clone(),
8266 edit_action,
8267 cx,
8268 );
8269 }
8270 }))
8271 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8272 editor.set_breakpoint_context_menu(
8273 row,
8274 Some(position),
8275 event.position(),
8276 window,
8277 cx,
8278 );
8279 }))
8280 .tooltip(move |_window, cx| {
8281 Tooltip::with_meta_in(
8282 primary_action_text,
8283 Some(&ToggleBreakpoint),
8284 meta.clone(),
8285 &focus_handle,
8286 cx,
8287 )
8288 })
8289 }
8290
8291 fn build_tasks_context(
8292 project: &Entity<Project>,
8293 buffer: &Entity<Buffer>,
8294 buffer_row: u32,
8295 tasks: &Arc<RunnableTasks>,
8296 cx: &mut Context<Self>,
8297 ) -> Task<Option<task::TaskContext>> {
8298 let position = Point::new(buffer_row, tasks.column);
8299 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8300 let location = Location {
8301 buffer: buffer.clone(),
8302 range: range_start..range_start,
8303 };
8304 // Fill in the environmental variables from the tree-sitter captures
8305 let mut captured_task_variables = TaskVariables::default();
8306 for (capture_name, value) in tasks.extra_variables.clone() {
8307 captured_task_variables.insert(
8308 task::VariableName::Custom(capture_name.into()),
8309 value.clone(),
8310 );
8311 }
8312 project.update(cx, |project, cx| {
8313 project.task_store().update(cx, |task_store, cx| {
8314 task_store.task_context_for_location(captured_task_variables, location, cx)
8315 })
8316 })
8317 }
8318
8319 pub fn spawn_nearest_task(
8320 &mut self,
8321 action: &SpawnNearestTask,
8322 window: &mut Window,
8323 cx: &mut Context<Self>,
8324 ) {
8325 let Some((workspace, _)) = self.workspace.clone() else {
8326 return;
8327 };
8328 let Some(project) = self.project.clone() else {
8329 return;
8330 };
8331
8332 // Try to find a closest, enclosing node using tree-sitter that has a task
8333 let Some((buffer, buffer_row, tasks)) = self
8334 .find_enclosing_node_task(cx)
8335 // Or find the task that's closest in row-distance.
8336 .or_else(|| self.find_closest_task(cx))
8337 else {
8338 return;
8339 };
8340
8341 let reveal_strategy = action.reveal;
8342 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8343 cx.spawn_in(window, async move |_, cx| {
8344 let context = task_context.await?;
8345 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8346
8347 let resolved = &mut resolved_task.resolved;
8348 resolved.reveal = reveal_strategy;
8349
8350 workspace
8351 .update_in(cx, |workspace, window, cx| {
8352 workspace.schedule_resolved_task(
8353 task_source_kind,
8354 resolved_task,
8355 false,
8356 window,
8357 cx,
8358 );
8359 })
8360 .ok()
8361 })
8362 .detach();
8363 }
8364
8365 fn find_closest_task(
8366 &mut self,
8367 cx: &mut Context<Self>,
8368 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8369 let cursor_row = self
8370 .selections
8371 .newest_adjusted(&self.display_snapshot(cx))
8372 .head()
8373 .row;
8374
8375 let ((buffer_id, row), tasks) = self
8376 .tasks
8377 .iter()
8378 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8379
8380 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8381 let tasks = Arc::new(tasks.to_owned());
8382 Some((buffer, *row, tasks))
8383 }
8384
8385 fn find_enclosing_node_task(
8386 &mut self,
8387 cx: &mut Context<Self>,
8388 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8389 let snapshot = self.buffer.read(cx).snapshot(cx);
8390 let offset = self
8391 .selections
8392 .newest::<usize>(&self.display_snapshot(cx))
8393 .head();
8394 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8395 let buffer_id = excerpt.buffer().remote_id();
8396
8397 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8398 let mut cursor = layer.node().walk();
8399
8400 while cursor.goto_first_child_for_byte(offset).is_some() {
8401 if cursor.node().end_byte() == offset {
8402 cursor.goto_next_sibling();
8403 }
8404 }
8405
8406 // Ascend to the smallest ancestor that contains the range and has a task.
8407 loop {
8408 let node = cursor.node();
8409 let node_range = node.byte_range();
8410 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8411
8412 // Check if this node contains our offset
8413 if node_range.start <= offset && node_range.end >= offset {
8414 // If it contains offset, check for task
8415 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8416 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8417 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8418 }
8419 }
8420
8421 if !cursor.goto_parent() {
8422 break;
8423 }
8424 }
8425 None
8426 }
8427
8428 fn render_run_indicator(
8429 &self,
8430 _style: &EditorStyle,
8431 is_active: bool,
8432 row: DisplayRow,
8433 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8434 cx: &mut Context<Self>,
8435 ) -> IconButton {
8436 let color = Color::Muted;
8437 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8438
8439 IconButton::new(
8440 ("run_indicator", row.0 as usize),
8441 ui::IconName::PlayOutlined,
8442 )
8443 .shape(ui::IconButtonShape::Square)
8444 .icon_size(IconSize::XSmall)
8445 .icon_color(color)
8446 .toggle_state(is_active)
8447 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8448 let quick_launch = match e {
8449 ClickEvent::Keyboard(_) => true,
8450 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8451 };
8452
8453 window.focus(&editor.focus_handle(cx));
8454 editor.toggle_code_actions(
8455 &ToggleCodeActions {
8456 deployed_from: Some(CodeActionSource::RunMenu(row)),
8457 quick_launch,
8458 },
8459 window,
8460 cx,
8461 );
8462 }))
8463 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8464 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8465 }))
8466 }
8467
8468 pub fn context_menu_visible(&self) -> bool {
8469 !self.edit_prediction_preview_is_active()
8470 && self
8471 .context_menu
8472 .borrow()
8473 .as_ref()
8474 .is_some_and(|menu| menu.visible())
8475 }
8476
8477 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8478 self.context_menu
8479 .borrow()
8480 .as_ref()
8481 .map(|menu| menu.origin())
8482 }
8483
8484 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8485 self.context_menu_options = Some(options);
8486 }
8487
8488 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8489 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8490
8491 fn render_edit_prediction_popover(
8492 &mut self,
8493 text_bounds: &Bounds<Pixels>,
8494 content_origin: gpui::Point<Pixels>,
8495 right_margin: Pixels,
8496 editor_snapshot: &EditorSnapshot,
8497 visible_row_range: Range<DisplayRow>,
8498 scroll_top: ScrollOffset,
8499 scroll_bottom: ScrollOffset,
8500 line_layouts: &[LineWithInvisibles],
8501 line_height: Pixels,
8502 scroll_position: gpui::Point<ScrollOffset>,
8503 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8504 newest_selection_head: Option<DisplayPoint>,
8505 editor_width: Pixels,
8506 style: &EditorStyle,
8507 window: &mut Window,
8508 cx: &mut App,
8509 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8510 if self.mode().is_minimap() {
8511 return None;
8512 }
8513 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8514
8515 if self.edit_prediction_visible_in_cursor_popover(true) {
8516 return None;
8517 }
8518
8519 match &active_edit_prediction.completion {
8520 EditPrediction::MoveWithin { target, .. } => {
8521 let target_display_point = target.to_display_point(editor_snapshot);
8522
8523 if self.edit_prediction_requires_modifier() {
8524 if !self.edit_prediction_preview_is_active() {
8525 return None;
8526 }
8527
8528 self.render_edit_prediction_modifier_jump_popover(
8529 text_bounds,
8530 content_origin,
8531 visible_row_range,
8532 line_layouts,
8533 line_height,
8534 scroll_pixel_position,
8535 newest_selection_head,
8536 target_display_point,
8537 window,
8538 cx,
8539 )
8540 } else {
8541 self.render_edit_prediction_eager_jump_popover(
8542 text_bounds,
8543 content_origin,
8544 editor_snapshot,
8545 visible_row_range,
8546 scroll_top,
8547 scroll_bottom,
8548 line_height,
8549 scroll_pixel_position,
8550 target_display_point,
8551 editor_width,
8552 window,
8553 cx,
8554 )
8555 }
8556 }
8557 EditPrediction::Edit {
8558 display_mode: EditDisplayMode::Inline,
8559 ..
8560 } => None,
8561 EditPrediction::Edit {
8562 display_mode: EditDisplayMode::TabAccept,
8563 edits,
8564 ..
8565 } => {
8566 let range = &edits.first()?.0;
8567 let target_display_point = range.end.to_display_point(editor_snapshot);
8568
8569 self.render_edit_prediction_end_of_line_popover(
8570 "Accept",
8571 editor_snapshot,
8572 visible_row_range,
8573 target_display_point,
8574 line_height,
8575 scroll_pixel_position,
8576 content_origin,
8577 editor_width,
8578 window,
8579 cx,
8580 )
8581 }
8582 EditPrediction::Edit {
8583 edits,
8584 edit_preview,
8585 display_mode: EditDisplayMode::DiffPopover,
8586 snapshot,
8587 } => self.render_edit_prediction_diff_popover(
8588 text_bounds,
8589 content_origin,
8590 right_margin,
8591 editor_snapshot,
8592 visible_row_range,
8593 line_layouts,
8594 line_height,
8595 scroll_position,
8596 scroll_pixel_position,
8597 newest_selection_head,
8598 editor_width,
8599 style,
8600 edits,
8601 edit_preview,
8602 snapshot,
8603 window,
8604 cx,
8605 ),
8606 EditPrediction::MoveOutside { snapshot, .. } => {
8607 let file_name = snapshot
8608 .file()
8609 .map(|file| file.file_name(cx))
8610 .unwrap_or("untitled");
8611 let mut element = self
8612 .render_edit_prediction_line_popover(
8613 format!("Jump to {file_name}"),
8614 Some(IconName::ZedPredict),
8615 window,
8616 cx,
8617 )
8618 .into_any();
8619
8620 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8621 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8622 let origin_y = text_bounds.size.height - size.height - px(30.);
8623 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8624 element.prepaint_at(origin, window, cx);
8625
8626 Some((element, origin))
8627 }
8628 }
8629 }
8630
8631 fn render_edit_prediction_modifier_jump_popover(
8632 &mut self,
8633 text_bounds: &Bounds<Pixels>,
8634 content_origin: gpui::Point<Pixels>,
8635 visible_row_range: Range<DisplayRow>,
8636 line_layouts: &[LineWithInvisibles],
8637 line_height: Pixels,
8638 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8639 newest_selection_head: Option<DisplayPoint>,
8640 target_display_point: DisplayPoint,
8641 window: &mut Window,
8642 cx: &mut App,
8643 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8644 let scrolled_content_origin =
8645 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8646
8647 const SCROLL_PADDING_Y: Pixels = px(12.);
8648
8649 if target_display_point.row() < visible_row_range.start {
8650 return self.render_edit_prediction_scroll_popover(
8651 |_| SCROLL_PADDING_Y,
8652 IconName::ArrowUp,
8653 visible_row_range,
8654 line_layouts,
8655 newest_selection_head,
8656 scrolled_content_origin,
8657 window,
8658 cx,
8659 );
8660 } else if target_display_point.row() >= visible_row_range.end {
8661 return self.render_edit_prediction_scroll_popover(
8662 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8663 IconName::ArrowDown,
8664 visible_row_range,
8665 line_layouts,
8666 newest_selection_head,
8667 scrolled_content_origin,
8668 window,
8669 cx,
8670 );
8671 }
8672
8673 const POLE_WIDTH: Pixels = px(2.);
8674
8675 let line_layout =
8676 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8677 let target_column = target_display_point.column() as usize;
8678
8679 let target_x = line_layout.x_for_index(target_column);
8680 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8681 - scroll_pixel_position.y;
8682
8683 let flag_on_right = target_x < text_bounds.size.width / 2.;
8684
8685 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8686 border_color.l += 0.001;
8687
8688 let mut element = v_flex()
8689 .items_end()
8690 .when(flag_on_right, |el| el.items_start())
8691 .child(if flag_on_right {
8692 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8693 .rounded_bl(px(0.))
8694 .rounded_tl(px(0.))
8695 .border_l_2()
8696 .border_color(border_color)
8697 } else {
8698 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8699 .rounded_br(px(0.))
8700 .rounded_tr(px(0.))
8701 .border_r_2()
8702 .border_color(border_color)
8703 })
8704 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8705 .into_any();
8706
8707 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8708
8709 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8710 - point(
8711 if flag_on_right {
8712 POLE_WIDTH
8713 } else {
8714 size.width - POLE_WIDTH
8715 },
8716 size.height - line_height,
8717 );
8718
8719 origin.x = origin.x.max(content_origin.x);
8720
8721 element.prepaint_at(origin, window, cx);
8722
8723 Some((element, origin))
8724 }
8725
8726 fn render_edit_prediction_scroll_popover(
8727 &mut self,
8728 to_y: impl Fn(Size<Pixels>) -> Pixels,
8729 scroll_icon: IconName,
8730 visible_row_range: Range<DisplayRow>,
8731 line_layouts: &[LineWithInvisibles],
8732 newest_selection_head: Option<DisplayPoint>,
8733 scrolled_content_origin: gpui::Point<Pixels>,
8734 window: &mut Window,
8735 cx: &mut App,
8736 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8737 let mut element = self
8738 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8739 .into_any();
8740
8741 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8742
8743 let cursor = newest_selection_head?;
8744 let cursor_row_layout =
8745 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8746 let cursor_column = cursor.column() as usize;
8747
8748 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8749
8750 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8751
8752 element.prepaint_at(origin, window, cx);
8753 Some((element, origin))
8754 }
8755
8756 fn render_edit_prediction_eager_jump_popover(
8757 &mut self,
8758 text_bounds: &Bounds<Pixels>,
8759 content_origin: gpui::Point<Pixels>,
8760 editor_snapshot: &EditorSnapshot,
8761 visible_row_range: Range<DisplayRow>,
8762 scroll_top: ScrollOffset,
8763 scroll_bottom: ScrollOffset,
8764 line_height: Pixels,
8765 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8766 target_display_point: DisplayPoint,
8767 editor_width: Pixels,
8768 window: &mut Window,
8769 cx: &mut App,
8770 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8771 if target_display_point.row().as_f64() < scroll_top {
8772 let mut element = self
8773 .render_edit_prediction_line_popover(
8774 "Jump to Edit",
8775 Some(IconName::ArrowUp),
8776 window,
8777 cx,
8778 )
8779 .into_any();
8780
8781 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8782 let offset = point(
8783 (text_bounds.size.width - size.width) / 2.,
8784 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8785 );
8786
8787 let origin = text_bounds.origin + offset;
8788 element.prepaint_at(origin, window, cx);
8789 Some((element, origin))
8790 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8791 let mut element = self
8792 .render_edit_prediction_line_popover(
8793 "Jump to Edit",
8794 Some(IconName::ArrowDown),
8795 window,
8796 cx,
8797 )
8798 .into_any();
8799
8800 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8801 let offset = point(
8802 (text_bounds.size.width - size.width) / 2.,
8803 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8804 );
8805
8806 let origin = text_bounds.origin + offset;
8807 element.prepaint_at(origin, window, cx);
8808 Some((element, origin))
8809 } else {
8810 self.render_edit_prediction_end_of_line_popover(
8811 "Jump to Edit",
8812 editor_snapshot,
8813 visible_row_range,
8814 target_display_point,
8815 line_height,
8816 scroll_pixel_position,
8817 content_origin,
8818 editor_width,
8819 window,
8820 cx,
8821 )
8822 }
8823 }
8824
8825 fn render_edit_prediction_end_of_line_popover(
8826 self: &mut Editor,
8827 label: &'static str,
8828 editor_snapshot: &EditorSnapshot,
8829 visible_row_range: Range<DisplayRow>,
8830 target_display_point: DisplayPoint,
8831 line_height: Pixels,
8832 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8833 content_origin: gpui::Point<Pixels>,
8834 editor_width: Pixels,
8835 window: &mut Window,
8836 cx: &mut App,
8837 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8838 let target_line_end = DisplayPoint::new(
8839 target_display_point.row(),
8840 editor_snapshot.line_len(target_display_point.row()),
8841 );
8842
8843 let mut element = self
8844 .render_edit_prediction_line_popover(label, None, window, cx)
8845 .into_any();
8846
8847 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8848
8849 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8850
8851 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8852 let mut origin = start_point
8853 + line_origin
8854 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8855 origin.x = origin.x.max(content_origin.x);
8856
8857 let max_x = content_origin.x + editor_width - size.width;
8858
8859 if origin.x > max_x {
8860 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8861
8862 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8863 origin.y += offset;
8864 IconName::ArrowUp
8865 } else {
8866 origin.y -= offset;
8867 IconName::ArrowDown
8868 };
8869
8870 element = self
8871 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
8872 .into_any();
8873
8874 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8875
8876 origin.x = content_origin.x + editor_width - size.width - px(2.);
8877 }
8878
8879 element.prepaint_at(origin, window, cx);
8880 Some((element, origin))
8881 }
8882
8883 fn render_edit_prediction_diff_popover(
8884 self: &Editor,
8885 text_bounds: &Bounds<Pixels>,
8886 content_origin: gpui::Point<Pixels>,
8887 right_margin: Pixels,
8888 editor_snapshot: &EditorSnapshot,
8889 visible_row_range: Range<DisplayRow>,
8890 line_layouts: &[LineWithInvisibles],
8891 line_height: Pixels,
8892 scroll_position: gpui::Point<ScrollOffset>,
8893 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8894 newest_selection_head: Option<DisplayPoint>,
8895 editor_width: Pixels,
8896 style: &EditorStyle,
8897 edits: &Vec<(Range<Anchor>, String)>,
8898 edit_preview: &Option<language::EditPreview>,
8899 snapshot: &language::BufferSnapshot,
8900 window: &mut Window,
8901 cx: &mut App,
8902 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8903 let edit_start = edits
8904 .first()
8905 .unwrap()
8906 .0
8907 .start
8908 .to_display_point(editor_snapshot);
8909 let edit_end = edits
8910 .last()
8911 .unwrap()
8912 .0
8913 .end
8914 .to_display_point(editor_snapshot);
8915
8916 let is_visible = visible_row_range.contains(&edit_start.row())
8917 || visible_row_range.contains(&edit_end.row());
8918 if !is_visible {
8919 return None;
8920 }
8921
8922 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8923 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8924 } else {
8925 // Fallback for providers without edit_preview
8926 crate::edit_prediction_fallback_text(edits, cx)
8927 };
8928
8929 let styled_text = highlighted_edits.to_styled_text(&style.text);
8930 let line_count = highlighted_edits.text.lines().count();
8931
8932 const BORDER_WIDTH: Pixels = px(1.);
8933
8934 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8935 let has_keybind = keybind.is_some();
8936
8937 let mut element = h_flex()
8938 .items_start()
8939 .child(
8940 h_flex()
8941 .bg(cx.theme().colors().editor_background)
8942 .border(BORDER_WIDTH)
8943 .shadow_xs()
8944 .border_color(cx.theme().colors().border)
8945 .rounded_l_lg()
8946 .when(line_count > 1, |el| el.rounded_br_lg())
8947 .pr_1()
8948 .child(styled_text),
8949 )
8950 .child(
8951 h_flex()
8952 .h(line_height + BORDER_WIDTH * 2.)
8953 .px_1p5()
8954 .gap_1()
8955 // Workaround: For some reason, there's a gap if we don't do this
8956 .ml(-BORDER_WIDTH)
8957 .shadow(vec![gpui::BoxShadow {
8958 color: gpui::black().opacity(0.05),
8959 offset: point(px(1.), px(1.)),
8960 blur_radius: px(2.),
8961 spread_radius: px(0.),
8962 }])
8963 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8964 .border(BORDER_WIDTH)
8965 .border_color(cx.theme().colors().border)
8966 .rounded_r_lg()
8967 .id("edit_prediction_diff_popover_keybind")
8968 .when(!has_keybind, |el| {
8969 let status_colors = cx.theme().status();
8970
8971 el.bg(status_colors.error_background)
8972 .border_color(status_colors.error.opacity(0.6))
8973 .child(Icon::new(IconName::Info).color(Color::Error))
8974 .cursor_default()
8975 .hoverable_tooltip(move |_window, cx| {
8976 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8977 })
8978 })
8979 .children(keybind),
8980 )
8981 .into_any();
8982
8983 let longest_row =
8984 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8985 let longest_line_width = if visible_row_range.contains(&longest_row) {
8986 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8987 } else {
8988 layout_line(
8989 longest_row,
8990 editor_snapshot,
8991 style,
8992 editor_width,
8993 |_| false,
8994 window,
8995 cx,
8996 )
8997 .width
8998 };
8999
9000 let viewport_bounds =
9001 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9002 right: -right_margin,
9003 ..Default::default()
9004 });
9005
9006 let x_after_longest = Pixels::from(
9007 ScrollPixelOffset::from(
9008 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9009 ) - scroll_pixel_position.x,
9010 );
9011
9012 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9013
9014 // Fully visible if it can be displayed within the window (allow overlapping other
9015 // panes). However, this is only allowed if the popover starts within text_bounds.
9016 let can_position_to_the_right = x_after_longest < text_bounds.right()
9017 && x_after_longest + element_bounds.width < viewport_bounds.right();
9018
9019 let mut origin = if can_position_to_the_right {
9020 point(
9021 x_after_longest,
9022 text_bounds.origin.y
9023 + Pixels::from(
9024 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9025 - scroll_pixel_position.y,
9026 ),
9027 )
9028 } else {
9029 let cursor_row = newest_selection_head.map(|head| head.row());
9030 let above_edit = edit_start
9031 .row()
9032 .0
9033 .checked_sub(line_count as u32)
9034 .map(DisplayRow);
9035 let below_edit = Some(edit_end.row() + 1);
9036 let above_cursor =
9037 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9038 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9039
9040 // Place the edit popover adjacent to the edit if there is a location
9041 // available that is onscreen and does not obscure the cursor. Otherwise,
9042 // place it adjacent to the cursor.
9043 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9044 .into_iter()
9045 .flatten()
9046 .find(|&start_row| {
9047 let end_row = start_row + line_count as u32;
9048 visible_row_range.contains(&start_row)
9049 && visible_row_range.contains(&end_row)
9050 && cursor_row
9051 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9052 })?;
9053
9054 content_origin
9055 + point(
9056 Pixels::from(-scroll_pixel_position.x),
9057 Pixels::from(
9058 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9059 ),
9060 )
9061 };
9062
9063 origin.x -= BORDER_WIDTH;
9064
9065 window.defer_draw(element, origin, 1);
9066
9067 // Do not return an element, since it will already be drawn due to defer_draw.
9068 None
9069 }
9070
9071 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9072 px(30.)
9073 }
9074
9075 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9076 if self.read_only(cx) {
9077 cx.theme().players().read_only()
9078 } else {
9079 self.style.as_ref().unwrap().local_player
9080 }
9081 }
9082
9083 fn render_edit_prediction_accept_keybind(
9084 &self,
9085 window: &mut Window,
9086 cx: &mut App,
9087 ) -> Option<AnyElement> {
9088 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9089 let accept_keystroke = accept_binding.keystroke()?;
9090
9091 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9092
9093 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9094 Color::Accent
9095 } else {
9096 Color::Muted
9097 };
9098
9099 h_flex()
9100 .px_0p5()
9101 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9102 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9103 .text_size(TextSize::XSmall.rems(cx))
9104 .child(h_flex().children(ui::render_modifiers(
9105 accept_keystroke.modifiers(),
9106 PlatformStyle::platform(),
9107 Some(modifiers_color),
9108 Some(IconSize::XSmall.rems().into()),
9109 true,
9110 )))
9111 .when(is_platform_style_mac, |parent| {
9112 parent.child(accept_keystroke.key().to_string())
9113 })
9114 .when(!is_platform_style_mac, |parent| {
9115 parent.child(
9116 Key::new(
9117 util::capitalize(accept_keystroke.key()),
9118 Some(Color::Default),
9119 )
9120 .size(Some(IconSize::XSmall.rems().into())),
9121 )
9122 })
9123 .into_any()
9124 .into()
9125 }
9126
9127 fn render_edit_prediction_line_popover(
9128 &self,
9129 label: impl Into<SharedString>,
9130 icon: Option<IconName>,
9131 window: &mut Window,
9132 cx: &mut App,
9133 ) -> Stateful<Div> {
9134 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9135
9136 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9137 let has_keybind = keybind.is_some();
9138
9139 h_flex()
9140 .id("ep-line-popover")
9141 .py_0p5()
9142 .pl_1()
9143 .pr(padding_right)
9144 .gap_1()
9145 .rounded_md()
9146 .border_1()
9147 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9148 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9149 .shadow_xs()
9150 .when(!has_keybind, |el| {
9151 let status_colors = cx.theme().status();
9152
9153 el.bg(status_colors.error_background)
9154 .border_color(status_colors.error.opacity(0.6))
9155 .pl_2()
9156 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9157 .cursor_default()
9158 .hoverable_tooltip(move |_window, cx| {
9159 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9160 })
9161 })
9162 .children(keybind)
9163 .child(
9164 Label::new(label)
9165 .size(LabelSize::Small)
9166 .when(!has_keybind, |el| {
9167 el.color(cx.theme().status().error.into()).strikethrough()
9168 }),
9169 )
9170 .when(!has_keybind, |el| {
9171 el.child(
9172 h_flex().ml_1().child(
9173 Icon::new(IconName::Info)
9174 .size(IconSize::Small)
9175 .color(cx.theme().status().error.into()),
9176 ),
9177 )
9178 })
9179 .when_some(icon, |element, icon| {
9180 element.child(
9181 div()
9182 .mt(px(1.5))
9183 .child(Icon::new(icon).size(IconSize::Small)),
9184 )
9185 })
9186 }
9187
9188 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9189 let accent_color = cx.theme().colors().text_accent;
9190 let editor_bg_color = cx.theme().colors().editor_background;
9191 editor_bg_color.blend(accent_color.opacity(0.1))
9192 }
9193
9194 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9195 let accent_color = cx.theme().colors().text_accent;
9196 let editor_bg_color = cx.theme().colors().editor_background;
9197 editor_bg_color.blend(accent_color.opacity(0.6))
9198 }
9199 fn get_prediction_provider_icon_name(
9200 provider: &Option<RegisteredEditPredictionProvider>,
9201 ) -> IconName {
9202 match provider {
9203 Some(provider) => match provider.provider.name() {
9204 "copilot" => IconName::Copilot,
9205 "supermaven" => IconName::Supermaven,
9206 _ => IconName::ZedPredict,
9207 },
9208 None => IconName::ZedPredict,
9209 }
9210 }
9211
9212 fn render_edit_prediction_cursor_popover(
9213 &self,
9214 min_width: Pixels,
9215 max_width: Pixels,
9216 cursor_point: Point,
9217 style: &EditorStyle,
9218 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9219 _window: &Window,
9220 cx: &mut Context<Editor>,
9221 ) -> Option<AnyElement> {
9222 let provider = self.edit_prediction_provider.as_ref()?;
9223 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9224
9225 let is_refreshing = provider.provider.is_refreshing(cx);
9226
9227 fn pending_completion_container(icon: IconName) -> Div {
9228 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9229 }
9230
9231 let completion = match &self.active_edit_prediction {
9232 Some(prediction) => {
9233 if !self.has_visible_completions_menu() {
9234 const RADIUS: Pixels = px(6.);
9235 const BORDER_WIDTH: Pixels = px(1.);
9236
9237 return Some(
9238 h_flex()
9239 .elevation_2(cx)
9240 .border(BORDER_WIDTH)
9241 .border_color(cx.theme().colors().border)
9242 .when(accept_keystroke.is_none(), |el| {
9243 el.border_color(cx.theme().status().error)
9244 })
9245 .rounded(RADIUS)
9246 .rounded_tl(px(0.))
9247 .overflow_hidden()
9248 .child(div().px_1p5().child(match &prediction.completion {
9249 EditPrediction::MoveWithin { target, snapshot } => {
9250 use text::ToPoint as _;
9251 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9252 {
9253 Icon::new(IconName::ZedPredictDown)
9254 } else {
9255 Icon::new(IconName::ZedPredictUp)
9256 }
9257 }
9258 EditPrediction::MoveOutside { .. } => {
9259 // TODO [zeta2] custom icon for external jump?
9260 Icon::new(provider_icon)
9261 }
9262 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9263 }))
9264 .child(
9265 h_flex()
9266 .gap_1()
9267 .py_1()
9268 .px_2()
9269 .rounded_r(RADIUS - BORDER_WIDTH)
9270 .border_l_1()
9271 .border_color(cx.theme().colors().border)
9272 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9273 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9274 el.child(
9275 Label::new("Hold")
9276 .size(LabelSize::Small)
9277 .when(accept_keystroke.is_none(), |el| {
9278 el.strikethrough()
9279 })
9280 .line_height_style(LineHeightStyle::UiLabel),
9281 )
9282 })
9283 .id("edit_prediction_cursor_popover_keybind")
9284 .when(accept_keystroke.is_none(), |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 .child(Icon::new(IconName::Info).color(Color::Error))
9290 .cursor_default()
9291 .hoverable_tooltip(move |_window, cx| {
9292 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9293 .into()
9294 })
9295 })
9296 .when_some(
9297 accept_keystroke.as_ref(),
9298 |el, accept_keystroke| {
9299 el.child(h_flex().children(ui::render_modifiers(
9300 accept_keystroke.modifiers(),
9301 PlatformStyle::platform(),
9302 Some(Color::Default),
9303 Some(IconSize::XSmall.rems().into()),
9304 false,
9305 )))
9306 },
9307 ),
9308 )
9309 .into_any(),
9310 );
9311 }
9312
9313 self.render_edit_prediction_cursor_popover_preview(
9314 prediction,
9315 cursor_point,
9316 style,
9317 cx,
9318 )?
9319 }
9320
9321 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9322 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9323 stale_completion,
9324 cursor_point,
9325 style,
9326 cx,
9327 )?,
9328
9329 None => pending_completion_container(provider_icon)
9330 .child(Label::new("...").size(LabelSize::Small)),
9331 },
9332
9333 None => pending_completion_container(provider_icon)
9334 .child(Label::new("...").size(LabelSize::Small)),
9335 };
9336
9337 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9338 completion
9339 .with_animation(
9340 "loading-completion",
9341 Animation::new(Duration::from_secs(2))
9342 .repeat()
9343 .with_easing(pulsating_between(0.4, 0.8)),
9344 |label, delta| label.opacity(delta),
9345 )
9346 .into_any_element()
9347 } else {
9348 completion.into_any_element()
9349 };
9350
9351 let has_completion = self.active_edit_prediction.is_some();
9352
9353 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9354 Some(
9355 h_flex()
9356 .min_w(min_width)
9357 .max_w(max_width)
9358 .flex_1()
9359 .elevation_2(cx)
9360 .border_color(cx.theme().colors().border)
9361 .child(
9362 div()
9363 .flex_1()
9364 .py_1()
9365 .px_2()
9366 .overflow_hidden()
9367 .child(completion),
9368 )
9369 .when_some(accept_keystroke, |el, accept_keystroke| {
9370 if !accept_keystroke.modifiers().modified() {
9371 return el;
9372 }
9373
9374 el.child(
9375 h_flex()
9376 .h_full()
9377 .border_l_1()
9378 .rounded_r_lg()
9379 .border_color(cx.theme().colors().border)
9380 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9381 .gap_1()
9382 .py_1()
9383 .px_2()
9384 .child(
9385 h_flex()
9386 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9387 .when(is_platform_style_mac, |parent| parent.gap_1())
9388 .child(h_flex().children(ui::render_modifiers(
9389 accept_keystroke.modifiers(),
9390 PlatformStyle::platform(),
9391 Some(if !has_completion {
9392 Color::Muted
9393 } else {
9394 Color::Default
9395 }),
9396 None,
9397 false,
9398 ))),
9399 )
9400 .child(Label::new("Preview").into_any_element())
9401 .opacity(if has_completion { 1.0 } else { 0.4 }),
9402 )
9403 })
9404 .into_any(),
9405 )
9406 }
9407
9408 fn render_edit_prediction_cursor_popover_preview(
9409 &self,
9410 completion: &EditPredictionState,
9411 cursor_point: Point,
9412 style: &EditorStyle,
9413 cx: &mut Context<Editor>,
9414 ) -> Option<Div> {
9415 use text::ToPoint as _;
9416
9417 fn render_relative_row_jump(
9418 prefix: impl Into<String>,
9419 current_row: u32,
9420 target_row: u32,
9421 ) -> Div {
9422 let (row_diff, arrow) = if target_row < current_row {
9423 (current_row - target_row, IconName::ArrowUp)
9424 } else {
9425 (target_row - current_row, IconName::ArrowDown)
9426 };
9427
9428 h_flex()
9429 .child(
9430 Label::new(format!("{}{}", prefix.into(), row_diff))
9431 .color(Color::Muted)
9432 .size(LabelSize::Small),
9433 )
9434 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9435 }
9436
9437 let supports_jump = self
9438 .edit_prediction_provider
9439 .as_ref()
9440 .map(|provider| provider.provider.supports_jump_to_edit())
9441 .unwrap_or(true);
9442
9443 match &completion.completion {
9444 EditPrediction::MoveWithin {
9445 target, snapshot, ..
9446 } => {
9447 if !supports_jump {
9448 return None;
9449 }
9450
9451 Some(
9452 h_flex()
9453 .px_2()
9454 .gap_2()
9455 .flex_1()
9456 .child(
9457 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9458 Icon::new(IconName::ZedPredictDown)
9459 } else {
9460 Icon::new(IconName::ZedPredictUp)
9461 },
9462 )
9463 .child(Label::new("Jump to Edit")),
9464 )
9465 }
9466 EditPrediction::MoveOutside { snapshot, .. } => {
9467 let file_name = snapshot
9468 .file()
9469 .map(|file| file.file_name(cx))
9470 .unwrap_or("untitled");
9471 Some(
9472 h_flex()
9473 .px_2()
9474 .gap_2()
9475 .flex_1()
9476 .child(Icon::new(IconName::ZedPredict))
9477 .child(Label::new(format!("Jump to {file_name}"))),
9478 )
9479 }
9480 EditPrediction::Edit {
9481 edits,
9482 edit_preview,
9483 snapshot,
9484 display_mode: _,
9485 } => {
9486 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9487
9488 let (highlighted_edits, has_more_lines) =
9489 if let Some(edit_preview) = edit_preview.as_ref() {
9490 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9491 .first_line_preview()
9492 } else {
9493 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9494 };
9495
9496 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9497 .with_default_highlights(&style.text, highlighted_edits.highlights);
9498
9499 let preview = h_flex()
9500 .gap_1()
9501 .min_w_16()
9502 .child(styled_text)
9503 .when(has_more_lines, |parent| parent.child("…"));
9504
9505 let left = if supports_jump && first_edit_row != cursor_point.row {
9506 render_relative_row_jump("", cursor_point.row, first_edit_row)
9507 .into_any_element()
9508 } else {
9509 let icon_name =
9510 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9511 Icon::new(icon_name).into_any_element()
9512 };
9513
9514 Some(
9515 h_flex()
9516 .h_full()
9517 .flex_1()
9518 .gap_2()
9519 .pr_1()
9520 .overflow_x_hidden()
9521 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9522 .child(left)
9523 .child(preview),
9524 )
9525 }
9526 }
9527 }
9528
9529 pub fn render_context_menu(
9530 &self,
9531 style: &EditorStyle,
9532 max_height_in_lines: u32,
9533 window: &mut Window,
9534 cx: &mut Context<Editor>,
9535 ) -> Option<AnyElement> {
9536 let menu = self.context_menu.borrow();
9537 let menu = menu.as_ref()?;
9538 if !menu.visible() {
9539 return None;
9540 };
9541 Some(menu.render(style, max_height_in_lines, window, cx))
9542 }
9543
9544 fn render_context_menu_aside(
9545 &mut self,
9546 max_size: Size<Pixels>,
9547 window: &mut Window,
9548 cx: &mut Context<Editor>,
9549 ) -> Option<AnyElement> {
9550 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9551 if menu.visible() {
9552 menu.render_aside(max_size, window, cx)
9553 } else {
9554 None
9555 }
9556 })
9557 }
9558
9559 fn hide_context_menu(
9560 &mut self,
9561 window: &mut Window,
9562 cx: &mut Context<Self>,
9563 ) -> Option<CodeContextMenu> {
9564 cx.notify();
9565 self.completion_tasks.clear();
9566 let context_menu = self.context_menu.borrow_mut().take();
9567 self.stale_edit_prediction_in_menu.take();
9568 self.update_visible_edit_prediction(window, cx);
9569 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9570 && let Some(completion_provider) = &self.completion_provider
9571 {
9572 completion_provider.selection_changed(None, window, cx);
9573 }
9574 context_menu
9575 }
9576
9577 fn show_snippet_choices(
9578 &mut self,
9579 choices: &Vec<String>,
9580 selection: Range<Anchor>,
9581 cx: &mut Context<Self>,
9582 ) {
9583 let Some((_, buffer, _)) = self
9584 .buffer()
9585 .read(cx)
9586 .excerpt_containing(selection.start, cx)
9587 else {
9588 return;
9589 };
9590 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9591 else {
9592 return;
9593 };
9594 if buffer != end_buffer {
9595 log::error!("expected anchor range to have matching buffer IDs");
9596 return;
9597 }
9598
9599 let id = post_inc(&mut self.next_completion_id);
9600 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9601 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9602 CompletionsMenu::new_snippet_choices(
9603 id,
9604 true,
9605 choices,
9606 selection,
9607 buffer,
9608 snippet_sort_order,
9609 ),
9610 ));
9611 }
9612
9613 pub fn insert_snippet(
9614 &mut self,
9615 insertion_ranges: &[Range<usize>],
9616 snippet: Snippet,
9617 window: &mut Window,
9618 cx: &mut Context<Self>,
9619 ) -> Result<()> {
9620 struct Tabstop<T> {
9621 is_end_tabstop: bool,
9622 ranges: Vec<Range<T>>,
9623 choices: Option<Vec<String>>,
9624 }
9625
9626 let tabstops = self.buffer.update(cx, |buffer, cx| {
9627 let snippet_text: Arc<str> = snippet.text.clone().into();
9628 let edits = insertion_ranges
9629 .iter()
9630 .cloned()
9631 .map(|range| (range, snippet_text.clone()));
9632 let autoindent_mode = AutoindentMode::Block {
9633 original_indent_columns: Vec::new(),
9634 };
9635 buffer.edit(edits, Some(autoindent_mode), cx);
9636
9637 let snapshot = &*buffer.read(cx);
9638 let snippet = &snippet;
9639 snippet
9640 .tabstops
9641 .iter()
9642 .map(|tabstop| {
9643 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9644 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9645 });
9646 let mut tabstop_ranges = tabstop
9647 .ranges
9648 .iter()
9649 .flat_map(|tabstop_range| {
9650 let mut delta = 0_isize;
9651 insertion_ranges.iter().map(move |insertion_range| {
9652 let insertion_start = insertion_range.start as isize + delta;
9653 delta +=
9654 snippet.text.len() as isize - insertion_range.len() as isize;
9655
9656 let start = ((insertion_start + tabstop_range.start) as usize)
9657 .min(snapshot.len());
9658 let end = ((insertion_start + tabstop_range.end) as usize)
9659 .min(snapshot.len());
9660 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9661 })
9662 })
9663 .collect::<Vec<_>>();
9664 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9665
9666 Tabstop {
9667 is_end_tabstop,
9668 ranges: tabstop_ranges,
9669 choices: tabstop.choices.clone(),
9670 }
9671 })
9672 .collect::<Vec<_>>()
9673 });
9674 if let Some(tabstop) = tabstops.first() {
9675 self.change_selections(Default::default(), window, cx, |s| {
9676 // Reverse order so that the first range is the newest created selection.
9677 // Completions will use it and autoscroll will prioritize it.
9678 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9679 });
9680
9681 if let Some(choices) = &tabstop.choices
9682 && let Some(selection) = tabstop.ranges.first()
9683 {
9684 self.show_snippet_choices(choices, selection.clone(), cx)
9685 }
9686
9687 // If we're already at the last tabstop and it's at the end of the snippet,
9688 // we're done, we don't need to keep the state around.
9689 if !tabstop.is_end_tabstop {
9690 let choices = tabstops
9691 .iter()
9692 .map(|tabstop| tabstop.choices.clone())
9693 .collect();
9694
9695 let ranges = tabstops
9696 .into_iter()
9697 .map(|tabstop| tabstop.ranges)
9698 .collect::<Vec<_>>();
9699
9700 self.snippet_stack.push(SnippetState {
9701 active_index: 0,
9702 ranges,
9703 choices,
9704 });
9705 }
9706
9707 // Check whether the just-entered snippet ends with an auto-closable bracket.
9708 if self.autoclose_regions.is_empty() {
9709 let snapshot = self.buffer.read(cx).snapshot(cx);
9710 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9711 let selection_head = selection.head();
9712 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9713 continue;
9714 };
9715
9716 let mut bracket_pair = None;
9717 let max_lookup_length = scope
9718 .brackets()
9719 .map(|(pair, _)| {
9720 pair.start
9721 .as_str()
9722 .chars()
9723 .count()
9724 .max(pair.end.as_str().chars().count())
9725 })
9726 .max();
9727 if let Some(max_lookup_length) = max_lookup_length {
9728 let next_text = snapshot
9729 .chars_at(selection_head)
9730 .take(max_lookup_length)
9731 .collect::<String>();
9732 let prev_text = snapshot
9733 .reversed_chars_at(selection_head)
9734 .take(max_lookup_length)
9735 .collect::<String>();
9736
9737 for (pair, enabled) in scope.brackets() {
9738 if enabled
9739 && pair.close
9740 && prev_text.starts_with(pair.start.as_str())
9741 && next_text.starts_with(pair.end.as_str())
9742 {
9743 bracket_pair = Some(pair.clone());
9744 break;
9745 }
9746 }
9747 }
9748
9749 if let Some(pair) = bracket_pair {
9750 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9751 let autoclose_enabled =
9752 self.use_autoclose && snapshot_settings.use_autoclose;
9753 if autoclose_enabled {
9754 let start = snapshot.anchor_after(selection_head);
9755 let end = snapshot.anchor_after(selection_head);
9756 self.autoclose_regions.push(AutocloseRegion {
9757 selection_id: selection.id,
9758 range: start..end,
9759 pair,
9760 });
9761 }
9762 }
9763 }
9764 }
9765 }
9766 Ok(())
9767 }
9768
9769 pub fn move_to_next_snippet_tabstop(
9770 &mut self,
9771 window: &mut Window,
9772 cx: &mut Context<Self>,
9773 ) -> bool {
9774 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9775 }
9776
9777 pub fn move_to_prev_snippet_tabstop(
9778 &mut self,
9779 window: &mut Window,
9780 cx: &mut Context<Self>,
9781 ) -> bool {
9782 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9783 }
9784
9785 pub fn move_to_snippet_tabstop(
9786 &mut self,
9787 bias: Bias,
9788 window: &mut Window,
9789 cx: &mut Context<Self>,
9790 ) -> bool {
9791 if let Some(mut snippet) = self.snippet_stack.pop() {
9792 match bias {
9793 Bias::Left => {
9794 if snippet.active_index > 0 {
9795 snippet.active_index -= 1;
9796 } else {
9797 self.snippet_stack.push(snippet);
9798 return false;
9799 }
9800 }
9801 Bias::Right => {
9802 if snippet.active_index + 1 < snippet.ranges.len() {
9803 snippet.active_index += 1;
9804 } else {
9805 self.snippet_stack.push(snippet);
9806 return false;
9807 }
9808 }
9809 }
9810 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9811 self.change_selections(Default::default(), window, cx, |s| {
9812 // Reverse order so that the first range is the newest created selection.
9813 // Completions will use it and autoscroll will prioritize it.
9814 s.select_ranges(current_ranges.iter().rev().cloned())
9815 });
9816
9817 if let Some(choices) = &snippet.choices[snippet.active_index]
9818 && let Some(selection) = current_ranges.first()
9819 {
9820 self.show_snippet_choices(choices, selection.clone(), cx);
9821 }
9822
9823 // If snippet state is not at the last tabstop, push it back on the stack
9824 if snippet.active_index + 1 < snippet.ranges.len() {
9825 self.snippet_stack.push(snippet);
9826 }
9827 return true;
9828 }
9829 }
9830
9831 false
9832 }
9833
9834 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9835 self.transact(window, cx, |this, window, cx| {
9836 this.select_all(&SelectAll, window, cx);
9837 this.insert("", window, cx);
9838 });
9839 }
9840
9841 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9842 if self.read_only(cx) {
9843 return;
9844 }
9845 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9846 self.transact(window, cx, |this, window, cx| {
9847 this.select_autoclose_pair(window, cx);
9848
9849 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9850
9851 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9852 if !this.linked_edit_ranges.is_empty() {
9853 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
9854 let snapshot = this.buffer.read(cx).snapshot(cx);
9855
9856 for selection in selections.iter() {
9857 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9858 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9859 if selection_start.buffer_id != selection_end.buffer_id {
9860 continue;
9861 }
9862 if let Some(ranges) =
9863 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9864 {
9865 for (buffer, entries) in ranges {
9866 linked_ranges.entry(buffer).or_default().extend(entries);
9867 }
9868 }
9869 }
9870 }
9871
9872 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
9873 for selection in &mut selections {
9874 if selection.is_empty() {
9875 let old_head = selection.head();
9876 let mut new_head =
9877 movement::left(&display_map, old_head.to_display_point(&display_map))
9878 .to_point(&display_map);
9879 if let Some((buffer, line_buffer_range)) = display_map
9880 .buffer_snapshot()
9881 .buffer_line_for_row(MultiBufferRow(old_head.row))
9882 {
9883 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9884 let indent_len = match indent_size.kind {
9885 IndentKind::Space => {
9886 buffer.settings_at(line_buffer_range.start, cx).tab_size
9887 }
9888 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9889 };
9890 if old_head.column <= indent_size.len && old_head.column > 0 {
9891 let indent_len = indent_len.get();
9892 new_head = cmp::min(
9893 new_head,
9894 MultiBufferPoint::new(
9895 old_head.row,
9896 ((old_head.column - 1) / indent_len) * indent_len,
9897 ),
9898 );
9899 }
9900 }
9901
9902 selection.set_head(new_head, SelectionGoal::None);
9903 }
9904 }
9905
9906 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9907 this.insert("", window, cx);
9908 let empty_str: Arc<str> = Arc::from("");
9909 for (buffer, edits) in linked_ranges {
9910 let snapshot = buffer.read(cx).snapshot();
9911 use text::ToPoint as TP;
9912
9913 let edits = edits
9914 .into_iter()
9915 .map(|range| {
9916 let end_point = TP::to_point(&range.end, &snapshot);
9917 let mut start_point = TP::to_point(&range.start, &snapshot);
9918
9919 if end_point == start_point {
9920 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9921 .saturating_sub(1);
9922 start_point =
9923 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9924 };
9925
9926 (start_point..end_point, empty_str.clone())
9927 })
9928 .sorted_by_key(|(range, _)| range.start)
9929 .collect::<Vec<_>>();
9930 buffer.update(cx, |this, cx| {
9931 this.edit(edits, None, cx);
9932 })
9933 }
9934 this.refresh_edit_prediction(true, false, window, cx);
9935 refresh_linked_ranges(this, window, cx);
9936 });
9937 }
9938
9939 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9940 if self.read_only(cx) {
9941 return;
9942 }
9943 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9944 self.transact(window, cx, |this, window, cx| {
9945 this.change_selections(Default::default(), window, cx, |s| {
9946 s.move_with(|map, selection| {
9947 if selection.is_empty() {
9948 let cursor = movement::right(map, selection.head());
9949 selection.end = cursor;
9950 selection.reversed = true;
9951 selection.goal = SelectionGoal::None;
9952 }
9953 })
9954 });
9955 this.insert("", window, cx);
9956 this.refresh_edit_prediction(true, false, window, cx);
9957 });
9958 }
9959
9960 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9961 if self.mode.is_single_line() {
9962 cx.propagate();
9963 return;
9964 }
9965
9966 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9967 if self.move_to_prev_snippet_tabstop(window, cx) {
9968 return;
9969 }
9970 self.outdent(&Outdent, window, cx);
9971 }
9972
9973 pub fn next_snippet_tabstop(
9974 &mut self,
9975 _: &NextSnippetTabstop,
9976 window: &mut Window,
9977 cx: &mut Context<Self>,
9978 ) {
9979 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
9980 return;
9981 }
9982
9983 if self.move_to_next_snippet_tabstop(window, cx) {
9984 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9985 return;
9986 }
9987 }
9988
9989 pub fn previous_snippet_tabstop(
9990 &mut self,
9991 _: &PreviousSnippetTabstop,
9992 window: &mut Window,
9993 cx: &mut Context<Self>,
9994 ) {
9995 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
9996 return;
9997 }
9998
9999 if self.move_to_prev_snippet_tabstop(window, cx) {
10000 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10001 return;
10002 }
10003 }
10004
10005 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10006 if self.mode.is_single_line() {
10007 cx.propagate();
10008 return;
10009 }
10010
10011 if self.move_to_next_snippet_tabstop(window, cx) {
10012 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10013 return;
10014 }
10015 if self.read_only(cx) {
10016 return;
10017 }
10018 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10019 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10020 let buffer = self.buffer.read(cx);
10021 let snapshot = buffer.snapshot(cx);
10022 let rows_iter = selections.iter().map(|s| s.head().row);
10023 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10024
10025 let has_some_cursor_in_whitespace = selections
10026 .iter()
10027 .filter(|selection| selection.is_empty())
10028 .any(|selection| {
10029 let cursor = selection.head();
10030 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10031 cursor.column < current_indent.len
10032 });
10033
10034 let mut edits = Vec::new();
10035 let mut prev_edited_row = 0;
10036 let mut row_delta = 0;
10037 for selection in &mut selections {
10038 if selection.start.row != prev_edited_row {
10039 row_delta = 0;
10040 }
10041 prev_edited_row = selection.end.row;
10042
10043 // If the selection is non-empty, then increase the indentation of the selected lines.
10044 if !selection.is_empty() {
10045 row_delta =
10046 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10047 continue;
10048 }
10049
10050 let cursor = selection.head();
10051 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10052 if let Some(suggested_indent) =
10053 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10054 {
10055 // Don't do anything if already at suggested indent
10056 // and there is any other cursor which is not
10057 if has_some_cursor_in_whitespace
10058 && cursor.column == current_indent.len
10059 && current_indent.len == suggested_indent.len
10060 {
10061 continue;
10062 }
10063
10064 // Adjust line and move cursor to suggested indent
10065 // if cursor is not at suggested indent
10066 if cursor.column < suggested_indent.len
10067 && cursor.column <= current_indent.len
10068 && current_indent.len <= suggested_indent.len
10069 {
10070 selection.start = Point::new(cursor.row, suggested_indent.len);
10071 selection.end = selection.start;
10072 if row_delta == 0 {
10073 edits.extend(Buffer::edit_for_indent_size_adjustment(
10074 cursor.row,
10075 current_indent,
10076 suggested_indent,
10077 ));
10078 row_delta = suggested_indent.len - current_indent.len;
10079 }
10080 continue;
10081 }
10082
10083 // If current indent is more than suggested indent
10084 // only move cursor to current indent and skip indent
10085 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10086 selection.start = Point::new(cursor.row, current_indent.len);
10087 selection.end = selection.start;
10088 continue;
10089 }
10090 }
10091
10092 // Otherwise, insert a hard or soft tab.
10093 let settings = buffer.language_settings_at(cursor, cx);
10094 let tab_size = if settings.hard_tabs {
10095 IndentSize::tab()
10096 } else {
10097 let tab_size = settings.tab_size.get();
10098 let indent_remainder = snapshot
10099 .text_for_range(Point::new(cursor.row, 0)..cursor)
10100 .flat_map(str::chars)
10101 .fold(row_delta % tab_size, |counter: u32, c| {
10102 if c == '\t' {
10103 0
10104 } else {
10105 (counter + 1) % tab_size
10106 }
10107 });
10108
10109 let chars_to_next_tab_stop = tab_size - indent_remainder;
10110 IndentSize::spaces(chars_to_next_tab_stop)
10111 };
10112 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10113 selection.end = selection.start;
10114 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10115 row_delta += tab_size.len;
10116 }
10117
10118 self.transact(window, cx, |this, window, cx| {
10119 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10120 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10121 this.refresh_edit_prediction(true, false, window, cx);
10122 });
10123 }
10124
10125 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10126 if self.read_only(cx) {
10127 return;
10128 }
10129 if self.mode.is_single_line() {
10130 cx.propagate();
10131 return;
10132 }
10133
10134 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10135 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10136 let mut prev_edited_row = 0;
10137 let mut row_delta = 0;
10138 let mut edits = Vec::new();
10139 let buffer = self.buffer.read(cx);
10140 let snapshot = buffer.snapshot(cx);
10141 for selection in &mut selections {
10142 if selection.start.row != prev_edited_row {
10143 row_delta = 0;
10144 }
10145 prev_edited_row = selection.end.row;
10146
10147 row_delta =
10148 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10149 }
10150
10151 self.transact(window, cx, |this, window, cx| {
10152 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10153 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10154 });
10155 }
10156
10157 fn indent_selection(
10158 buffer: &MultiBuffer,
10159 snapshot: &MultiBufferSnapshot,
10160 selection: &mut Selection<Point>,
10161 edits: &mut Vec<(Range<Point>, String)>,
10162 delta_for_start_row: u32,
10163 cx: &App,
10164 ) -> u32 {
10165 let settings = buffer.language_settings_at(selection.start, cx);
10166 let tab_size = settings.tab_size.get();
10167 let indent_kind = if settings.hard_tabs {
10168 IndentKind::Tab
10169 } else {
10170 IndentKind::Space
10171 };
10172 let mut start_row = selection.start.row;
10173 let mut end_row = selection.end.row + 1;
10174
10175 // If a selection ends at the beginning of a line, don't indent
10176 // that last line.
10177 if selection.end.column == 0 && selection.end.row > selection.start.row {
10178 end_row -= 1;
10179 }
10180
10181 // Avoid re-indenting a row that has already been indented by a
10182 // previous selection, but still update this selection's column
10183 // to reflect that indentation.
10184 if delta_for_start_row > 0 {
10185 start_row += 1;
10186 selection.start.column += delta_for_start_row;
10187 if selection.end.row == selection.start.row {
10188 selection.end.column += delta_for_start_row;
10189 }
10190 }
10191
10192 let mut delta_for_end_row = 0;
10193 let has_multiple_rows = start_row + 1 != end_row;
10194 for row in start_row..end_row {
10195 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10196 let indent_delta = match (current_indent.kind, indent_kind) {
10197 (IndentKind::Space, IndentKind::Space) => {
10198 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10199 IndentSize::spaces(columns_to_next_tab_stop)
10200 }
10201 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10202 (_, IndentKind::Tab) => IndentSize::tab(),
10203 };
10204
10205 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10206 0
10207 } else {
10208 selection.start.column
10209 };
10210 let row_start = Point::new(row, start);
10211 edits.push((
10212 row_start..row_start,
10213 indent_delta.chars().collect::<String>(),
10214 ));
10215
10216 // Update this selection's endpoints to reflect the indentation.
10217 if row == selection.start.row {
10218 selection.start.column += indent_delta.len;
10219 }
10220 if row == selection.end.row {
10221 selection.end.column += indent_delta.len;
10222 delta_for_end_row = indent_delta.len;
10223 }
10224 }
10225
10226 if selection.start.row == selection.end.row {
10227 delta_for_start_row + delta_for_end_row
10228 } else {
10229 delta_for_end_row
10230 }
10231 }
10232
10233 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10234 if self.read_only(cx) {
10235 return;
10236 }
10237 if self.mode.is_single_line() {
10238 cx.propagate();
10239 return;
10240 }
10241
10242 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10243 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10244 let selections = self.selections.all::<Point>(&display_map);
10245 let mut deletion_ranges = Vec::new();
10246 let mut last_outdent = None;
10247 {
10248 let buffer = self.buffer.read(cx);
10249 let snapshot = buffer.snapshot(cx);
10250 for selection in &selections {
10251 let settings = buffer.language_settings_at(selection.start, cx);
10252 let tab_size = settings.tab_size.get();
10253 let mut rows = selection.spanned_rows(false, &display_map);
10254
10255 // Avoid re-outdenting a row that has already been outdented by a
10256 // previous selection.
10257 if let Some(last_row) = last_outdent
10258 && last_row == rows.start
10259 {
10260 rows.start = rows.start.next_row();
10261 }
10262 let has_multiple_rows = rows.len() > 1;
10263 for row in rows.iter_rows() {
10264 let indent_size = snapshot.indent_size_for_line(row);
10265 if indent_size.len > 0 {
10266 let deletion_len = match indent_size.kind {
10267 IndentKind::Space => {
10268 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10269 if columns_to_prev_tab_stop == 0 {
10270 tab_size
10271 } else {
10272 columns_to_prev_tab_stop
10273 }
10274 }
10275 IndentKind::Tab => 1,
10276 };
10277 let start = if has_multiple_rows
10278 || deletion_len > selection.start.column
10279 || indent_size.len < selection.start.column
10280 {
10281 0
10282 } else {
10283 selection.start.column - deletion_len
10284 };
10285 deletion_ranges.push(
10286 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10287 );
10288 last_outdent = Some(row);
10289 }
10290 }
10291 }
10292 }
10293
10294 self.transact(window, cx, |this, window, cx| {
10295 this.buffer.update(cx, |buffer, cx| {
10296 let empty_str: Arc<str> = Arc::default();
10297 buffer.edit(
10298 deletion_ranges
10299 .into_iter()
10300 .map(|range| (range, empty_str.clone())),
10301 None,
10302 cx,
10303 );
10304 });
10305 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10306 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10307 });
10308 }
10309
10310 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10311 if self.read_only(cx) {
10312 return;
10313 }
10314 if self.mode.is_single_line() {
10315 cx.propagate();
10316 return;
10317 }
10318
10319 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10320 let selections = self
10321 .selections
10322 .all::<usize>(&self.display_snapshot(cx))
10323 .into_iter()
10324 .map(|s| s.range());
10325
10326 self.transact(window, cx, |this, window, cx| {
10327 this.buffer.update(cx, |buffer, cx| {
10328 buffer.autoindent_ranges(selections, cx);
10329 });
10330 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10331 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10332 });
10333 }
10334
10335 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10336 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10337 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10338 let selections = self.selections.all::<Point>(&display_map);
10339
10340 let mut new_cursors = Vec::new();
10341 let mut edit_ranges = Vec::new();
10342 let mut selections = selections.iter().peekable();
10343 while let Some(selection) = selections.next() {
10344 let mut rows = selection.spanned_rows(false, &display_map);
10345
10346 // Accumulate contiguous regions of rows that we want to delete.
10347 while let Some(next_selection) = selections.peek() {
10348 let next_rows = next_selection.spanned_rows(false, &display_map);
10349 if next_rows.start <= rows.end {
10350 rows.end = next_rows.end;
10351 selections.next().unwrap();
10352 } else {
10353 break;
10354 }
10355 }
10356
10357 let buffer = display_map.buffer_snapshot();
10358 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10359 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10360 // If there's a line after the range, delete the \n from the end of the row range
10361 (
10362 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10363 rows.end,
10364 )
10365 } else {
10366 // If there isn't a line after the range, delete the \n from the line before the
10367 // start of the row range
10368 edit_start = edit_start.saturating_sub(1);
10369 (buffer.len(), rows.start.previous_row())
10370 };
10371
10372 let text_layout_details = self.text_layout_details(window);
10373 let x = display_map.x_for_display_point(
10374 selection.head().to_display_point(&display_map),
10375 &text_layout_details,
10376 );
10377 let row = Point::new(target_row.0, 0)
10378 .to_display_point(&display_map)
10379 .row();
10380 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10381
10382 new_cursors.push((
10383 selection.id,
10384 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10385 SelectionGoal::None,
10386 ));
10387 edit_ranges.push(edit_start..edit_end);
10388 }
10389
10390 self.transact(window, cx, |this, window, cx| {
10391 let buffer = this.buffer.update(cx, |buffer, cx| {
10392 let empty_str: Arc<str> = Arc::default();
10393 buffer.edit(
10394 edit_ranges
10395 .into_iter()
10396 .map(|range| (range, empty_str.clone())),
10397 None,
10398 cx,
10399 );
10400 buffer.snapshot(cx)
10401 });
10402 let new_selections = new_cursors
10403 .into_iter()
10404 .map(|(id, cursor, goal)| {
10405 let cursor = cursor.to_point(&buffer);
10406 Selection {
10407 id,
10408 start: cursor,
10409 end: cursor,
10410 reversed: false,
10411 goal,
10412 }
10413 })
10414 .collect();
10415
10416 this.change_selections(Default::default(), window, cx, |s| {
10417 s.select(new_selections);
10418 });
10419 });
10420 }
10421
10422 pub fn join_lines_impl(
10423 &mut self,
10424 insert_whitespace: bool,
10425 window: &mut Window,
10426 cx: &mut Context<Self>,
10427 ) {
10428 if self.read_only(cx) {
10429 return;
10430 }
10431 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10432 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10433 let start = MultiBufferRow(selection.start.row);
10434 // Treat single line selections as if they include the next line. Otherwise this action
10435 // would do nothing for single line selections individual cursors.
10436 let end = if selection.start.row == selection.end.row {
10437 MultiBufferRow(selection.start.row + 1)
10438 } else {
10439 MultiBufferRow(selection.end.row)
10440 };
10441
10442 if let Some(last_row_range) = row_ranges.last_mut()
10443 && start <= last_row_range.end
10444 {
10445 last_row_range.end = end;
10446 continue;
10447 }
10448 row_ranges.push(start..end);
10449 }
10450
10451 let snapshot = self.buffer.read(cx).snapshot(cx);
10452 let mut cursor_positions = Vec::new();
10453 for row_range in &row_ranges {
10454 let anchor = snapshot.anchor_before(Point::new(
10455 row_range.end.previous_row().0,
10456 snapshot.line_len(row_range.end.previous_row()),
10457 ));
10458 cursor_positions.push(anchor..anchor);
10459 }
10460
10461 self.transact(window, cx, |this, window, cx| {
10462 for row_range in row_ranges.into_iter().rev() {
10463 for row in row_range.iter_rows().rev() {
10464 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10465 let next_line_row = row.next_row();
10466 let indent = snapshot.indent_size_for_line(next_line_row);
10467 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10468
10469 let replace =
10470 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10471 " "
10472 } else {
10473 ""
10474 };
10475
10476 this.buffer.update(cx, |buffer, cx| {
10477 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10478 });
10479 }
10480 }
10481
10482 this.change_selections(Default::default(), window, cx, |s| {
10483 s.select_anchor_ranges(cursor_positions)
10484 });
10485 });
10486 }
10487
10488 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10489 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10490 self.join_lines_impl(true, window, cx);
10491 }
10492
10493 pub fn sort_lines_case_sensitive(
10494 &mut self,
10495 _: &SortLinesCaseSensitive,
10496 window: &mut Window,
10497 cx: &mut Context<Self>,
10498 ) {
10499 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10500 }
10501
10502 pub fn sort_lines_by_length(
10503 &mut self,
10504 _: &SortLinesByLength,
10505 window: &mut Window,
10506 cx: &mut Context<Self>,
10507 ) {
10508 self.manipulate_immutable_lines(window, cx, |lines| {
10509 lines.sort_by_key(|&line| line.chars().count())
10510 })
10511 }
10512
10513 pub fn sort_lines_case_insensitive(
10514 &mut self,
10515 _: &SortLinesCaseInsensitive,
10516 window: &mut Window,
10517 cx: &mut Context<Self>,
10518 ) {
10519 self.manipulate_immutable_lines(window, cx, |lines| {
10520 lines.sort_by_key(|line| line.to_lowercase())
10521 })
10522 }
10523
10524 pub fn unique_lines_case_insensitive(
10525 &mut self,
10526 _: &UniqueLinesCaseInsensitive,
10527 window: &mut Window,
10528 cx: &mut Context<Self>,
10529 ) {
10530 self.manipulate_immutable_lines(window, cx, |lines| {
10531 let mut seen = HashSet::default();
10532 lines.retain(|line| seen.insert(line.to_lowercase()));
10533 })
10534 }
10535
10536 pub fn unique_lines_case_sensitive(
10537 &mut self,
10538 _: &UniqueLinesCaseSensitive,
10539 window: &mut Window,
10540 cx: &mut Context<Self>,
10541 ) {
10542 self.manipulate_immutable_lines(window, cx, |lines| {
10543 let mut seen = HashSet::default();
10544 lines.retain(|line| seen.insert(*line));
10545 })
10546 }
10547
10548 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10549 let snapshot = self.buffer.read(cx).snapshot(cx);
10550 for selection in self.selections.disjoint_anchors_arc().iter() {
10551 if snapshot
10552 .language_at(selection.start)
10553 .and_then(|lang| lang.config().wrap_characters.as_ref())
10554 .is_some()
10555 {
10556 return true;
10557 }
10558 }
10559 false
10560 }
10561
10562 fn wrap_selections_in_tag(
10563 &mut self,
10564 _: &WrapSelectionsInTag,
10565 window: &mut Window,
10566 cx: &mut Context<Self>,
10567 ) {
10568 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10569
10570 let snapshot = self.buffer.read(cx).snapshot(cx);
10571
10572 let mut edits = Vec::new();
10573 let mut boundaries = Vec::new();
10574
10575 for selection in self
10576 .selections
10577 .all_adjusted(&self.display_snapshot(cx))
10578 .iter()
10579 {
10580 let Some(wrap_config) = snapshot
10581 .language_at(selection.start)
10582 .and_then(|lang| lang.config().wrap_characters.clone())
10583 else {
10584 continue;
10585 };
10586
10587 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10588 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10589
10590 let start_before = snapshot.anchor_before(selection.start);
10591 let end_after = snapshot.anchor_after(selection.end);
10592
10593 edits.push((start_before..start_before, open_tag));
10594 edits.push((end_after..end_after, close_tag));
10595
10596 boundaries.push((
10597 start_before,
10598 end_after,
10599 wrap_config.start_prefix.len(),
10600 wrap_config.end_suffix.len(),
10601 ));
10602 }
10603
10604 if edits.is_empty() {
10605 return;
10606 }
10607
10608 self.transact(window, cx, |this, window, cx| {
10609 let buffer = this.buffer.update(cx, |buffer, cx| {
10610 buffer.edit(edits, None, cx);
10611 buffer.snapshot(cx)
10612 });
10613
10614 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10615 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10616 boundaries.into_iter()
10617 {
10618 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10619 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10620 new_selections.push(open_offset..open_offset);
10621 new_selections.push(close_offset..close_offset);
10622 }
10623
10624 this.change_selections(Default::default(), window, cx, |s| {
10625 s.select_ranges(new_selections);
10626 });
10627
10628 this.request_autoscroll(Autoscroll::fit(), cx);
10629 });
10630 }
10631
10632 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10633 let Some(project) = self.project.clone() else {
10634 return;
10635 };
10636 self.reload(project, window, cx)
10637 .detach_and_notify_err(window, cx);
10638 }
10639
10640 pub fn restore_file(
10641 &mut self,
10642 _: &::git::RestoreFile,
10643 window: &mut Window,
10644 cx: &mut Context<Self>,
10645 ) {
10646 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10647 let mut buffer_ids = HashSet::default();
10648 let snapshot = self.buffer().read(cx).snapshot(cx);
10649 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10650 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10651 }
10652
10653 let buffer = self.buffer().read(cx);
10654 let ranges = buffer_ids
10655 .into_iter()
10656 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10657 .collect::<Vec<_>>();
10658
10659 self.restore_hunks_in_ranges(ranges, window, cx);
10660 }
10661
10662 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10663 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10664 let selections = self
10665 .selections
10666 .all(&self.display_snapshot(cx))
10667 .into_iter()
10668 .map(|s| s.range())
10669 .collect();
10670 self.restore_hunks_in_ranges(selections, window, cx);
10671 }
10672
10673 pub fn restore_hunks_in_ranges(
10674 &mut self,
10675 ranges: Vec<Range<Point>>,
10676 window: &mut Window,
10677 cx: &mut Context<Editor>,
10678 ) {
10679 let mut revert_changes = HashMap::default();
10680 let chunk_by = self
10681 .snapshot(window, cx)
10682 .hunks_for_ranges(ranges)
10683 .into_iter()
10684 .chunk_by(|hunk| hunk.buffer_id);
10685 for (buffer_id, hunks) in &chunk_by {
10686 let hunks = hunks.collect::<Vec<_>>();
10687 for hunk in &hunks {
10688 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10689 }
10690 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10691 }
10692 drop(chunk_by);
10693 if !revert_changes.is_empty() {
10694 self.transact(window, cx, |editor, window, cx| {
10695 editor.restore(revert_changes, window, cx);
10696 });
10697 }
10698 }
10699
10700 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10701 if let Some(status) = self
10702 .addons
10703 .iter()
10704 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10705 {
10706 return Some(status);
10707 }
10708 self.project
10709 .as_ref()?
10710 .read(cx)
10711 .status_for_buffer_id(buffer_id, cx)
10712 }
10713
10714 pub fn open_active_item_in_terminal(
10715 &mut self,
10716 _: &OpenInTerminal,
10717 window: &mut Window,
10718 cx: &mut Context<Self>,
10719 ) {
10720 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10721 let project_path = buffer.read(cx).project_path(cx)?;
10722 let project = self.project()?.read(cx);
10723 let entry = project.entry_for_path(&project_path, cx)?;
10724 let parent = match &entry.canonical_path {
10725 Some(canonical_path) => canonical_path.to_path_buf(),
10726 None => project.absolute_path(&project_path, cx)?,
10727 }
10728 .parent()?
10729 .to_path_buf();
10730 Some(parent)
10731 }) {
10732 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10733 }
10734 }
10735
10736 fn set_breakpoint_context_menu(
10737 &mut self,
10738 display_row: DisplayRow,
10739 position: Option<Anchor>,
10740 clicked_point: gpui::Point<Pixels>,
10741 window: &mut Window,
10742 cx: &mut Context<Self>,
10743 ) {
10744 let source = self
10745 .buffer
10746 .read(cx)
10747 .snapshot(cx)
10748 .anchor_before(Point::new(display_row.0, 0u32));
10749
10750 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10751
10752 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10753 self,
10754 source,
10755 clicked_point,
10756 context_menu,
10757 window,
10758 cx,
10759 );
10760 }
10761
10762 fn add_edit_breakpoint_block(
10763 &mut self,
10764 anchor: Anchor,
10765 breakpoint: &Breakpoint,
10766 edit_action: BreakpointPromptEditAction,
10767 window: &mut Window,
10768 cx: &mut Context<Self>,
10769 ) {
10770 let weak_editor = cx.weak_entity();
10771 let bp_prompt = cx.new(|cx| {
10772 BreakpointPromptEditor::new(
10773 weak_editor,
10774 anchor,
10775 breakpoint.clone(),
10776 edit_action,
10777 window,
10778 cx,
10779 )
10780 });
10781
10782 let height = bp_prompt.update(cx, |this, cx| {
10783 this.prompt
10784 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10785 });
10786 let cloned_prompt = bp_prompt.clone();
10787 let blocks = vec![BlockProperties {
10788 style: BlockStyle::Sticky,
10789 placement: BlockPlacement::Above(anchor),
10790 height: Some(height),
10791 render: Arc::new(move |cx| {
10792 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10793 cloned_prompt.clone().into_any_element()
10794 }),
10795 priority: 0,
10796 }];
10797
10798 let focus_handle = bp_prompt.focus_handle(cx);
10799 window.focus(&focus_handle);
10800
10801 let block_ids = self.insert_blocks(blocks, None, cx);
10802 bp_prompt.update(cx, |prompt, _| {
10803 prompt.add_block_ids(block_ids);
10804 });
10805 }
10806
10807 pub(crate) fn breakpoint_at_row(
10808 &self,
10809 row: u32,
10810 window: &mut Window,
10811 cx: &mut Context<Self>,
10812 ) -> Option<(Anchor, Breakpoint)> {
10813 let snapshot = self.snapshot(window, cx);
10814 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10815
10816 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10817 }
10818
10819 pub(crate) fn breakpoint_at_anchor(
10820 &self,
10821 breakpoint_position: Anchor,
10822 snapshot: &EditorSnapshot,
10823 cx: &mut Context<Self>,
10824 ) -> Option<(Anchor, Breakpoint)> {
10825 let buffer = self
10826 .buffer
10827 .read(cx)
10828 .buffer_for_anchor(breakpoint_position, cx)?;
10829
10830 let enclosing_excerpt = breakpoint_position.excerpt_id;
10831 let buffer_snapshot = buffer.read(cx).snapshot();
10832
10833 let row = buffer_snapshot
10834 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10835 .row;
10836
10837 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10838 let anchor_end = snapshot
10839 .buffer_snapshot()
10840 .anchor_after(Point::new(row, line_len));
10841
10842 self.breakpoint_store
10843 .as_ref()?
10844 .read_with(cx, |breakpoint_store, cx| {
10845 breakpoint_store
10846 .breakpoints(
10847 &buffer,
10848 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10849 &buffer_snapshot,
10850 cx,
10851 )
10852 .next()
10853 .and_then(|(bp, _)| {
10854 let breakpoint_row = buffer_snapshot
10855 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10856 .row;
10857
10858 if breakpoint_row == row {
10859 snapshot
10860 .buffer_snapshot()
10861 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10862 .map(|position| (position, bp.bp.clone()))
10863 } else {
10864 None
10865 }
10866 })
10867 })
10868 }
10869
10870 pub fn edit_log_breakpoint(
10871 &mut self,
10872 _: &EditLogBreakpoint,
10873 window: &mut Window,
10874 cx: &mut Context<Self>,
10875 ) {
10876 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10877 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10878 message: None,
10879 state: BreakpointState::Enabled,
10880 condition: None,
10881 hit_condition: None,
10882 });
10883
10884 self.add_edit_breakpoint_block(
10885 anchor,
10886 &breakpoint,
10887 BreakpointPromptEditAction::Log,
10888 window,
10889 cx,
10890 );
10891 }
10892 }
10893
10894 fn breakpoints_at_cursors(
10895 &self,
10896 window: &mut Window,
10897 cx: &mut Context<Self>,
10898 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10899 let snapshot = self.snapshot(window, cx);
10900 let cursors = self
10901 .selections
10902 .disjoint_anchors_arc()
10903 .iter()
10904 .map(|selection| {
10905 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
10906
10907 let breakpoint_position = self
10908 .breakpoint_at_row(cursor_position.row, window, cx)
10909 .map(|bp| bp.0)
10910 .unwrap_or_else(|| {
10911 snapshot
10912 .display_snapshot
10913 .buffer_snapshot()
10914 .anchor_after(Point::new(cursor_position.row, 0))
10915 });
10916
10917 let breakpoint = self
10918 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10919 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10920
10921 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10922 })
10923 // 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.
10924 .collect::<HashMap<Anchor, _>>();
10925
10926 cursors.into_iter().collect()
10927 }
10928
10929 pub fn enable_breakpoint(
10930 &mut self,
10931 _: &crate::actions::EnableBreakpoint,
10932 window: &mut Window,
10933 cx: &mut Context<Self>,
10934 ) {
10935 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10936 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10937 continue;
10938 };
10939 self.edit_breakpoint_at_anchor(
10940 anchor,
10941 breakpoint,
10942 BreakpointEditAction::InvertState,
10943 cx,
10944 );
10945 }
10946 }
10947
10948 pub fn disable_breakpoint(
10949 &mut self,
10950 _: &crate::actions::DisableBreakpoint,
10951 window: &mut Window,
10952 cx: &mut Context<Self>,
10953 ) {
10954 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10955 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10956 continue;
10957 };
10958 self.edit_breakpoint_at_anchor(
10959 anchor,
10960 breakpoint,
10961 BreakpointEditAction::InvertState,
10962 cx,
10963 );
10964 }
10965 }
10966
10967 pub fn toggle_breakpoint(
10968 &mut self,
10969 _: &crate::actions::ToggleBreakpoint,
10970 window: &mut Window,
10971 cx: &mut Context<Self>,
10972 ) {
10973 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10974 if let Some(breakpoint) = breakpoint {
10975 self.edit_breakpoint_at_anchor(
10976 anchor,
10977 breakpoint,
10978 BreakpointEditAction::Toggle,
10979 cx,
10980 );
10981 } else {
10982 self.edit_breakpoint_at_anchor(
10983 anchor,
10984 Breakpoint::new_standard(),
10985 BreakpointEditAction::Toggle,
10986 cx,
10987 );
10988 }
10989 }
10990 }
10991
10992 pub fn edit_breakpoint_at_anchor(
10993 &mut self,
10994 breakpoint_position: Anchor,
10995 breakpoint: Breakpoint,
10996 edit_action: BreakpointEditAction,
10997 cx: &mut Context<Self>,
10998 ) {
10999 let Some(breakpoint_store) = &self.breakpoint_store else {
11000 return;
11001 };
11002
11003 let Some(buffer) = self
11004 .buffer
11005 .read(cx)
11006 .buffer_for_anchor(breakpoint_position, cx)
11007 else {
11008 return;
11009 };
11010
11011 breakpoint_store.update(cx, |breakpoint_store, cx| {
11012 breakpoint_store.toggle_breakpoint(
11013 buffer,
11014 BreakpointWithPosition {
11015 position: breakpoint_position.text_anchor,
11016 bp: breakpoint,
11017 },
11018 edit_action,
11019 cx,
11020 );
11021 });
11022
11023 cx.notify();
11024 }
11025
11026 #[cfg(any(test, feature = "test-support"))]
11027 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11028 self.breakpoint_store.clone()
11029 }
11030
11031 pub fn prepare_restore_change(
11032 &self,
11033 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11034 hunk: &MultiBufferDiffHunk,
11035 cx: &mut App,
11036 ) -> Option<()> {
11037 if hunk.is_created_file() {
11038 return None;
11039 }
11040 let buffer = self.buffer.read(cx);
11041 let diff = buffer.diff_for(hunk.buffer_id)?;
11042 let buffer = buffer.buffer(hunk.buffer_id)?;
11043 let buffer = buffer.read(cx);
11044 let original_text = diff
11045 .read(cx)
11046 .base_text()
11047 .as_rope()
11048 .slice(hunk.diff_base_byte_range.clone());
11049 let buffer_snapshot = buffer.snapshot();
11050 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11051 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11052 probe
11053 .0
11054 .start
11055 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11056 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11057 }) {
11058 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11059 Some(())
11060 } else {
11061 None
11062 }
11063 }
11064
11065 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11066 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11067 }
11068
11069 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11070 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11071 }
11072
11073 fn manipulate_lines<M>(
11074 &mut self,
11075 window: &mut Window,
11076 cx: &mut Context<Self>,
11077 mut manipulate: M,
11078 ) where
11079 M: FnMut(&str) -> LineManipulationResult,
11080 {
11081 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11082
11083 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11084 let buffer = self.buffer.read(cx).snapshot(cx);
11085
11086 let mut edits = Vec::new();
11087
11088 let selections = self.selections.all::<Point>(&display_map);
11089 let mut selections = selections.iter().peekable();
11090 let mut contiguous_row_selections = Vec::new();
11091 let mut new_selections = Vec::new();
11092 let mut added_lines = 0;
11093 let mut removed_lines = 0;
11094
11095 while let Some(selection) = selections.next() {
11096 let (start_row, end_row) = consume_contiguous_rows(
11097 &mut contiguous_row_selections,
11098 selection,
11099 &display_map,
11100 &mut selections,
11101 );
11102
11103 let start_point = Point::new(start_row.0, 0);
11104 let end_point = Point::new(
11105 end_row.previous_row().0,
11106 buffer.line_len(end_row.previous_row()),
11107 );
11108 let text = buffer
11109 .text_for_range(start_point..end_point)
11110 .collect::<String>();
11111
11112 let LineManipulationResult {
11113 new_text,
11114 line_count_before,
11115 line_count_after,
11116 } = manipulate(&text);
11117
11118 edits.push((start_point..end_point, new_text));
11119
11120 // Selections must change based on added and removed line count
11121 let start_row =
11122 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11123 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11124 new_selections.push(Selection {
11125 id: selection.id,
11126 start: start_row,
11127 end: end_row,
11128 goal: SelectionGoal::None,
11129 reversed: selection.reversed,
11130 });
11131
11132 if line_count_after > line_count_before {
11133 added_lines += line_count_after - line_count_before;
11134 } else if line_count_before > line_count_after {
11135 removed_lines += line_count_before - line_count_after;
11136 }
11137 }
11138
11139 self.transact(window, cx, |this, window, cx| {
11140 let buffer = this.buffer.update(cx, |buffer, cx| {
11141 buffer.edit(edits, None, cx);
11142 buffer.snapshot(cx)
11143 });
11144
11145 // Recalculate offsets on newly edited buffer
11146 let new_selections = new_selections
11147 .iter()
11148 .map(|s| {
11149 let start_point = Point::new(s.start.0, 0);
11150 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11151 Selection {
11152 id: s.id,
11153 start: buffer.point_to_offset(start_point),
11154 end: buffer.point_to_offset(end_point),
11155 goal: s.goal,
11156 reversed: s.reversed,
11157 }
11158 })
11159 .collect();
11160
11161 this.change_selections(Default::default(), window, cx, |s| {
11162 s.select(new_selections);
11163 });
11164
11165 this.request_autoscroll(Autoscroll::fit(), cx);
11166 });
11167 }
11168
11169 fn manipulate_immutable_lines<Fn>(
11170 &mut self,
11171 window: &mut Window,
11172 cx: &mut Context<Self>,
11173 mut callback: Fn,
11174 ) where
11175 Fn: FnMut(&mut Vec<&str>),
11176 {
11177 self.manipulate_lines(window, cx, |text| {
11178 let mut lines: Vec<&str> = text.split('\n').collect();
11179 let line_count_before = lines.len();
11180
11181 callback(&mut lines);
11182
11183 LineManipulationResult {
11184 new_text: lines.join("\n"),
11185 line_count_before,
11186 line_count_after: lines.len(),
11187 }
11188 });
11189 }
11190
11191 fn manipulate_mutable_lines<Fn>(
11192 &mut self,
11193 window: &mut Window,
11194 cx: &mut Context<Self>,
11195 mut callback: Fn,
11196 ) where
11197 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11198 {
11199 self.manipulate_lines(window, cx, |text| {
11200 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11201 let line_count_before = lines.len();
11202
11203 callback(&mut lines);
11204
11205 LineManipulationResult {
11206 new_text: lines.join("\n"),
11207 line_count_before,
11208 line_count_after: lines.len(),
11209 }
11210 });
11211 }
11212
11213 pub fn convert_indentation_to_spaces(
11214 &mut self,
11215 _: &ConvertIndentationToSpaces,
11216 window: &mut Window,
11217 cx: &mut Context<Self>,
11218 ) {
11219 let settings = self.buffer.read(cx).language_settings(cx);
11220 let tab_size = settings.tab_size.get() as usize;
11221
11222 self.manipulate_mutable_lines(window, cx, |lines| {
11223 // Allocates a reasonably sized scratch buffer once for the whole loop
11224 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11225 // Avoids recomputing spaces that could be inserted many times
11226 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11227 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11228 .collect();
11229
11230 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11231 let mut chars = line.as_ref().chars();
11232 let mut col = 0;
11233 let mut changed = false;
11234
11235 for ch in chars.by_ref() {
11236 match ch {
11237 ' ' => {
11238 reindented_line.push(' ');
11239 col += 1;
11240 }
11241 '\t' => {
11242 // \t are converted to spaces depending on the current column
11243 let spaces_len = tab_size - (col % tab_size);
11244 reindented_line.extend(&space_cache[spaces_len - 1]);
11245 col += spaces_len;
11246 changed = true;
11247 }
11248 _ => {
11249 // If we dont append before break, the character is consumed
11250 reindented_line.push(ch);
11251 break;
11252 }
11253 }
11254 }
11255
11256 if !changed {
11257 reindented_line.clear();
11258 continue;
11259 }
11260 // Append the rest of the line and replace old reference with new one
11261 reindented_line.extend(chars);
11262 *line = Cow::Owned(reindented_line.clone());
11263 reindented_line.clear();
11264 }
11265 });
11266 }
11267
11268 pub fn convert_indentation_to_tabs(
11269 &mut self,
11270 _: &ConvertIndentationToTabs,
11271 window: &mut Window,
11272 cx: &mut Context<Self>,
11273 ) {
11274 let settings = self.buffer.read(cx).language_settings(cx);
11275 let tab_size = settings.tab_size.get() as usize;
11276
11277 self.manipulate_mutable_lines(window, cx, |lines| {
11278 // Allocates a reasonably sized buffer once for the whole loop
11279 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11280 // Avoids recomputing spaces that could be inserted many times
11281 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11282 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11283 .collect();
11284
11285 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11286 let mut chars = line.chars();
11287 let mut spaces_count = 0;
11288 let mut first_non_indent_char = None;
11289 let mut changed = false;
11290
11291 for ch in chars.by_ref() {
11292 match ch {
11293 ' ' => {
11294 // Keep track of spaces. Append \t when we reach tab_size
11295 spaces_count += 1;
11296 changed = true;
11297 if spaces_count == tab_size {
11298 reindented_line.push('\t');
11299 spaces_count = 0;
11300 }
11301 }
11302 '\t' => {
11303 reindented_line.push('\t');
11304 spaces_count = 0;
11305 }
11306 _ => {
11307 // Dont append it yet, we might have remaining spaces
11308 first_non_indent_char = Some(ch);
11309 break;
11310 }
11311 }
11312 }
11313
11314 if !changed {
11315 reindented_line.clear();
11316 continue;
11317 }
11318 // Remaining spaces that didn't make a full tab stop
11319 if spaces_count > 0 {
11320 reindented_line.extend(&space_cache[spaces_count - 1]);
11321 }
11322 // If we consume an extra character that was not indentation, add it back
11323 if let Some(extra_char) = first_non_indent_char {
11324 reindented_line.push(extra_char);
11325 }
11326 // Append the rest of the line and replace old reference with new one
11327 reindented_line.extend(chars);
11328 *line = Cow::Owned(reindented_line.clone());
11329 reindented_line.clear();
11330 }
11331 });
11332 }
11333
11334 pub fn convert_to_upper_case(
11335 &mut self,
11336 _: &ConvertToUpperCase,
11337 window: &mut Window,
11338 cx: &mut Context<Self>,
11339 ) {
11340 self.manipulate_text(window, cx, |text| text.to_uppercase())
11341 }
11342
11343 pub fn convert_to_lower_case(
11344 &mut self,
11345 _: &ConvertToLowerCase,
11346 window: &mut Window,
11347 cx: &mut Context<Self>,
11348 ) {
11349 self.manipulate_text(window, cx, |text| text.to_lowercase())
11350 }
11351
11352 pub fn convert_to_title_case(
11353 &mut self,
11354 _: &ConvertToTitleCase,
11355 window: &mut Window,
11356 cx: &mut Context<Self>,
11357 ) {
11358 self.manipulate_text(window, cx, |text| {
11359 text.split('\n')
11360 .map(|line| line.to_case(Case::Title))
11361 .join("\n")
11362 })
11363 }
11364
11365 pub fn convert_to_snake_case(
11366 &mut self,
11367 _: &ConvertToSnakeCase,
11368 window: &mut Window,
11369 cx: &mut Context<Self>,
11370 ) {
11371 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11372 }
11373
11374 pub fn convert_to_kebab_case(
11375 &mut self,
11376 _: &ConvertToKebabCase,
11377 window: &mut Window,
11378 cx: &mut Context<Self>,
11379 ) {
11380 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11381 }
11382
11383 pub fn convert_to_upper_camel_case(
11384 &mut self,
11385 _: &ConvertToUpperCamelCase,
11386 window: &mut Window,
11387 cx: &mut Context<Self>,
11388 ) {
11389 self.manipulate_text(window, cx, |text| {
11390 text.split('\n')
11391 .map(|line| line.to_case(Case::UpperCamel))
11392 .join("\n")
11393 })
11394 }
11395
11396 pub fn convert_to_lower_camel_case(
11397 &mut self,
11398 _: &ConvertToLowerCamelCase,
11399 window: &mut Window,
11400 cx: &mut Context<Self>,
11401 ) {
11402 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11403 }
11404
11405 pub fn convert_to_opposite_case(
11406 &mut self,
11407 _: &ConvertToOppositeCase,
11408 window: &mut Window,
11409 cx: &mut Context<Self>,
11410 ) {
11411 self.manipulate_text(window, cx, |text| {
11412 text.chars()
11413 .fold(String::with_capacity(text.len()), |mut t, c| {
11414 if c.is_uppercase() {
11415 t.extend(c.to_lowercase());
11416 } else {
11417 t.extend(c.to_uppercase());
11418 }
11419 t
11420 })
11421 })
11422 }
11423
11424 pub fn convert_to_sentence_case(
11425 &mut self,
11426 _: &ConvertToSentenceCase,
11427 window: &mut Window,
11428 cx: &mut Context<Self>,
11429 ) {
11430 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11431 }
11432
11433 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11434 self.manipulate_text(window, cx, |text| {
11435 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11436 if has_upper_case_characters {
11437 text.to_lowercase()
11438 } else {
11439 text.to_uppercase()
11440 }
11441 })
11442 }
11443
11444 pub fn convert_to_rot13(
11445 &mut self,
11446 _: &ConvertToRot13,
11447 window: &mut Window,
11448 cx: &mut Context<Self>,
11449 ) {
11450 self.manipulate_text(window, cx, |text| {
11451 text.chars()
11452 .map(|c| match c {
11453 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11454 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11455 _ => c,
11456 })
11457 .collect()
11458 })
11459 }
11460
11461 pub fn convert_to_rot47(
11462 &mut self,
11463 _: &ConvertToRot47,
11464 window: &mut Window,
11465 cx: &mut Context<Self>,
11466 ) {
11467 self.manipulate_text(window, cx, |text| {
11468 text.chars()
11469 .map(|c| {
11470 let code_point = c as u32;
11471 if code_point >= 33 && code_point <= 126 {
11472 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11473 }
11474 c
11475 })
11476 .collect()
11477 })
11478 }
11479
11480 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11481 where
11482 Fn: FnMut(&str) -> String,
11483 {
11484 let buffer = self.buffer.read(cx).snapshot(cx);
11485
11486 let mut new_selections = Vec::new();
11487 let mut edits = Vec::new();
11488 let mut selection_adjustment = 0i32;
11489
11490 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11491 let selection_is_empty = selection.is_empty();
11492
11493 let (start, end) = if selection_is_empty {
11494 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11495 (word_range.start, word_range.end)
11496 } else {
11497 (
11498 buffer.point_to_offset(selection.start),
11499 buffer.point_to_offset(selection.end),
11500 )
11501 };
11502
11503 let text = buffer.text_for_range(start..end).collect::<String>();
11504 let old_length = text.len() as i32;
11505 let text = callback(&text);
11506
11507 new_selections.push(Selection {
11508 start: (start as i32 - selection_adjustment) as usize,
11509 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11510 goal: SelectionGoal::None,
11511 id: selection.id,
11512 reversed: selection.reversed,
11513 });
11514
11515 selection_adjustment += old_length - text.len() as i32;
11516
11517 edits.push((start..end, text));
11518 }
11519
11520 self.transact(window, cx, |this, window, cx| {
11521 this.buffer.update(cx, |buffer, cx| {
11522 buffer.edit(edits, None, cx);
11523 });
11524
11525 this.change_selections(Default::default(), window, cx, |s| {
11526 s.select(new_selections);
11527 });
11528
11529 this.request_autoscroll(Autoscroll::fit(), cx);
11530 });
11531 }
11532
11533 pub fn move_selection_on_drop(
11534 &mut self,
11535 selection: &Selection<Anchor>,
11536 target: DisplayPoint,
11537 is_cut: bool,
11538 window: &mut Window,
11539 cx: &mut Context<Self>,
11540 ) {
11541 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11542 let buffer = display_map.buffer_snapshot();
11543 let mut edits = Vec::new();
11544 let insert_point = display_map
11545 .clip_point(target, Bias::Left)
11546 .to_point(&display_map);
11547 let text = buffer
11548 .text_for_range(selection.start..selection.end)
11549 .collect::<String>();
11550 if is_cut {
11551 edits.push(((selection.start..selection.end), String::new()));
11552 }
11553 let insert_anchor = buffer.anchor_before(insert_point);
11554 edits.push(((insert_anchor..insert_anchor), text));
11555 let last_edit_start = insert_anchor.bias_left(buffer);
11556 let last_edit_end = insert_anchor.bias_right(buffer);
11557 self.transact(window, cx, |this, window, cx| {
11558 this.buffer.update(cx, |buffer, cx| {
11559 buffer.edit(edits, None, cx);
11560 });
11561 this.change_selections(Default::default(), window, cx, |s| {
11562 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11563 });
11564 });
11565 }
11566
11567 pub fn clear_selection_drag_state(&mut self) {
11568 self.selection_drag_state = SelectionDragState::None;
11569 }
11570
11571 pub fn duplicate(
11572 &mut self,
11573 upwards: bool,
11574 whole_lines: bool,
11575 window: &mut Window,
11576 cx: &mut Context<Self>,
11577 ) {
11578 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11579
11580 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11581 let buffer = display_map.buffer_snapshot();
11582 let selections = self.selections.all::<Point>(&display_map);
11583
11584 let mut edits = Vec::new();
11585 let mut selections_iter = selections.iter().peekable();
11586 while let Some(selection) = selections_iter.next() {
11587 let mut rows = selection.spanned_rows(false, &display_map);
11588 // duplicate line-wise
11589 if whole_lines || selection.start == selection.end {
11590 // Avoid duplicating the same lines twice.
11591 while let Some(next_selection) = selections_iter.peek() {
11592 let next_rows = next_selection.spanned_rows(false, &display_map);
11593 if next_rows.start < rows.end {
11594 rows.end = next_rows.end;
11595 selections_iter.next().unwrap();
11596 } else {
11597 break;
11598 }
11599 }
11600
11601 // Copy the text from the selected row region and splice it either at the start
11602 // or end of the region.
11603 let start = Point::new(rows.start.0, 0);
11604 let end = Point::new(
11605 rows.end.previous_row().0,
11606 buffer.line_len(rows.end.previous_row()),
11607 );
11608
11609 let mut text = buffer.text_for_range(start..end).collect::<String>();
11610
11611 let insert_location = if upwards {
11612 // When duplicating upward, we need to insert before the current line.
11613 // If we're on the last line and it doesn't end with a newline,
11614 // we need to add a newline before the duplicated content.
11615 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11616 && buffer.max_point().column > 0
11617 && !text.ends_with('\n');
11618
11619 if needs_leading_newline {
11620 text.insert(0, '\n');
11621 end
11622 } else {
11623 text.push('\n');
11624 Point::new(rows.start.0, 0)
11625 }
11626 } else {
11627 text.push('\n');
11628 start
11629 };
11630 edits.push((insert_location..insert_location, text));
11631 } else {
11632 // duplicate character-wise
11633 let start = selection.start;
11634 let end = selection.end;
11635 let text = buffer.text_for_range(start..end).collect::<String>();
11636 edits.push((selection.end..selection.end, text));
11637 }
11638 }
11639
11640 self.transact(window, cx, |this, window, cx| {
11641 this.buffer.update(cx, |buffer, cx| {
11642 buffer.edit(edits, None, cx);
11643 });
11644
11645 // When duplicating upward with whole lines, move the cursor to the duplicated line
11646 if upwards && whole_lines {
11647 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11648
11649 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11650 let mut new_ranges = Vec::new();
11651 let selections = s.all::<Point>(&display_map);
11652 let mut selections_iter = selections.iter().peekable();
11653
11654 while let Some(first_selection) = selections_iter.next() {
11655 // Group contiguous selections together to find the total row span
11656 let mut group_selections = vec![first_selection];
11657 let mut rows = first_selection.spanned_rows(false, &display_map);
11658
11659 while let Some(next_selection) = selections_iter.peek() {
11660 let next_rows = next_selection.spanned_rows(false, &display_map);
11661 if next_rows.start < rows.end {
11662 rows.end = next_rows.end;
11663 group_selections.push(selections_iter.next().unwrap());
11664 } else {
11665 break;
11666 }
11667 }
11668
11669 let row_count = rows.end.0 - rows.start.0;
11670
11671 // Move all selections in this group up by the total number of duplicated rows
11672 for selection in group_selections {
11673 let new_start = Point::new(
11674 selection.start.row.saturating_sub(row_count),
11675 selection.start.column,
11676 );
11677
11678 let new_end = Point::new(
11679 selection.end.row.saturating_sub(row_count),
11680 selection.end.column,
11681 );
11682
11683 new_ranges.push(new_start..new_end);
11684 }
11685 }
11686
11687 s.select_ranges(new_ranges);
11688 });
11689 }
11690
11691 this.request_autoscroll(Autoscroll::fit(), cx);
11692 });
11693 }
11694
11695 pub fn duplicate_line_up(
11696 &mut self,
11697 _: &DuplicateLineUp,
11698 window: &mut Window,
11699 cx: &mut Context<Self>,
11700 ) {
11701 self.duplicate(true, true, window, cx);
11702 }
11703
11704 pub fn duplicate_line_down(
11705 &mut self,
11706 _: &DuplicateLineDown,
11707 window: &mut Window,
11708 cx: &mut Context<Self>,
11709 ) {
11710 self.duplicate(false, true, window, cx);
11711 }
11712
11713 pub fn duplicate_selection(
11714 &mut self,
11715 _: &DuplicateSelection,
11716 window: &mut Window,
11717 cx: &mut Context<Self>,
11718 ) {
11719 self.duplicate(false, false, window, cx);
11720 }
11721
11722 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11723 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11724 if self.mode.is_single_line() {
11725 cx.propagate();
11726 return;
11727 }
11728
11729 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11730 let buffer = self.buffer.read(cx).snapshot(cx);
11731
11732 let mut edits = Vec::new();
11733 let mut unfold_ranges = Vec::new();
11734 let mut refold_creases = Vec::new();
11735
11736 let selections = self.selections.all::<Point>(&display_map);
11737 let mut selections = selections.iter().peekable();
11738 let mut contiguous_row_selections = Vec::new();
11739 let mut new_selections = Vec::new();
11740
11741 while let Some(selection) = selections.next() {
11742 // Find all the selections that span a contiguous row range
11743 let (start_row, end_row) = consume_contiguous_rows(
11744 &mut contiguous_row_selections,
11745 selection,
11746 &display_map,
11747 &mut selections,
11748 );
11749
11750 // Move the text spanned by the row range to be before the line preceding the row range
11751 if start_row.0 > 0 {
11752 let range_to_move = Point::new(
11753 start_row.previous_row().0,
11754 buffer.line_len(start_row.previous_row()),
11755 )
11756 ..Point::new(
11757 end_row.previous_row().0,
11758 buffer.line_len(end_row.previous_row()),
11759 );
11760 let insertion_point = display_map
11761 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11762 .0;
11763
11764 // Don't move lines across excerpts
11765 if buffer
11766 .excerpt_containing(insertion_point..range_to_move.end)
11767 .is_some()
11768 {
11769 let text = buffer
11770 .text_for_range(range_to_move.clone())
11771 .flat_map(|s| s.chars())
11772 .skip(1)
11773 .chain(['\n'])
11774 .collect::<String>();
11775
11776 edits.push((
11777 buffer.anchor_after(range_to_move.start)
11778 ..buffer.anchor_before(range_to_move.end),
11779 String::new(),
11780 ));
11781 let insertion_anchor = buffer.anchor_after(insertion_point);
11782 edits.push((insertion_anchor..insertion_anchor, text));
11783
11784 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11785
11786 // Move selections up
11787 new_selections.extend(contiguous_row_selections.drain(..).map(
11788 |mut selection| {
11789 selection.start.row -= row_delta;
11790 selection.end.row -= row_delta;
11791 selection
11792 },
11793 ));
11794
11795 // Move folds up
11796 unfold_ranges.push(range_to_move.clone());
11797 for fold in display_map.folds_in_range(
11798 buffer.anchor_before(range_to_move.start)
11799 ..buffer.anchor_after(range_to_move.end),
11800 ) {
11801 let mut start = fold.range.start.to_point(&buffer);
11802 let mut end = fold.range.end.to_point(&buffer);
11803 start.row -= row_delta;
11804 end.row -= row_delta;
11805 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11806 }
11807 }
11808 }
11809
11810 // If we didn't move line(s), preserve the existing selections
11811 new_selections.append(&mut contiguous_row_selections);
11812 }
11813
11814 self.transact(window, cx, |this, window, cx| {
11815 this.unfold_ranges(&unfold_ranges, true, true, cx);
11816 this.buffer.update(cx, |buffer, cx| {
11817 for (range, text) in edits {
11818 buffer.edit([(range, text)], None, cx);
11819 }
11820 });
11821 this.fold_creases(refold_creases, true, window, cx);
11822 this.change_selections(Default::default(), window, cx, |s| {
11823 s.select(new_selections);
11824 })
11825 });
11826 }
11827
11828 pub fn move_line_down(
11829 &mut self,
11830 _: &MoveLineDown,
11831 window: &mut Window,
11832 cx: &mut Context<Self>,
11833 ) {
11834 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11835 if self.mode.is_single_line() {
11836 cx.propagate();
11837 return;
11838 }
11839
11840 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11841 let buffer = self.buffer.read(cx).snapshot(cx);
11842
11843 let mut edits = Vec::new();
11844 let mut unfold_ranges = Vec::new();
11845 let mut refold_creases = Vec::new();
11846
11847 let selections = self.selections.all::<Point>(&display_map);
11848 let mut selections = selections.iter().peekable();
11849 let mut contiguous_row_selections = Vec::new();
11850 let mut new_selections = Vec::new();
11851
11852 while let Some(selection) = selections.next() {
11853 // Find all the selections that span a contiguous row range
11854 let (start_row, end_row) = consume_contiguous_rows(
11855 &mut contiguous_row_selections,
11856 selection,
11857 &display_map,
11858 &mut selections,
11859 );
11860
11861 // Move the text spanned by the row range to be after the last line of the row range
11862 if end_row.0 <= buffer.max_point().row {
11863 let range_to_move =
11864 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11865 let insertion_point = display_map
11866 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11867 .0;
11868
11869 // Don't move lines across excerpt boundaries
11870 if buffer
11871 .excerpt_containing(range_to_move.start..insertion_point)
11872 .is_some()
11873 {
11874 let mut text = String::from("\n");
11875 text.extend(buffer.text_for_range(range_to_move.clone()));
11876 text.pop(); // Drop trailing newline
11877 edits.push((
11878 buffer.anchor_after(range_to_move.start)
11879 ..buffer.anchor_before(range_to_move.end),
11880 String::new(),
11881 ));
11882 let insertion_anchor = buffer.anchor_after(insertion_point);
11883 edits.push((insertion_anchor..insertion_anchor, text));
11884
11885 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11886
11887 // Move selections down
11888 new_selections.extend(contiguous_row_selections.drain(..).map(
11889 |mut selection| {
11890 selection.start.row += row_delta;
11891 selection.end.row += row_delta;
11892 selection
11893 },
11894 ));
11895
11896 // Move folds down
11897 unfold_ranges.push(range_to_move.clone());
11898 for fold in display_map.folds_in_range(
11899 buffer.anchor_before(range_to_move.start)
11900 ..buffer.anchor_after(range_to_move.end),
11901 ) {
11902 let mut start = fold.range.start.to_point(&buffer);
11903 let mut end = fold.range.end.to_point(&buffer);
11904 start.row += row_delta;
11905 end.row += row_delta;
11906 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11907 }
11908 }
11909 }
11910
11911 // If we didn't move line(s), preserve the existing selections
11912 new_selections.append(&mut contiguous_row_selections);
11913 }
11914
11915 self.transact(window, cx, |this, window, cx| {
11916 this.unfold_ranges(&unfold_ranges, true, true, cx);
11917 this.buffer.update(cx, |buffer, cx| {
11918 for (range, text) in edits {
11919 buffer.edit([(range, text)], None, cx);
11920 }
11921 });
11922 this.fold_creases(refold_creases, true, window, cx);
11923 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11924 });
11925 }
11926
11927 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11928 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11929 let text_layout_details = &self.text_layout_details(window);
11930 self.transact(window, cx, |this, window, cx| {
11931 let edits = this.change_selections(Default::default(), window, cx, |s| {
11932 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11933 s.move_with(|display_map, selection| {
11934 if !selection.is_empty() {
11935 return;
11936 }
11937
11938 let mut head = selection.head();
11939 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11940 if head.column() == display_map.line_len(head.row()) {
11941 transpose_offset = display_map
11942 .buffer_snapshot()
11943 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11944 }
11945
11946 if transpose_offset == 0 {
11947 return;
11948 }
11949
11950 *head.column_mut() += 1;
11951 head = display_map.clip_point(head, Bias::Right);
11952 let goal = SelectionGoal::HorizontalPosition(
11953 display_map
11954 .x_for_display_point(head, text_layout_details)
11955 .into(),
11956 );
11957 selection.collapse_to(head, goal);
11958
11959 let transpose_start = display_map
11960 .buffer_snapshot()
11961 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11962 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11963 let transpose_end = display_map
11964 .buffer_snapshot()
11965 .clip_offset(transpose_offset + 1, Bias::Right);
11966 if let Some(ch) = display_map
11967 .buffer_snapshot()
11968 .chars_at(transpose_start)
11969 .next()
11970 {
11971 edits.push((transpose_start..transpose_offset, String::new()));
11972 edits.push((transpose_end..transpose_end, ch.to_string()));
11973 }
11974 }
11975 });
11976 edits
11977 });
11978 this.buffer
11979 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11980 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
11981 this.change_selections(Default::default(), window, cx, |s| {
11982 s.select(selections);
11983 });
11984 });
11985 }
11986
11987 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11988 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11989 if self.mode.is_single_line() {
11990 cx.propagate();
11991 return;
11992 }
11993
11994 self.rewrap_impl(RewrapOptions::default(), cx)
11995 }
11996
11997 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11998 let buffer = self.buffer.read(cx).snapshot(cx);
11999 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12000
12001 #[derive(Clone, Debug, PartialEq)]
12002 enum CommentFormat {
12003 /// single line comment, with prefix for line
12004 Line(String),
12005 /// single line within a block comment, with prefix for line
12006 BlockLine(String),
12007 /// a single line of a block comment that includes the initial delimiter
12008 BlockCommentWithStart(BlockCommentConfig),
12009 /// a single line of a block comment that includes the ending delimiter
12010 BlockCommentWithEnd(BlockCommentConfig),
12011 }
12012
12013 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12014 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12015 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12016 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12017 .peekable();
12018
12019 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12020 row
12021 } else {
12022 return Vec::new();
12023 };
12024
12025 let language_settings = buffer.language_settings_at(selection.head(), cx);
12026 let language_scope = buffer.language_scope_at(selection.head());
12027
12028 let indent_and_prefix_for_row =
12029 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12030 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12031 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12032 &language_scope
12033 {
12034 let indent_end = Point::new(row, indent.len);
12035 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12036 let line_text_after_indent = buffer
12037 .text_for_range(indent_end..line_end)
12038 .collect::<String>();
12039
12040 let is_within_comment_override = buffer
12041 .language_scope_at(indent_end)
12042 .is_some_and(|scope| scope.override_name() == Some("comment"));
12043 let comment_delimiters = if is_within_comment_override {
12044 // we are within a comment syntax node, but we don't
12045 // yet know what kind of comment: block, doc or line
12046 match (
12047 language_scope.documentation_comment(),
12048 language_scope.block_comment(),
12049 ) {
12050 (Some(config), _) | (_, Some(config))
12051 if buffer.contains_str_at(indent_end, &config.start) =>
12052 {
12053 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12054 }
12055 (Some(config), _) | (_, Some(config))
12056 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12057 {
12058 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12059 }
12060 (Some(config), _) | (_, Some(config))
12061 if buffer.contains_str_at(indent_end, &config.prefix) =>
12062 {
12063 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12064 }
12065 (_, _) => language_scope
12066 .line_comment_prefixes()
12067 .iter()
12068 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12069 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12070 }
12071 } else {
12072 // we not in an overridden comment node, but we may
12073 // be within a non-overridden line comment node
12074 language_scope
12075 .line_comment_prefixes()
12076 .iter()
12077 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12078 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12079 };
12080
12081 let rewrap_prefix = language_scope
12082 .rewrap_prefixes()
12083 .iter()
12084 .find_map(|prefix_regex| {
12085 prefix_regex.find(&line_text_after_indent).map(|mat| {
12086 if mat.start() == 0 {
12087 Some(mat.as_str().to_string())
12088 } else {
12089 None
12090 }
12091 })
12092 })
12093 .flatten();
12094 (comment_delimiters, rewrap_prefix)
12095 } else {
12096 (None, None)
12097 };
12098 (indent, comment_prefix, rewrap_prefix)
12099 };
12100
12101 let mut ranges = Vec::new();
12102 let from_empty_selection = selection.is_empty();
12103
12104 let mut current_range_start = first_row;
12105 let mut prev_row = first_row;
12106 let (
12107 mut current_range_indent,
12108 mut current_range_comment_delimiters,
12109 mut current_range_rewrap_prefix,
12110 ) = indent_and_prefix_for_row(first_row);
12111
12112 for row in non_blank_rows_iter.skip(1) {
12113 let has_paragraph_break = row > prev_row + 1;
12114
12115 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12116 indent_and_prefix_for_row(row);
12117
12118 let has_indent_change = row_indent != current_range_indent;
12119 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12120
12121 let has_boundary_change = has_comment_change
12122 || row_rewrap_prefix.is_some()
12123 || (has_indent_change && current_range_comment_delimiters.is_some());
12124
12125 if has_paragraph_break || has_boundary_change {
12126 ranges.push((
12127 language_settings.clone(),
12128 Point::new(current_range_start, 0)
12129 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12130 current_range_indent,
12131 current_range_comment_delimiters.clone(),
12132 current_range_rewrap_prefix.clone(),
12133 from_empty_selection,
12134 ));
12135 current_range_start = row;
12136 current_range_indent = row_indent;
12137 current_range_comment_delimiters = row_comment_delimiters;
12138 current_range_rewrap_prefix = row_rewrap_prefix;
12139 }
12140 prev_row = row;
12141 }
12142
12143 ranges.push((
12144 language_settings.clone(),
12145 Point::new(current_range_start, 0)
12146 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12147 current_range_indent,
12148 current_range_comment_delimiters,
12149 current_range_rewrap_prefix,
12150 from_empty_selection,
12151 ));
12152
12153 ranges
12154 });
12155
12156 let mut edits = Vec::new();
12157 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12158
12159 for (
12160 language_settings,
12161 wrap_range,
12162 mut indent_size,
12163 comment_prefix,
12164 rewrap_prefix,
12165 from_empty_selection,
12166 ) in wrap_ranges
12167 {
12168 let mut start_row = wrap_range.start.row;
12169 let mut end_row = wrap_range.end.row;
12170
12171 // Skip selections that overlap with a range that has already been rewrapped.
12172 let selection_range = start_row..end_row;
12173 if rewrapped_row_ranges
12174 .iter()
12175 .any(|range| range.overlaps(&selection_range))
12176 {
12177 continue;
12178 }
12179
12180 let tab_size = language_settings.tab_size;
12181
12182 let (line_prefix, inside_comment) = match &comment_prefix {
12183 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12184 (Some(prefix.as_str()), true)
12185 }
12186 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12187 (Some(prefix.as_ref()), true)
12188 }
12189 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12190 start: _,
12191 end: _,
12192 prefix,
12193 tab_size,
12194 })) => {
12195 indent_size.len += tab_size;
12196 (Some(prefix.as_ref()), true)
12197 }
12198 None => (None, false),
12199 };
12200 let indent_prefix = indent_size.chars().collect::<String>();
12201 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12202
12203 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12204 RewrapBehavior::InComments => inside_comment,
12205 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12206 RewrapBehavior::Anywhere => true,
12207 };
12208
12209 let should_rewrap = options.override_language_settings
12210 || allow_rewrap_based_on_language
12211 || self.hard_wrap.is_some();
12212 if !should_rewrap {
12213 continue;
12214 }
12215
12216 if from_empty_selection {
12217 'expand_upwards: while start_row > 0 {
12218 let prev_row = start_row - 1;
12219 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12220 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12221 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12222 {
12223 start_row = prev_row;
12224 } else {
12225 break 'expand_upwards;
12226 }
12227 }
12228
12229 'expand_downwards: while end_row < buffer.max_point().row {
12230 let next_row = end_row + 1;
12231 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12232 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12233 && !buffer.is_line_blank(MultiBufferRow(next_row))
12234 {
12235 end_row = next_row;
12236 } else {
12237 break 'expand_downwards;
12238 }
12239 }
12240 }
12241
12242 let start = Point::new(start_row, 0);
12243 let start_offset = ToOffset::to_offset(&start, &buffer);
12244 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12245 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12246 let mut first_line_delimiter = None;
12247 let mut last_line_delimiter = None;
12248 let Some(lines_without_prefixes) = selection_text
12249 .lines()
12250 .enumerate()
12251 .map(|(ix, line)| {
12252 let line_trimmed = line.trim_start();
12253 if rewrap_prefix.is_some() && ix > 0 {
12254 Ok(line_trimmed)
12255 } else if let Some(
12256 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12257 start,
12258 prefix,
12259 end,
12260 tab_size,
12261 })
12262 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12263 start,
12264 prefix,
12265 end,
12266 tab_size,
12267 }),
12268 ) = &comment_prefix
12269 {
12270 let line_trimmed = line_trimmed
12271 .strip_prefix(start.as_ref())
12272 .map(|s| {
12273 let mut indent_size = indent_size;
12274 indent_size.len -= tab_size;
12275 let indent_prefix: String = indent_size.chars().collect();
12276 first_line_delimiter = Some((indent_prefix, start));
12277 s.trim_start()
12278 })
12279 .unwrap_or(line_trimmed);
12280 let line_trimmed = line_trimmed
12281 .strip_suffix(end.as_ref())
12282 .map(|s| {
12283 last_line_delimiter = Some(end);
12284 s.trim_end()
12285 })
12286 .unwrap_or(line_trimmed);
12287 let line_trimmed = line_trimmed
12288 .strip_prefix(prefix.as_ref())
12289 .unwrap_or(line_trimmed);
12290 Ok(line_trimmed)
12291 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12292 line_trimmed.strip_prefix(prefix).with_context(|| {
12293 format!("line did not start with prefix {prefix:?}: {line:?}")
12294 })
12295 } else {
12296 line_trimmed
12297 .strip_prefix(&line_prefix.trim_start())
12298 .with_context(|| {
12299 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12300 })
12301 }
12302 })
12303 .collect::<Result<Vec<_>, _>>()
12304 .log_err()
12305 else {
12306 continue;
12307 };
12308
12309 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12310 buffer
12311 .language_settings_at(Point::new(start_row, 0), cx)
12312 .preferred_line_length as usize
12313 });
12314
12315 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12316 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12317 } else {
12318 line_prefix.clone()
12319 };
12320
12321 let wrapped_text = {
12322 let mut wrapped_text = wrap_with_prefix(
12323 line_prefix,
12324 subsequent_lines_prefix,
12325 lines_without_prefixes.join("\n"),
12326 wrap_column,
12327 tab_size,
12328 options.preserve_existing_whitespace,
12329 );
12330
12331 if let Some((indent, delimiter)) = first_line_delimiter {
12332 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12333 }
12334 if let Some(last_line) = last_line_delimiter {
12335 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12336 }
12337
12338 wrapped_text
12339 };
12340
12341 // TODO: should always use char-based diff while still supporting cursor behavior that
12342 // matches vim.
12343 let mut diff_options = DiffOptions::default();
12344 if options.override_language_settings {
12345 diff_options.max_word_diff_len = 0;
12346 diff_options.max_word_diff_line_count = 0;
12347 } else {
12348 diff_options.max_word_diff_len = usize::MAX;
12349 diff_options.max_word_diff_line_count = usize::MAX;
12350 }
12351
12352 for (old_range, new_text) in
12353 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12354 {
12355 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12356 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12357 edits.push((edit_start..edit_end, new_text));
12358 }
12359
12360 rewrapped_row_ranges.push(start_row..=end_row);
12361 }
12362
12363 self.buffer
12364 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12365 }
12366
12367 pub fn cut_common(
12368 &mut self,
12369 cut_no_selection_line: bool,
12370 window: &mut Window,
12371 cx: &mut Context<Self>,
12372 ) -> ClipboardItem {
12373 let mut text = String::new();
12374 let buffer = self.buffer.read(cx).snapshot(cx);
12375 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12376 let mut clipboard_selections = Vec::with_capacity(selections.len());
12377 {
12378 let max_point = buffer.max_point();
12379 let mut is_first = true;
12380 for selection in &mut selections {
12381 let is_entire_line =
12382 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12383 if is_entire_line {
12384 selection.start = Point::new(selection.start.row, 0);
12385 if !selection.is_empty() && selection.end.column == 0 {
12386 selection.end = cmp::min(max_point, selection.end);
12387 } else {
12388 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12389 }
12390 selection.goal = SelectionGoal::None;
12391 }
12392 if is_first {
12393 is_first = false;
12394 } else {
12395 text += "\n";
12396 }
12397 let mut len = 0;
12398 for chunk in buffer.text_for_range(selection.start..selection.end) {
12399 text.push_str(chunk);
12400 len += chunk.len();
12401 }
12402 clipboard_selections.push(ClipboardSelection {
12403 len,
12404 is_entire_line,
12405 first_line_indent: buffer
12406 .indent_size_for_line(MultiBufferRow(selection.start.row))
12407 .len,
12408 });
12409 }
12410 }
12411
12412 self.transact(window, cx, |this, window, cx| {
12413 this.change_selections(Default::default(), window, cx, |s| {
12414 s.select(selections);
12415 });
12416 this.insert("", window, cx);
12417 });
12418 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12419 }
12420
12421 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12422 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12423 let item = self.cut_common(true, window, cx);
12424 cx.write_to_clipboard(item);
12425 }
12426
12427 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12428 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12429 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12430 s.move_with(|snapshot, sel| {
12431 if sel.is_empty() {
12432 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12433 }
12434 if sel.is_empty() {
12435 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12436 }
12437 });
12438 });
12439 let item = self.cut_common(false, window, cx);
12440 cx.set_global(KillRing(item))
12441 }
12442
12443 pub fn kill_ring_yank(
12444 &mut self,
12445 _: &KillRingYank,
12446 window: &mut Window,
12447 cx: &mut Context<Self>,
12448 ) {
12449 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12450 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12451 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12452 (kill_ring.text().to_string(), kill_ring.metadata_json())
12453 } else {
12454 return;
12455 }
12456 } else {
12457 return;
12458 };
12459 self.do_paste(&text, metadata, false, window, cx);
12460 }
12461
12462 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12463 self.do_copy(true, cx);
12464 }
12465
12466 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12467 self.do_copy(false, cx);
12468 }
12469
12470 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12471 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12472 let buffer = self.buffer.read(cx).read(cx);
12473 let mut text = String::new();
12474
12475 let mut clipboard_selections = Vec::with_capacity(selections.len());
12476 {
12477 let max_point = buffer.max_point();
12478 let mut is_first = true;
12479 for selection in &selections {
12480 let mut start = selection.start;
12481 let mut end = selection.end;
12482 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12483 let mut add_trailing_newline = false;
12484 if is_entire_line {
12485 start = Point::new(start.row, 0);
12486 let next_line_start = Point::new(end.row + 1, 0);
12487 if next_line_start <= max_point {
12488 end = next_line_start;
12489 } else {
12490 // We're on the last line without a trailing newline.
12491 // Copy to the end of the line and add a newline afterwards.
12492 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12493 add_trailing_newline = true;
12494 }
12495 }
12496
12497 let mut trimmed_selections = Vec::new();
12498 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12499 let row = MultiBufferRow(start.row);
12500 let first_indent = buffer.indent_size_for_line(row);
12501 if first_indent.len == 0 || start.column > first_indent.len {
12502 trimmed_selections.push(start..end);
12503 } else {
12504 trimmed_selections.push(
12505 Point::new(row.0, first_indent.len)
12506 ..Point::new(row.0, buffer.line_len(row)),
12507 );
12508 for row in start.row + 1..=end.row {
12509 let mut line_len = buffer.line_len(MultiBufferRow(row));
12510 if row == end.row {
12511 line_len = end.column;
12512 }
12513 if line_len == 0 {
12514 trimmed_selections
12515 .push(Point::new(row, 0)..Point::new(row, line_len));
12516 continue;
12517 }
12518 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12519 if row_indent_size.len >= first_indent.len {
12520 trimmed_selections.push(
12521 Point::new(row, first_indent.len)..Point::new(row, line_len),
12522 );
12523 } else {
12524 trimmed_selections.clear();
12525 trimmed_selections.push(start..end);
12526 break;
12527 }
12528 }
12529 }
12530 } else {
12531 trimmed_selections.push(start..end);
12532 }
12533
12534 for trimmed_range in trimmed_selections {
12535 if is_first {
12536 is_first = false;
12537 } else {
12538 text += "\n";
12539 }
12540 let mut len = 0;
12541 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12542 text.push_str(chunk);
12543 len += chunk.len();
12544 }
12545 if add_trailing_newline {
12546 text.push('\n');
12547 len += 1;
12548 }
12549 clipboard_selections.push(ClipboardSelection {
12550 len,
12551 is_entire_line,
12552 first_line_indent: buffer
12553 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12554 .len,
12555 });
12556 }
12557 }
12558 }
12559
12560 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12561 text,
12562 clipboard_selections,
12563 ));
12564 }
12565
12566 pub fn do_paste(
12567 &mut self,
12568 text: &String,
12569 clipboard_selections: Option<Vec<ClipboardSelection>>,
12570 handle_entire_lines: bool,
12571 window: &mut Window,
12572 cx: &mut Context<Self>,
12573 ) {
12574 if self.read_only(cx) {
12575 return;
12576 }
12577
12578 let clipboard_text = Cow::Borrowed(text.as_str());
12579
12580 self.transact(window, cx, |this, window, cx| {
12581 let had_active_edit_prediction = this.has_active_edit_prediction();
12582 let display_map = this.display_snapshot(cx);
12583 let old_selections = this.selections.all::<usize>(&display_map);
12584 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12585
12586 if let Some(mut clipboard_selections) = clipboard_selections {
12587 let all_selections_were_entire_line =
12588 clipboard_selections.iter().all(|s| s.is_entire_line);
12589 let first_selection_indent_column =
12590 clipboard_selections.first().map(|s| s.first_line_indent);
12591 if clipboard_selections.len() != old_selections.len() {
12592 clipboard_selections.drain(..);
12593 }
12594 let mut auto_indent_on_paste = true;
12595
12596 this.buffer.update(cx, |buffer, cx| {
12597 let snapshot = buffer.read(cx);
12598 auto_indent_on_paste = snapshot
12599 .language_settings_at(cursor_offset, cx)
12600 .auto_indent_on_paste;
12601
12602 let mut start_offset = 0;
12603 let mut edits = Vec::new();
12604 let mut original_indent_columns = Vec::new();
12605 for (ix, selection) in old_selections.iter().enumerate() {
12606 let to_insert;
12607 let entire_line;
12608 let original_indent_column;
12609 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12610 let end_offset = start_offset + clipboard_selection.len;
12611 to_insert = &clipboard_text[start_offset..end_offset];
12612 entire_line = clipboard_selection.is_entire_line;
12613 start_offset = end_offset + 1;
12614 original_indent_column = Some(clipboard_selection.first_line_indent);
12615 } else {
12616 to_insert = &*clipboard_text;
12617 entire_line = all_selections_were_entire_line;
12618 original_indent_column = first_selection_indent_column
12619 }
12620
12621 let (range, to_insert) =
12622 if selection.is_empty() && handle_entire_lines && entire_line {
12623 // If the corresponding selection was empty when this slice of the
12624 // clipboard text was written, then the entire line containing the
12625 // selection was copied. If this selection is also currently empty,
12626 // then paste the line before the current line of the buffer.
12627 let column = selection.start.to_point(&snapshot).column as usize;
12628 let line_start = selection.start - column;
12629 (line_start..line_start, Cow::Borrowed(to_insert))
12630 } else {
12631 let language = snapshot.language_at(selection.head());
12632 let range = selection.range();
12633 if let Some(language) = language
12634 && language.name() == "Markdown".into()
12635 {
12636 edit_for_markdown_paste(
12637 &snapshot,
12638 range,
12639 to_insert,
12640 url::Url::parse(to_insert).ok(),
12641 )
12642 } else {
12643 (range, Cow::Borrowed(to_insert))
12644 }
12645 };
12646
12647 edits.push((range, to_insert));
12648 original_indent_columns.push(original_indent_column);
12649 }
12650 drop(snapshot);
12651
12652 buffer.edit(
12653 edits,
12654 if auto_indent_on_paste {
12655 Some(AutoindentMode::Block {
12656 original_indent_columns,
12657 })
12658 } else {
12659 None
12660 },
12661 cx,
12662 );
12663 });
12664
12665 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12666 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12667 } else {
12668 let url = url::Url::parse(&clipboard_text).ok();
12669
12670 let auto_indent_mode = if !clipboard_text.is_empty() {
12671 Some(AutoindentMode::Block {
12672 original_indent_columns: Vec::new(),
12673 })
12674 } else {
12675 None
12676 };
12677
12678 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12679 let snapshot = buffer.snapshot(cx);
12680
12681 let anchors = old_selections
12682 .iter()
12683 .map(|s| {
12684 let anchor = snapshot.anchor_after(s.head());
12685 s.map(|_| anchor)
12686 })
12687 .collect::<Vec<_>>();
12688
12689 let mut edits = Vec::new();
12690
12691 for selection in old_selections.iter() {
12692 let language = snapshot.language_at(selection.head());
12693 let range = selection.range();
12694
12695 let (edit_range, edit_text) = if let Some(language) = language
12696 && language.name() == "Markdown".into()
12697 {
12698 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12699 } else {
12700 (range, clipboard_text.clone())
12701 };
12702
12703 edits.push((edit_range, edit_text));
12704 }
12705
12706 drop(snapshot);
12707 buffer.edit(edits, auto_indent_mode, cx);
12708
12709 anchors
12710 });
12711
12712 this.change_selections(Default::default(), window, cx, |s| {
12713 s.select_anchors(selection_anchors);
12714 });
12715 }
12716
12717 let trigger_in_words =
12718 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12719
12720 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12721 });
12722 }
12723
12724 pub fn diff_clipboard_with_selection(
12725 &mut self,
12726 _: &DiffClipboardWithSelection,
12727 window: &mut Window,
12728 cx: &mut Context<Self>,
12729 ) {
12730 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12731
12732 if selections.is_empty() {
12733 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12734 return;
12735 };
12736
12737 let clipboard_text = match cx.read_from_clipboard() {
12738 Some(item) => match item.entries().first() {
12739 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12740 _ => None,
12741 },
12742 None => None,
12743 };
12744
12745 let Some(clipboard_text) = clipboard_text else {
12746 log::warn!("Clipboard doesn't contain text.");
12747 return;
12748 };
12749
12750 window.dispatch_action(
12751 Box::new(DiffClipboardWithSelectionData {
12752 clipboard_text,
12753 editor: cx.entity(),
12754 }),
12755 cx,
12756 );
12757 }
12758
12759 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12760 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12761 if let Some(item) = cx.read_from_clipboard() {
12762 let entries = item.entries();
12763
12764 match entries.first() {
12765 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12766 // of all the pasted entries.
12767 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12768 .do_paste(
12769 clipboard_string.text(),
12770 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12771 true,
12772 window,
12773 cx,
12774 ),
12775 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12776 }
12777 }
12778 }
12779
12780 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12781 if self.read_only(cx) {
12782 return;
12783 }
12784
12785 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12786
12787 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12788 if let Some((selections, _)) =
12789 self.selection_history.transaction(transaction_id).cloned()
12790 {
12791 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12792 s.select_anchors(selections.to_vec());
12793 });
12794 } else {
12795 log::error!(
12796 "No entry in selection_history found for undo. \
12797 This may correspond to a bug where undo does not update the selection. \
12798 If this is occurring, please add details to \
12799 https://github.com/zed-industries/zed/issues/22692"
12800 );
12801 }
12802 self.request_autoscroll(Autoscroll::fit(), cx);
12803 self.unmark_text(window, cx);
12804 self.refresh_edit_prediction(true, false, window, cx);
12805 cx.emit(EditorEvent::Edited { transaction_id });
12806 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12807 }
12808 }
12809
12810 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12811 if self.read_only(cx) {
12812 return;
12813 }
12814
12815 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12816
12817 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12818 if let Some((_, Some(selections))) =
12819 self.selection_history.transaction(transaction_id).cloned()
12820 {
12821 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12822 s.select_anchors(selections.to_vec());
12823 });
12824 } else {
12825 log::error!(
12826 "No entry in selection_history found for redo. \
12827 This may correspond to a bug where undo does not update the selection. \
12828 If this is occurring, please add details to \
12829 https://github.com/zed-industries/zed/issues/22692"
12830 );
12831 }
12832 self.request_autoscroll(Autoscroll::fit(), cx);
12833 self.unmark_text(window, cx);
12834 self.refresh_edit_prediction(true, false, window, cx);
12835 cx.emit(EditorEvent::Edited { transaction_id });
12836 }
12837 }
12838
12839 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12840 self.buffer
12841 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12842 }
12843
12844 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12845 self.buffer
12846 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12847 }
12848
12849 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12850 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12851 self.change_selections(Default::default(), window, cx, |s| {
12852 s.move_with(|map, selection| {
12853 let cursor = if selection.is_empty() {
12854 movement::left(map, selection.start)
12855 } else {
12856 selection.start
12857 };
12858 selection.collapse_to(cursor, SelectionGoal::None);
12859 });
12860 })
12861 }
12862
12863 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12864 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12865 self.change_selections(Default::default(), window, cx, |s| {
12866 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12867 })
12868 }
12869
12870 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12871 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12872 self.change_selections(Default::default(), window, cx, |s| {
12873 s.move_with(|map, selection| {
12874 let cursor = if selection.is_empty() {
12875 movement::right(map, selection.end)
12876 } else {
12877 selection.end
12878 };
12879 selection.collapse_to(cursor, SelectionGoal::None)
12880 });
12881 })
12882 }
12883
12884 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12885 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12886 self.change_selections(Default::default(), window, cx, |s| {
12887 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12888 });
12889 }
12890
12891 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12892 if self.take_rename(true, window, cx).is_some() {
12893 return;
12894 }
12895
12896 if self.mode.is_single_line() {
12897 cx.propagate();
12898 return;
12899 }
12900
12901 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12902
12903 let text_layout_details = &self.text_layout_details(window);
12904 let selection_count = self.selections.count();
12905 let first_selection = self.selections.first_anchor();
12906
12907 self.change_selections(Default::default(), window, cx, |s| {
12908 s.move_with(|map, selection| {
12909 if !selection.is_empty() {
12910 selection.goal = SelectionGoal::None;
12911 }
12912 let (cursor, goal) = movement::up(
12913 map,
12914 selection.start,
12915 selection.goal,
12916 false,
12917 text_layout_details,
12918 );
12919 selection.collapse_to(cursor, goal);
12920 });
12921 });
12922
12923 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12924 {
12925 cx.propagate();
12926 }
12927 }
12928
12929 pub fn move_up_by_lines(
12930 &mut self,
12931 action: &MoveUpByLines,
12932 window: &mut Window,
12933 cx: &mut Context<Self>,
12934 ) {
12935 if self.take_rename(true, window, cx).is_some() {
12936 return;
12937 }
12938
12939 if self.mode.is_single_line() {
12940 cx.propagate();
12941 return;
12942 }
12943
12944 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12945
12946 let text_layout_details = &self.text_layout_details(window);
12947
12948 self.change_selections(Default::default(), window, cx, |s| {
12949 s.move_with(|map, selection| {
12950 if !selection.is_empty() {
12951 selection.goal = SelectionGoal::None;
12952 }
12953 let (cursor, goal) = movement::up_by_rows(
12954 map,
12955 selection.start,
12956 action.lines,
12957 selection.goal,
12958 false,
12959 text_layout_details,
12960 );
12961 selection.collapse_to(cursor, goal);
12962 });
12963 })
12964 }
12965
12966 pub fn move_down_by_lines(
12967 &mut self,
12968 action: &MoveDownByLines,
12969 window: &mut Window,
12970 cx: &mut Context<Self>,
12971 ) {
12972 if self.take_rename(true, window, cx).is_some() {
12973 return;
12974 }
12975
12976 if self.mode.is_single_line() {
12977 cx.propagate();
12978 return;
12979 }
12980
12981 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12982
12983 let text_layout_details = &self.text_layout_details(window);
12984
12985 self.change_selections(Default::default(), window, cx, |s| {
12986 s.move_with(|map, selection| {
12987 if !selection.is_empty() {
12988 selection.goal = SelectionGoal::None;
12989 }
12990 let (cursor, goal) = movement::down_by_rows(
12991 map,
12992 selection.start,
12993 action.lines,
12994 selection.goal,
12995 false,
12996 text_layout_details,
12997 );
12998 selection.collapse_to(cursor, goal);
12999 });
13000 })
13001 }
13002
13003 pub fn select_down_by_lines(
13004 &mut self,
13005 action: &SelectDownByLines,
13006 window: &mut Window,
13007 cx: &mut Context<Self>,
13008 ) {
13009 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13010 let text_layout_details = &self.text_layout_details(window);
13011 self.change_selections(Default::default(), window, cx, |s| {
13012 s.move_heads_with(|map, head, goal| {
13013 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13014 })
13015 })
13016 }
13017
13018 pub fn select_up_by_lines(
13019 &mut self,
13020 action: &SelectUpByLines,
13021 window: &mut Window,
13022 cx: &mut Context<Self>,
13023 ) {
13024 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13025 let text_layout_details = &self.text_layout_details(window);
13026 self.change_selections(Default::default(), window, cx, |s| {
13027 s.move_heads_with(|map, head, goal| {
13028 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13029 })
13030 })
13031 }
13032
13033 pub fn select_page_up(
13034 &mut self,
13035 _: &SelectPageUp,
13036 window: &mut Window,
13037 cx: &mut Context<Self>,
13038 ) {
13039 let Some(row_count) = self.visible_row_count() else {
13040 return;
13041 };
13042
13043 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13044
13045 let text_layout_details = &self.text_layout_details(window);
13046
13047 self.change_selections(Default::default(), window, cx, |s| {
13048 s.move_heads_with(|map, head, goal| {
13049 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13050 })
13051 })
13052 }
13053
13054 pub fn move_page_up(
13055 &mut self,
13056 action: &MovePageUp,
13057 window: &mut Window,
13058 cx: &mut Context<Self>,
13059 ) {
13060 if self.take_rename(true, window, cx).is_some() {
13061 return;
13062 }
13063
13064 if self
13065 .context_menu
13066 .borrow_mut()
13067 .as_mut()
13068 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13069 .unwrap_or(false)
13070 {
13071 return;
13072 }
13073
13074 if matches!(self.mode, EditorMode::SingleLine) {
13075 cx.propagate();
13076 return;
13077 }
13078
13079 let Some(row_count) = self.visible_row_count() else {
13080 return;
13081 };
13082
13083 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13084
13085 let effects = if action.center_cursor {
13086 SelectionEffects::scroll(Autoscroll::center())
13087 } else {
13088 SelectionEffects::default()
13089 };
13090
13091 let text_layout_details = &self.text_layout_details(window);
13092
13093 self.change_selections(effects, window, cx, |s| {
13094 s.move_with(|map, selection| {
13095 if !selection.is_empty() {
13096 selection.goal = SelectionGoal::None;
13097 }
13098 let (cursor, goal) = movement::up_by_rows(
13099 map,
13100 selection.end,
13101 row_count,
13102 selection.goal,
13103 false,
13104 text_layout_details,
13105 );
13106 selection.collapse_to(cursor, goal);
13107 });
13108 });
13109 }
13110
13111 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13112 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13113 let text_layout_details = &self.text_layout_details(window);
13114 self.change_selections(Default::default(), window, cx, |s| {
13115 s.move_heads_with(|map, head, goal| {
13116 movement::up(map, head, goal, false, text_layout_details)
13117 })
13118 })
13119 }
13120
13121 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13122 self.take_rename(true, window, cx);
13123
13124 if self.mode.is_single_line() {
13125 cx.propagate();
13126 return;
13127 }
13128
13129 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13130
13131 let text_layout_details = &self.text_layout_details(window);
13132 let selection_count = self.selections.count();
13133 let first_selection = self.selections.first_anchor();
13134
13135 self.change_selections(Default::default(), window, cx, |s| {
13136 s.move_with(|map, selection| {
13137 if !selection.is_empty() {
13138 selection.goal = SelectionGoal::None;
13139 }
13140 let (cursor, goal) = movement::down(
13141 map,
13142 selection.end,
13143 selection.goal,
13144 false,
13145 text_layout_details,
13146 );
13147 selection.collapse_to(cursor, goal);
13148 });
13149 });
13150
13151 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13152 {
13153 cx.propagate();
13154 }
13155 }
13156
13157 pub fn select_page_down(
13158 &mut self,
13159 _: &SelectPageDown,
13160 window: &mut Window,
13161 cx: &mut Context<Self>,
13162 ) {
13163 let Some(row_count) = self.visible_row_count() else {
13164 return;
13165 };
13166
13167 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13168
13169 let text_layout_details = &self.text_layout_details(window);
13170
13171 self.change_selections(Default::default(), window, cx, |s| {
13172 s.move_heads_with(|map, head, goal| {
13173 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13174 })
13175 })
13176 }
13177
13178 pub fn move_page_down(
13179 &mut self,
13180 action: &MovePageDown,
13181 window: &mut Window,
13182 cx: &mut Context<Self>,
13183 ) {
13184 if self.take_rename(true, window, cx).is_some() {
13185 return;
13186 }
13187
13188 if self
13189 .context_menu
13190 .borrow_mut()
13191 .as_mut()
13192 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13193 .unwrap_or(false)
13194 {
13195 return;
13196 }
13197
13198 if matches!(self.mode, EditorMode::SingleLine) {
13199 cx.propagate();
13200 return;
13201 }
13202
13203 let Some(row_count) = self.visible_row_count() else {
13204 return;
13205 };
13206
13207 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13208
13209 let effects = if action.center_cursor {
13210 SelectionEffects::scroll(Autoscroll::center())
13211 } else {
13212 SelectionEffects::default()
13213 };
13214
13215 let text_layout_details = &self.text_layout_details(window);
13216 self.change_selections(effects, window, cx, |s| {
13217 s.move_with(|map, selection| {
13218 if !selection.is_empty() {
13219 selection.goal = SelectionGoal::None;
13220 }
13221 let (cursor, goal) = movement::down_by_rows(
13222 map,
13223 selection.end,
13224 row_count,
13225 selection.goal,
13226 false,
13227 text_layout_details,
13228 );
13229 selection.collapse_to(cursor, goal);
13230 });
13231 });
13232 }
13233
13234 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13235 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13236 let text_layout_details = &self.text_layout_details(window);
13237 self.change_selections(Default::default(), window, cx, |s| {
13238 s.move_heads_with(|map, head, goal| {
13239 movement::down(map, head, goal, false, text_layout_details)
13240 })
13241 });
13242 }
13243
13244 pub fn context_menu_first(
13245 &mut self,
13246 _: &ContextMenuFirst,
13247 window: &mut Window,
13248 cx: &mut Context<Self>,
13249 ) {
13250 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13251 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13252 }
13253 }
13254
13255 pub fn context_menu_prev(
13256 &mut self,
13257 _: &ContextMenuPrevious,
13258 window: &mut Window,
13259 cx: &mut Context<Self>,
13260 ) {
13261 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13262 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13263 }
13264 }
13265
13266 pub fn context_menu_next(
13267 &mut self,
13268 _: &ContextMenuNext,
13269 window: &mut Window,
13270 cx: &mut Context<Self>,
13271 ) {
13272 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13273 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13274 }
13275 }
13276
13277 pub fn context_menu_last(
13278 &mut self,
13279 _: &ContextMenuLast,
13280 window: &mut Window,
13281 cx: &mut Context<Self>,
13282 ) {
13283 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13284 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13285 }
13286 }
13287
13288 pub fn signature_help_prev(
13289 &mut self,
13290 _: &SignatureHelpPrevious,
13291 _: &mut Window,
13292 cx: &mut Context<Self>,
13293 ) {
13294 if let Some(popover) = self.signature_help_state.popover_mut() {
13295 if popover.current_signature == 0 {
13296 popover.current_signature = popover.signatures.len() - 1;
13297 } else {
13298 popover.current_signature -= 1;
13299 }
13300 cx.notify();
13301 }
13302 }
13303
13304 pub fn signature_help_next(
13305 &mut self,
13306 _: &SignatureHelpNext,
13307 _: &mut Window,
13308 cx: &mut Context<Self>,
13309 ) {
13310 if let Some(popover) = self.signature_help_state.popover_mut() {
13311 if popover.current_signature + 1 == popover.signatures.len() {
13312 popover.current_signature = 0;
13313 } else {
13314 popover.current_signature += 1;
13315 }
13316 cx.notify();
13317 }
13318 }
13319
13320 pub fn move_to_previous_word_start(
13321 &mut self,
13322 _: &MoveToPreviousWordStart,
13323 window: &mut Window,
13324 cx: &mut Context<Self>,
13325 ) {
13326 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13327 self.change_selections(Default::default(), window, cx, |s| {
13328 s.move_cursors_with(|map, head, _| {
13329 (
13330 movement::previous_word_start(map, head),
13331 SelectionGoal::None,
13332 )
13333 });
13334 })
13335 }
13336
13337 pub fn move_to_previous_subword_start(
13338 &mut self,
13339 _: &MoveToPreviousSubwordStart,
13340 window: &mut Window,
13341 cx: &mut Context<Self>,
13342 ) {
13343 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13344 self.change_selections(Default::default(), window, cx, |s| {
13345 s.move_cursors_with(|map, head, _| {
13346 (
13347 movement::previous_subword_start(map, head),
13348 SelectionGoal::None,
13349 )
13350 });
13351 })
13352 }
13353
13354 pub fn select_to_previous_word_start(
13355 &mut self,
13356 _: &SelectToPreviousWordStart,
13357 window: &mut Window,
13358 cx: &mut Context<Self>,
13359 ) {
13360 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13361 self.change_selections(Default::default(), window, cx, |s| {
13362 s.move_heads_with(|map, head, _| {
13363 (
13364 movement::previous_word_start(map, head),
13365 SelectionGoal::None,
13366 )
13367 });
13368 })
13369 }
13370
13371 pub fn select_to_previous_subword_start(
13372 &mut self,
13373 _: &SelectToPreviousSubwordStart,
13374 window: &mut Window,
13375 cx: &mut Context<Self>,
13376 ) {
13377 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13378 self.change_selections(Default::default(), window, cx, |s| {
13379 s.move_heads_with(|map, head, _| {
13380 (
13381 movement::previous_subword_start(map, head),
13382 SelectionGoal::None,
13383 )
13384 });
13385 })
13386 }
13387
13388 pub fn delete_to_previous_word_start(
13389 &mut self,
13390 action: &DeleteToPreviousWordStart,
13391 window: &mut Window,
13392 cx: &mut Context<Self>,
13393 ) {
13394 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13395 self.transact(window, cx, |this, window, cx| {
13396 this.select_autoclose_pair(window, cx);
13397 this.change_selections(Default::default(), window, cx, |s| {
13398 s.move_with(|map, selection| {
13399 if selection.is_empty() {
13400 let mut cursor = if action.ignore_newlines {
13401 movement::previous_word_start(map, selection.head())
13402 } else {
13403 movement::previous_word_start_or_newline(map, selection.head())
13404 };
13405 cursor = movement::adjust_greedy_deletion(
13406 map,
13407 selection.head(),
13408 cursor,
13409 action.ignore_brackets,
13410 );
13411 selection.set_head(cursor, SelectionGoal::None);
13412 }
13413 });
13414 });
13415 this.insert("", window, cx);
13416 });
13417 }
13418
13419 pub fn delete_to_previous_subword_start(
13420 &mut self,
13421 _: &DeleteToPreviousSubwordStart,
13422 window: &mut Window,
13423 cx: &mut Context<Self>,
13424 ) {
13425 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13426 self.transact(window, cx, |this, window, cx| {
13427 this.select_autoclose_pair(window, cx);
13428 this.change_selections(Default::default(), window, cx, |s| {
13429 s.move_with(|map, selection| {
13430 if selection.is_empty() {
13431 let mut cursor = movement::previous_subword_start(map, selection.head());
13432 cursor =
13433 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13434 selection.set_head(cursor, SelectionGoal::None);
13435 }
13436 });
13437 });
13438 this.insert("", window, cx);
13439 });
13440 }
13441
13442 pub fn move_to_next_word_end(
13443 &mut self,
13444 _: &MoveToNextWordEnd,
13445 window: &mut Window,
13446 cx: &mut Context<Self>,
13447 ) {
13448 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13449 self.change_selections(Default::default(), window, cx, |s| {
13450 s.move_cursors_with(|map, head, _| {
13451 (movement::next_word_end(map, head), SelectionGoal::None)
13452 });
13453 })
13454 }
13455
13456 pub fn move_to_next_subword_end(
13457 &mut self,
13458 _: &MoveToNextSubwordEnd,
13459 window: &mut Window,
13460 cx: &mut Context<Self>,
13461 ) {
13462 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13463 self.change_selections(Default::default(), window, cx, |s| {
13464 s.move_cursors_with(|map, head, _| {
13465 (movement::next_subword_end(map, head), SelectionGoal::None)
13466 });
13467 })
13468 }
13469
13470 pub fn select_to_next_word_end(
13471 &mut self,
13472 _: &SelectToNextWordEnd,
13473 window: &mut Window,
13474 cx: &mut Context<Self>,
13475 ) {
13476 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13477 self.change_selections(Default::default(), window, cx, |s| {
13478 s.move_heads_with(|map, head, _| {
13479 (movement::next_word_end(map, head), SelectionGoal::None)
13480 });
13481 })
13482 }
13483
13484 pub fn select_to_next_subword_end(
13485 &mut self,
13486 _: &SelectToNextSubwordEnd,
13487 window: &mut Window,
13488 cx: &mut Context<Self>,
13489 ) {
13490 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13491 self.change_selections(Default::default(), window, cx, |s| {
13492 s.move_heads_with(|map, head, _| {
13493 (movement::next_subword_end(map, head), SelectionGoal::None)
13494 });
13495 })
13496 }
13497
13498 pub fn delete_to_next_word_end(
13499 &mut self,
13500 action: &DeleteToNextWordEnd,
13501 window: &mut Window,
13502 cx: &mut Context<Self>,
13503 ) {
13504 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13505 self.transact(window, cx, |this, window, cx| {
13506 this.change_selections(Default::default(), window, cx, |s| {
13507 s.move_with(|map, selection| {
13508 if selection.is_empty() {
13509 let mut cursor = if action.ignore_newlines {
13510 movement::next_word_end(map, selection.head())
13511 } else {
13512 movement::next_word_end_or_newline(map, selection.head())
13513 };
13514 cursor = movement::adjust_greedy_deletion(
13515 map,
13516 selection.head(),
13517 cursor,
13518 action.ignore_brackets,
13519 );
13520 selection.set_head(cursor, SelectionGoal::None);
13521 }
13522 });
13523 });
13524 this.insert("", window, cx);
13525 });
13526 }
13527
13528 pub fn delete_to_next_subword_end(
13529 &mut self,
13530 _: &DeleteToNextSubwordEnd,
13531 window: &mut Window,
13532 cx: &mut Context<Self>,
13533 ) {
13534 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13535 self.transact(window, cx, |this, window, cx| {
13536 this.change_selections(Default::default(), window, cx, |s| {
13537 s.move_with(|map, selection| {
13538 if selection.is_empty() {
13539 let mut cursor = movement::next_subword_end(map, selection.head());
13540 cursor =
13541 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13542 selection.set_head(cursor, SelectionGoal::None);
13543 }
13544 });
13545 });
13546 this.insert("", window, cx);
13547 });
13548 }
13549
13550 pub fn move_to_beginning_of_line(
13551 &mut self,
13552 action: &MoveToBeginningOfLine,
13553 window: &mut Window,
13554 cx: &mut Context<Self>,
13555 ) {
13556 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13557 self.change_selections(Default::default(), window, cx, |s| {
13558 s.move_cursors_with(|map, head, _| {
13559 (
13560 movement::indented_line_beginning(
13561 map,
13562 head,
13563 action.stop_at_soft_wraps,
13564 action.stop_at_indent,
13565 ),
13566 SelectionGoal::None,
13567 )
13568 });
13569 })
13570 }
13571
13572 pub fn select_to_beginning_of_line(
13573 &mut self,
13574 action: &SelectToBeginningOfLine,
13575 window: &mut Window,
13576 cx: &mut Context<Self>,
13577 ) {
13578 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13579 self.change_selections(Default::default(), window, cx, |s| {
13580 s.move_heads_with(|map, head, _| {
13581 (
13582 movement::indented_line_beginning(
13583 map,
13584 head,
13585 action.stop_at_soft_wraps,
13586 action.stop_at_indent,
13587 ),
13588 SelectionGoal::None,
13589 )
13590 });
13591 });
13592 }
13593
13594 pub fn delete_to_beginning_of_line(
13595 &mut self,
13596 action: &DeleteToBeginningOfLine,
13597 window: &mut Window,
13598 cx: &mut Context<Self>,
13599 ) {
13600 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13601 self.transact(window, cx, |this, window, cx| {
13602 this.change_selections(Default::default(), window, cx, |s| {
13603 s.move_with(|_, selection| {
13604 selection.reversed = true;
13605 });
13606 });
13607
13608 this.select_to_beginning_of_line(
13609 &SelectToBeginningOfLine {
13610 stop_at_soft_wraps: false,
13611 stop_at_indent: action.stop_at_indent,
13612 },
13613 window,
13614 cx,
13615 );
13616 this.backspace(&Backspace, window, cx);
13617 });
13618 }
13619
13620 pub fn move_to_end_of_line(
13621 &mut self,
13622 action: &MoveToEndOfLine,
13623 window: &mut Window,
13624 cx: &mut Context<Self>,
13625 ) {
13626 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13627 self.change_selections(Default::default(), window, cx, |s| {
13628 s.move_cursors_with(|map, head, _| {
13629 (
13630 movement::line_end(map, head, action.stop_at_soft_wraps),
13631 SelectionGoal::None,
13632 )
13633 });
13634 })
13635 }
13636
13637 pub fn select_to_end_of_line(
13638 &mut self,
13639 action: &SelectToEndOfLine,
13640 window: &mut Window,
13641 cx: &mut Context<Self>,
13642 ) {
13643 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13644 self.change_selections(Default::default(), window, cx, |s| {
13645 s.move_heads_with(|map, head, _| {
13646 (
13647 movement::line_end(map, head, action.stop_at_soft_wraps),
13648 SelectionGoal::None,
13649 )
13650 });
13651 })
13652 }
13653
13654 pub fn delete_to_end_of_line(
13655 &mut self,
13656 _: &DeleteToEndOfLine,
13657 window: &mut Window,
13658 cx: &mut Context<Self>,
13659 ) {
13660 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13661 self.transact(window, cx, |this, window, cx| {
13662 this.select_to_end_of_line(
13663 &SelectToEndOfLine {
13664 stop_at_soft_wraps: false,
13665 },
13666 window,
13667 cx,
13668 );
13669 this.delete(&Delete, window, cx);
13670 });
13671 }
13672
13673 pub fn cut_to_end_of_line(
13674 &mut self,
13675 action: &CutToEndOfLine,
13676 window: &mut Window,
13677 cx: &mut Context<Self>,
13678 ) {
13679 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13680 self.transact(window, cx, |this, window, cx| {
13681 this.select_to_end_of_line(
13682 &SelectToEndOfLine {
13683 stop_at_soft_wraps: false,
13684 },
13685 window,
13686 cx,
13687 );
13688 if !action.stop_at_newlines {
13689 this.change_selections(Default::default(), window, cx, |s| {
13690 s.move_with(|_, sel| {
13691 if sel.is_empty() {
13692 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13693 }
13694 });
13695 });
13696 }
13697 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13698 let item = this.cut_common(false, window, cx);
13699 cx.write_to_clipboard(item);
13700 });
13701 }
13702
13703 pub fn move_to_start_of_paragraph(
13704 &mut self,
13705 _: &MoveToStartOfParagraph,
13706 window: &mut Window,
13707 cx: &mut Context<Self>,
13708 ) {
13709 if matches!(self.mode, EditorMode::SingleLine) {
13710 cx.propagate();
13711 return;
13712 }
13713 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13714 self.change_selections(Default::default(), window, cx, |s| {
13715 s.move_with(|map, selection| {
13716 selection.collapse_to(
13717 movement::start_of_paragraph(map, selection.head(), 1),
13718 SelectionGoal::None,
13719 )
13720 });
13721 })
13722 }
13723
13724 pub fn move_to_end_of_paragraph(
13725 &mut self,
13726 _: &MoveToEndOfParagraph,
13727 window: &mut Window,
13728 cx: &mut Context<Self>,
13729 ) {
13730 if matches!(self.mode, EditorMode::SingleLine) {
13731 cx.propagate();
13732 return;
13733 }
13734 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13735 self.change_selections(Default::default(), window, cx, |s| {
13736 s.move_with(|map, selection| {
13737 selection.collapse_to(
13738 movement::end_of_paragraph(map, selection.head(), 1),
13739 SelectionGoal::None,
13740 )
13741 });
13742 })
13743 }
13744
13745 pub fn select_to_start_of_paragraph(
13746 &mut self,
13747 _: &SelectToStartOfParagraph,
13748 window: &mut Window,
13749 cx: &mut Context<Self>,
13750 ) {
13751 if matches!(self.mode, EditorMode::SingleLine) {
13752 cx.propagate();
13753 return;
13754 }
13755 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13756 self.change_selections(Default::default(), window, cx, |s| {
13757 s.move_heads_with(|map, head, _| {
13758 (
13759 movement::start_of_paragraph(map, head, 1),
13760 SelectionGoal::None,
13761 )
13762 });
13763 })
13764 }
13765
13766 pub fn select_to_end_of_paragraph(
13767 &mut self,
13768 _: &SelectToEndOfParagraph,
13769 window: &mut Window,
13770 cx: &mut Context<Self>,
13771 ) {
13772 if matches!(self.mode, EditorMode::SingleLine) {
13773 cx.propagate();
13774 return;
13775 }
13776 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13777 self.change_selections(Default::default(), window, cx, |s| {
13778 s.move_heads_with(|map, head, _| {
13779 (
13780 movement::end_of_paragraph(map, head, 1),
13781 SelectionGoal::None,
13782 )
13783 });
13784 })
13785 }
13786
13787 pub fn move_to_start_of_excerpt(
13788 &mut self,
13789 _: &MoveToStartOfExcerpt,
13790 window: &mut Window,
13791 cx: &mut Context<Self>,
13792 ) {
13793 if matches!(self.mode, EditorMode::SingleLine) {
13794 cx.propagate();
13795 return;
13796 }
13797 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13798 self.change_selections(Default::default(), window, cx, |s| {
13799 s.move_with(|map, selection| {
13800 selection.collapse_to(
13801 movement::start_of_excerpt(
13802 map,
13803 selection.head(),
13804 workspace::searchable::Direction::Prev,
13805 ),
13806 SelectionGoal::None,
13807 )
13808 });
13809 })
13810 }
13811
13812 pub fn move_to_start_of_next_excerpt(
13813 &mut self,
13814 _: &MoveToStartOfNextExcerpt,
13815 window: &mut Window,
13816 cx: &mut Context<Self>,
13817 ) {
13818 if matches!(self.mode, EditorMode::SingleLine) {
13819 cx.propagate();
13820 return;
13821 }
13822
13823 self.change_selections(Default::default(), window, cx, |s| {
13824 s.move_with(|map, selection| {
13825 selection.collapse_to(
13826 movement::start_of_excerpt(
13827 map,
13828 selection.head(),
13829 workspace::searchable::Direction::Next,
13830 ),
13831 SelectionGoal::None,
13832 )
13833 });
13834 })
13835 }
13836
13837 pub fn move_to_end_of_excerpt(
13838 &mut self,
13839 _: &MoveToEndOfExcerpt,
13840 window: &mut Window,
13841 cx: &mut Context<Self>,
13842 ) {
13843 if matches!(self.mode, EditorMode::SingleLine) {
13844 cx.propagate();
13845 return;
13846 }
13847 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13848 self.change_selections(Default::default(), window, cx, |s| {
13849 s.move_with(|map, selection| {
13850 selection.collapse_to(
13851 movement::end_of_excerpt(
13852 map,
13853 selection.head(),
13854 workspace::searchable::Direction::Next,
13855 ),
13856 SelectionGoal::None,
13857 )
13858 });
13859 })
13860 }
13861
13862 pub fn move_to_end_of_previous_excerpt(
13863 &mut self,
13864 _: &MoveToEndOfPreviousExcerpt,
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_excerpt(
13877 map,
13878 selection.head(),
13879 workspace::searchable::Direction::Prev,
13880 ),
13881 SelectionGoal::None,
13882 )
13883 });
13884 })
13885 }
13886
13887 pub fn select_to_start_of_excerpt(
13888 &mut self,
13889 _: &SelectToStartOfExcerpt,
13890 window: &mut Window,
13891 cx: &mut Context<Self>,
13892 ) {
13893 if matches!(self.mode, EditorMode::SingleLine) {
13894 cx.propagate();
13895 return;
13896 }
13897 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13898 self.change_selections(Default::default(), window, cx, |s| {
13899 s.move_heads_with(|map, head, _| {
13900 (
13901 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13902 SelectionGoal::None,
13903 )
13904 });
13905 })
13906 }
13907
13908 pub fn select_to_start_of_next_excerpt(
13909 &mut self,
13910 _: &SelectToStartOfNextExcerpt,
13911 window: &mut Window,
13912 cx: &mut Context<Self>,
13913 ) {
13914 if matches!(self.mode, EditorMode::SingleLine) {
13915 cx.propagate();
13916 return;
13917 }
13918 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13919 self.change_selections(Default::default(), window, cx, |s| {
13920 s.move_heads_with(|map, head, _| {
13921 (
13922 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13923 SelectionGoal::None,
13924 )
13925 });
13926 })
13927 }
13928
13929 pub fn select_to_end_of_excerpt(
13930 &mut self,
13931 _: &SelectToEndOfExcerpt,
13932 window: &mut Window,
13933 cx: &mut Context<Self>,
13934 ) {
13935 if matches!(self.mode, EditorMode::SingleLine) {
13936 cx.propagate();
13937 return;
13938 }
13939 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13940 self.change_selections(Default::default(), window, cx, |s| {
13941 s.move_heads_with(|map, head, _| {
13942 (
13943 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13944 SelectionGoal::None,
13945 )
13946 });
13947 })
13948 }
13949
13950 pub fn select_to_end_of_previous_excerpt(
13951 &mut self,
13952 _: &SelectToEndOfPreviousExcerpt,
13953 window: &mut Window,
13954 cx: &mut Context<Self>,
13955 ) {
13956 if matches!(self.mode, EditorMode::SingleLine) {
13957 cx.propagate();
13958 return;
13959 }
13960 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13961 self.change_selections(Default::default(), window, cx, |s| {
13962 s.move_heads_with(|map, head, _| {
13963 (
13964 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13965 SelectionGoal::None,
13966 )
13967 });
13968 })
13969 }
13970
13971 pub fn move_to_beginning(
13972 &mut self,
13973 _: &MoveToBeginning,
13974 window: &mut Window,
13975 cx: &mut Context<Self>,
13976 ) {
13977 if matches!(self.mode, EditorMode::SingleLine) {
13978 cx.propagate();
13979 return;
13980 }
13981 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13982 self.change_selections(Default::default(), window, cx, |s| {
13983 s.select_ranges(vec![0..0]);
13984 });
13985 }
13986
13987 pub fn select_to_beginning(
13988 &mut self,
13989 _: &SelectToBeginning,
13990 window: &mut Window,
13991 cx: &mut Context<Self>,
13992 ) {
13993 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
13994 selection.set_head(Point::zero(), SelectionGoal::None);
13995 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13996 self.change_selections(Default::default(), window, cx, |s| {
13997 s.select(vec![selection]);
13998 });
13999 }
14000
14001 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14002 if matches!(self.mode, EditorMode::SingleLine) {
14003 cx.propagate();
14004 return;
14005 }
14006 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14007 let cursor = self.buffer.read(cx).read(cx).len();
14008 self.change_selections(Default::default(), window, cx, |s| {
14009 s.select_ranges(vec![cursor..cursor])
14010 });
14011 }
14012
14013 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14014 self.nav_history = nav_history;
14015 }
14016
14017 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14018 self.nav_history.as_ref()
14019 }
14020
14021 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14022 self.push_to_nav_history(
14023 self.selections.newest_anchor().head(),
14024 None,
14025 false,
14026 true,
14027 cx,
14028 );
14029 }
14030
14031 fn push_to_nav_history(
14032 &mut self,
14033 cursor_anchor: Anchor,
14034 new_position: Option<Point>,
14035 is_deactivate: bool,
14036 always: bool,
14037 cx: &mut Context<Self>,
14038 ) {
14039 if let Some(nav_history) = self.nav_history.as_mut() {
14040 let buffer = self.buffer.read(cx).read(cx);
14041 let cursor_position = cursor_anchor.to_point(&buffer);
14042 let scroll_state = self.scroll_manager.anchor();
14043 let scroll_top_row = scroll_state.top_row(&buffer);
14044 drop(buffer);
14045
14046 if let Some(new_position) = new_position {
14047 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14048 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14049 return;
14050 }
14051 }
14052
14053 nav_history.push(
14054 Some(NavigationData {
14055 cursor_anchor,
14056 cursor_position,
14057 scroll_anchor: scroll_state,
14058 scroll_top_row,
14059 }),
14060 cx,
14061 );
14062 cx.emit(EditorEvent::PushedToNavHistory {
14063 anchor: cursor_anchor,
14064 is_deactivate,
14065 })
14066 }
14067 }
14068
14069 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14070 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14071 let buffer = self.buffer.read(cx).snapshot(cx);
14072 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14073 selection.set_head(buffer.len(), SelectionGoal::None);
14074 self.change_selections(Default::default(), window, cx, |s| {
14075 s.select(vec![selection]);
14076 });
14077 }
14078
14079 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14080 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14081 let end = self.buffer.read(cx).read(cx).len();
14082 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14083 s.select_ranges(vec![0..end]);
14084 });
14085 }
14086
14087 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14088 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14089 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14090 let mut selections = self.selections.all::<Point>(&display_map);
14091 let max_point = display_map.buffer_snapshot().max_point();
14092 for selection in &mut selections {
14093 let rows = selection.spanned_rows(true, &display_map);
14094 selection.start = Point::new(rows.start.0, 0);
14095 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14096 selection.reversed = false;
14097 }
14098 self.change_selections(Default::default(), window, cx, |s| {
14099 s.select(selections);
14100 });
14101 }
14102
14103 pub fn split_selection_into_lines(
14104 &mut self,
14105 action: &SplitSelectionIntoLines,
14106 window: &mut Window,
14107 cx: &mut Context<Self>,
14108 ) {
14109 let selections = self
14110 .selections
14111 .all::<Point>(&self.display_snapshot(cx))
14112 .into_iter()
14113 .map(|selection| selection.start..selection.end)
14114 .collect::<Vec<_>>();
14115 self.unfold_ranges(&selections, true, true, cx);
14116
14117 let mut new_selection_ranges = Vec::new();
14118 {
14119 let buffer = self.buffer.read(cx).read(cx);
14120 for selection in selections {
14121 for row in selection.start.row..selection.end.row {
14122 let line_start = Point::new(row, 0);
14123 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14124
14125 if action.keep_selections {
14126 // Keep the selection range for each line
14127 let selection_start = if row == selection.start.row {
14128 selection.start
14129 } else {
14130 line_start
14131 };
14132 new_selection_ranges.push(selection_start..line_end);
14133 } else {
14134 // Collapse to cursor at end of line
14135 new_selection_ranges.push(line_end..line_end);
14136 }
14137 }
14138
14139 let is_multiline_selection = selection.start.row != selection.end.row;
14140 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14141 // so this action feels more ergonomic when paired with other selection operations
14142 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14143 if !should_skip_last {
14144 if action.keep_selections {
14145 if is_multiline_selection {
14146 let line_start = Point::new(selection.end.row, 0);
14147 new_selection_ranges.push(line_start..selection.end);
14148 } else {
14149 new_selection_ranges.push(selection.start..selection.end);
14150 }
14151 } else {
14152 new_selection_ranges.push(selection.end..selection.end);
14153 }
14154 }
14155 }
14156 }
14157 self.change_selections(Default::default(), window, cx, |s| {
14158 s.select_ranges(new_selection_ranges);
14159 });
14160 }
14161
14162 pub fn add_selection_above(
14163 &mut self,
14164 action: &AddSelectionAbove,
14165 window: &mut Window,
14166 cx: &mut Context<Self>,
14167 ) {
14168 self.add_selection(true, action.skip_soft_wrap, window, cx);
14169 }
14170
14171 pub fn add_selection_below(
14172 &mut self,
14173 action: &AddSelectionBelow,
14174 window: &mut Window,
14175 cx: &mut Context<Self>,
14176 ) {
14177 self.add_selection(false, action.skip_soft_wrap, window, cx);
14178 }
14179
14180 fn add_selection(
14181 &mut self,
14182 above: bool,
14183 skip_soft_wrap: bool,
14184 window: &mut Window,
14185 cx: &mut Context<Self>,
14186 ) {
14187 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14188
14189 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14190 let all_selections = self.selections.all::<Point>(&display_map);
14191 let text_layout_details = self.text_layout_details(window);
14192
14193 let (mut columnar_selections, new_selections_to_columnarize) = {
14194 if let Some(state) = self.add_selections_state.as_ref() {
14195 let columnar_selection_ids: HashSet<_> = state
14196 .groups
14197 .iter()
14198 .flat_map(|group| group.stack.iter())
14199 .copied()
14200 .collect();
14201
14202 all_selections
14203 .into_iter()
14204 .partition(|s| columnar_selection_ids.contains(&s.id))
14205 } else {
14206 (Vec::new(), all_selections)
14207 }
14208 };
14209
14210 let mut state = self
14211 .add_selections_state
14212 .take()
14213 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14214
14215 for selection in new_selections_to_columnarize {
14216 let range = selection.display_range(&display_map).sorted();
14217 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14218 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14219 let positions = start_x.min(end_x)..start_x.max(end_x);
14220 let mut stack = Vec::new();
14221 for row in range.start.row().0..=range.end.row().0 {
14222 if let Some(selection) = self.selections.build_columnar_selection(
14223 &display_map,
14224 DisplayRow(row),
14225 &positions,
14226 selection.reversed,
14227 &text_layout_details,
14228 ) {
14229 stack.push(selection.id);
14230 columnar_selections.push(selection);
14231 }
14232 }
14233 if !stack.is_empty() {
14234 if above {
14235 stack.reverse();
14236 }
14237 state.groups.push(AddSelectionsGroup { above, stack });
14238 }
14239 }
14240
14241 let mut final_selections = Vec::new();
14242 let end_row = if above {
14243 DisplayRow(0)
14244 } else {
14245 display_map.max_point().row()
14246 };
14247
14248 let mut last_added_item_per_group = HashMap::default();
14249 for group in state.groups.iter_mut() {
14250 if let Some(last_id) = group.stack.last() {
14251 last_added_item_per_group.insert(*last_id, group);
14252 }
14253 }
14254
14255 for selection in columnar_selections {
14256 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14257 if above == group.above {
14258 let range = selection.display_range(&display_map).sorted();
14259 debug_assert_eq!(range.start.row(), range.end.row());
14260 let mut row = range.start.row();
14261 let positions =
14262 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14263 Pixels::from(start)..Pixels::from(end)
14264 } else {
14265 let start_x =
14266 display_map.x_for_display_point(range.start, &text_layout_details);
14267 let end_x =
14268 display_map.x_for_display_point(range.end, &text_layout_details);
14269 start_x.min(end_x)..start_x.max(end_x)
14270 };
14271
14272 let mut maybe_new_selection = None;
14273 let direction = if above { -1 } else { 1 };
14274
14275 while row != end_row {
14276 if skip_soft_wrap {
14277 row = display_map
14278 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14279 .row();
14280 } else if above {
14281 row.0 -= 1;
14282 } else {
14283 row.0 += 1;
14284 }
14285
14286 if let Some(new_selection) = self.selections.build_columnar_selection(
14287 &display_map,
14288 row,
14289 &positions,
14290 selection.reversed,
14291 &text_layout_details,
14292 ) {
14293 maybe_new_selection = Some(new_selection);
14294 break;
14295 }
14296 }
14297
14298 if let Some(new_selection) = maybe_new_selection {
14299 group.stack.push(new_selection.id);
14300 if above {
14301 final_selections.push(new_selection);
14302 final_selections.push(selection);
14303 } else {
14304 final_selections.push(selection);
14305 final_selections.push(new_selection);
14306 }
14307 } else {
14308 final_selections.push(selection);
14309 }
14310 } else {
14311 group.stack.pop();
14312 }
14313 } else {
14314 final_selections.push(selection);
14315 }
14316 }
14317
14318 self.change_selections(Default::default(), window, cx, |s| {
14319 s.select(final_selections);
14320 });
14321
14322 let final_selection_ids: HashSet<_> = self
14323 .selections
14324 .all::<Point>(&display_map)
14325 .iter()
14326 .map(|s| s.id)
14327 .collect();
14328 state.groups.retain_mut(|group| {
14329 // selections might get merged above so we remove invalid items from stacks
14330 group.stack.retain(|id| final_selection_ids.contains(id));
14331
14332 // single selection in stack can be treated as initial state
14333 group.stack.len() > 1
14334 });
14335
14336 if !state.groups.is_empty() {
14337 self.add_selections_state = Some(state);
14338 }
14339 }
14340
14341 fn select_match_ranges(
14342 &mut self,
14343 range: Range<usize>,
14344 reversed: bool,
14345 replace_newest: bool,
14346 auto_scroll: Option<Autoscroll>,
14347 window: &mut Window,
14348 cx: &mut Context<Editor>,
14349 ) {
14350 self.unfold_ranges(
14351 std::slice::from_ref(&range),
14352 false,
14353 auto_scroll.is_some(),
14354 cx,
14355 );
14356 let effects = if let Some(scroll) = auto_scroll {
14357 SelectionEffects::scroll(scroll)
14358 } else {
14359 SelectionEffects::no_scroll()
14360 };
14361 self.change_selections(effects, window, cx, |s| {
14362 if replace_newest {
14363 s.delete(s.newest_anchor().id);
14364 }
14365 if reversed {
14366 s.insert_range(range.end..range.start);
14367 } else {
14368 s.insert_range(range);
14369 }
14370 });
14371 }
14372
14373 pub fn select_next_match_internal(
14374 &mut self,
14375 display_map: &DisplaySnapshot,
14376 replace_newest: bool,
14377 autoscroll: Option<Autoscroll>,
14378 window: &mut Window,
14379 cx: &mut Context<Self>,
14380 ) -> Result<()> {
14381 let buffer = display_map.buffer_snapshot();
14382 let mut selections = self.selections.all::<usize>(&display_map);
14383 if let Some(mut select_next_state) = self.select_next_state.take() {
14384 let query = &select_next_state.query;
14385 if !select_next_state.done {
14386 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14387 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14388 let mut next_selected_range = None;
14389
14390 let bytes_after_last_selection =
14391 buffer.bytes_in_range(last_selection.end..buffer.len());
14392 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14393 let query_matches = query
14394 .stream_find_iter(bytes_after_last_selection)
14395 .map(|result| (last_selection.end, result))
14396 .chain(
14397 query
14398 .stream_find_iter(bytes_before_first_selection)
14399 .map(|result| (0, result)),
14400 );
14401
14402 for (start_offset, query_match) in query_matches {
14403 let query_match = query_match.unwrap(); // can only fail due to I/O
14404 let offset_range =
14405 start_offset + query_match.start()..start_offset + query_match.end();
14406
14407 if !select_next_state.wordwise
14408 || (!buffer.is_inside_word(offset_range.start, None)
14409 && !buffer.is_inside_word(offset_range.end, None))
14410 {
14411 let idx = selections
14412 .partition_point(|selection| selection.end <= offset_range.start);
14413 let overlaps = selections
14414 .get(idx)
14415 .map_or(false, |selection| selection.start < offset_range.end);
14416
14417 if !overlaps {
14418 next_selected_range = Some(offset_range);
14419 break;
14420 }
14421 }
14422 }
14423
14424 if let Some(next_selected_range) = next_selected_range {
14425 self.select_match_ranges(
14426 next_selected_range,
14427 last_selection.reversed,
14428 replace_newest,
14429 autoscroll,
14430 window,
14431 cx,
14432 );
14433 } else {
14434 select_next_state.done = true;
14435 }
14436 }
14437
14438 self.select_next_state = Some(select_next_state);
14439 } else {
14440 let mut only_carets = true;
14441 let mut same_text_selected = true;
14442 let mut selected_text = None;
14443
14444 let mut selections_iter = selections.iter().peekable();
14445 while let Some(selection) = selections_iter.next() {
14446 if selection.start != selection.end {
14447 only_carets = false;
14448 }
14449
14450 if same_text_selected {
14451 if selected_text.is_none() {
14452 selected_text =
14453 Some(buffer.text_for_range(selection.range()).collect::<String>());
14454 }
14455
14456 if let Some(next_selection) = selections_iter.peek() {
14457 if next_selection.range().len() == selection.range().len() {
14458 let next_selected_text = buffer
14459 .text_for_range(next_selection.range())
14460 .collect::<String>();
14461 if Some(next_selected_text) != selected_text {
14462 same_text_selected = false;
14463 selected_text = None;
14464 }
14465 } else {
14466 same_text_selected = false;
14467 selected_text = None;
14468 }
14469 }
14470 }
14471 }
14472
14473 if only_carets {
14474 for selection in &mut selections {
14475 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14476 selection.start = word_range.start;
14477 selection.end = word_range.end;
14478 selection.goal = SelectionGoal::None;
14479 selection.reversed = false;
14480 self.select_match_ranges(
14481 selection.start..selection.end,
14482 selection.reversed,
14483 replace_newest,
14484 autoscroll,
14485 window,
14486 cx,
14487 );
14488 }
14489
14490 if selections.len() == 1 {
14491 let selection = selections
14492 .last()
14493 .expect("ensured that there's only one selection");
14494 let query = buffer
14495 .text_for_range(selection.start..selection.end)
14496 .collect::<String>();
14497 let is_empty = query.is_empty();
14498 let select_state = SelectNextState {
14499 query: AhoCorasick::new(&[query])?,
14500 wordwise: true,
14501 done: is_empty,
14502 };
14503 self.select_next_state = Some(select_state);
14504 } else {
14505 self.select_next_state = None;
14506 }
14507 } else if let Some(selected_text) = selected_text {
14508 self.select_next_state = Some(SelectNextState {
14509 query: AhoCorasick::new(&[selected_text])?,
14510 wordwise: false,
14511 done: false,
14512 });
14513 self.select_next_match_internal(
14514 display_map,
14515 replace_newest,
14516 autoscroll,
14517 window,
14518 cx,
14519 )?;
14520 }
14521 }
14522 Ok(())
14523 }
14524
14525 pub fn select_all_matches(
14526 &mut self,
14527 _action: &SelectAllMatches,
14528 window: &mut Window,
14529 cx: &mut Context<Self>,
14530 ) -> Result<()> {
14531 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14532
14533 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14534
14535 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14536 let Some(select_next_state) = self.select_next_state.as_mut() else {
14537 return Ok(());
14538 };
14539 if select_next_state.done {
14540 return Ok(());
14541 }
14542
14543 let mut new_selections = Vec::new();
14544
14545 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14546 let buffer = display_map.buffer_snapshot();
14547 let query_matches = select_next_state
14548 .query
14549 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14550
14551 for query_match in query_matches.into_iter() {
14552 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14553 let offset_range = if reversed {
14554 query_match.end()..query_match.start()
14555 } else {
14556 query_match.start()..query_match.end()
14557 };
14558
14559 if !select_next_state.wordwise
14560 || (!buffer.is_inside_word(offset_range.start, None)
14561 && !buffer.is_inside_word(offset_range.end, None))
14562 {
14563 new_selections.push(offset_range.start..offset_range.end);
14564 }
14565 }
14566
14567 select_next_state.done = true;
14568
14569 if new_selections.is_empty() {
14570 log::error!("bug: new_selections is empty in select_all_matches");
14571 return Ok(());
14572 }
14573
14574 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14575 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14576 selections.select_ranges(new_selections)
14577 });
14578
14579 Ok(())
14580 }
14581
14582 pub fn select_next(
14583 &mut self,
14584 action: &SelectNext,
14585 window: &mut Window,
14586 cx: &mut Context<Self>,
14587 ) -> Result<()> {
14588 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14589 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14590 self.select_next_match_internal(
14591 &display_map,
14592 action.replace_newest,
14593 Some(Autoscroll::newest()),
14594 window,
14595 cx,
14596 )?;
14597 Ok(())
14598 }
14599
14600 pub fn select_previous(
14601 &mut self,
14602 action: &SelectPrevious,
14603 window: &mut Window,
14604 cx: &mut Context<Self>,
14605 ) -> Result<()> {
14606 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14607 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14608 let buffer = display_map.buffer_snapshot();
14609 let mut selections = self.selections.all::<usize>(&display_map);
14610 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14611 let query = &select_prev_state.query;
14612 if !select_prev_state.done {
14613 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14614 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14615 let mut next_selected_range = None;
14616 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14617 let bytes_before_last_selection =
14618 buffer.reversed_bytes_in_range(0..last_selection.start);
14619 let bytes_after_first_selection =
14620 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14621 let query_matches = query
14622 .stream_find_iter(bytes_before_last_selection)
14623 .map(|result| (last_selection.start, result))
14624 .chain(
14625 query
14626 .stream_find_iter(bytes_after_first_selection)
14627 .map(|result| (buffer.len(), result)),
14628 );
14629 for (end_offset, query_match) in query_matches {
14630 let query_match = query_match.unwrap(); // can only fail due to I/O
14631 let offset_range =
14632 end_offset - query_match.end()..end_offset - query_match.start();
14633
14634 if !select_prev_state.wordwise
14635 || (!buffer.is_inside_word(offset_range.start, None)
14636 && !buffer.is_inside_word(offset_range.end, None))
14637 {
14638 next_selected_range = Some(offset_range);
14639 break;
14640 }
14641 }
14642
14643 if let Some(next_selected_range) = next_selected_range {
14644 self.select_match_ranges(
14645 next_selected_range,
14646 last_selection.reversed,
14647 action.replace_newest,
14648 Some(Autoscroll::newest()),
14649 window,
14650 cx,
14651 );
14652 } else {
14653 select_prev_state.done = true;
14654 }
14655 }
14656
14657 self.select_prev_state = Some(select_prev_state);
14658 } else {
14659 let mut only_carets = true;
14660 let mut same_text_selected = true;
14661 let mut selected_text = None;
14662
14663 let mut selections_iter = selections.iter().peekable();
14664 while let Some(selection) = selections_iter.next() {
14665 if selection.start != selection.end {
14666 only_carets = false;
14667 }
14668
14669 if same_text_selected {
14670 if selected_text.is_none() {
14671 selected_text =
14672 Some(buffer.text_for_range(selection.range()).collect::<String>());
14673 }
14674
14675 if let Some(next_selection) = selections_iter.peek() {
14676 if next_selection.range().len() == selection.range().len() {
14677 let next_selected_text = buffer
14678 .text_for_range(next_selection.range())
14679 .collect::<String>();
14680 if Some(next_selected_text) != selected_text {
14681 same_text_selected = false;
14682 selected_text = None;
14683 }
14684 } else {
14685 same_text_selected = false;
14686 selected_text = None;
14687 }
14688 }
14689 }
14690 }
14691
14692 if only_carets {
14693 for selection in &mut selections {
14694 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14695 selection.start = word_range.start;
14696 selection.end = word_range.end;
14697 selection.goal = SelectionGoal::None;
14698 selection.reversed = false;
14699 self.select_match_ranges(
14700 selection.start..selection.end,
14701 selection.reversed,
14702 action.replace_newest,
14703 Some(Autoscroll::newest()),
14704 window,
14705 cx,
14706 );
14707 }
14708 if selections.len() == 1 {
14709 let selection = selections
14710 .last()
14711 .expect("ensured that there's only one selection");
14712 let query = buffer
14713 .text_for_range(selection.start..selection.end)
14714 .collect::<String>();
14715 let is_empty = query.is_empty();
14716 let select_state = SelectNextState {
14717 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14718 wordwise: true,
14719 done: is_empty,
14720 };
14721 self.select_prev_state = Some(select_state);
14722 } else {
14723 self.select_prev_state = None;
14724 }
14725 } else if let Some(selected_text) = selected_text {
14726 self.select_prev_state = Some(SelectNextState {
14727 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14728 wordwise: false,
14729 done: false,
14730 });
14731 self.select_previous(action, window, cx)?;
14732 }
14733 }
14734 Ok(())
14735 }
14736
14737 pub fn find_next_match(
14738 &mut self,
14739 _: &FindNextMatch,
14740 window: &mut Window,
14741 cx: &mut Context<Self>,
14742 ) -> Result<()> {
14743 let selections = self.selections.disjoint_anchors_arc();
14744 match selections.first() {
14745 Some(first) if selections.len() >= 2 => {
14746 self.change_selections(Default::default(), window, cx, |s| {
14747 s.select_ranges([first.range()]);
14748 });
14749 }
14750 _ => self.select_next(
14751 &SelectNext {
14752 replace_newest: true,
14753 },
14754 window,
14755 cx,
14756 )?,
14757 }
14758 Ok(())
14759 }
14760
14761 pub fn find_previous_match(
14762 &mut self,
14763 _: &FindPreviousMatch,
14764 window: &mut Window,
14765 cx: &mut Context<Self>,
14766 ) -> Result<()> {
14767 let selections = self.selections.disjoint_anchors_arc();
14768 match selections.last() {
14769 Some(last) if selections.len() >= 2 => {
14770 self.change_selections(Default::default(), window, cx, |s| {
14771 s.select_ranges([last.range()]);
14772 });
14773 }
14774 _ => self.select_previous(
14775 &SelectPrevious {
14776 replace_newest: true,
14777 },
14778 window,
14779 cx,
14780 )?,
14781 }
14782 Ok(())
14783 }
14784
14785 pub fn toggle_comments(
14786 &mut self,
14787 action: &ToggleComments,
14788 window: &mut Window,
14789 cx: &mut Context<Self>,
14790 ) {
14791 if self.read_only(cx) {
14792 return;
14793 }
14794 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14795 let text_layout_details = &self.text_layout_details(window);
14796 self.transact(window, cx, |this, window, cx| {
14797 let mut selections = this
14798 .selections
14799 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14800 let mut edits = Vec::new();
14801 let mut selection_edit_ranges = Vec::new();
14802 let mut last_toggled_row = None;
14803 let snapshot = this.buffer.read(cx).read(cx);
14804 let empty_str: Arc<str> = Arc::default();
14805 let mut suffixes_inserted = Vec::new();
14806 let ignore_indent = action.ignore_indent;
14807
14808 fn comment_prefix_range(
14809 snapshot: &MultiBufferSnapshot,
14810 row: MultiBufferRow,
14811 comment_prefix: &str,
14812 comment_prefix_whitespace: &str,
14813 ignore_indent: bool,
14814 ) -> Range<Point> {
14815 let indent_size = if ignore_indent {
14816 0
14817 } else {
14818 snapshot.indent_size_for_line(row).len
14819 };
14820
14821 let start = Point::new(row.0, indent_size);
14822
14823 let mut line_bytes = snapshot
14824 .bytes_in_range(start..snapshot.max_point())
14825 .flatten()
14826 .copied();
14827
14828 // If this line currently begins with the line comment prefix, then record
14829 // the range containing the prefix.
14830 if line_bytes
14831 .by_ref()
14832 .take(comment_prefix.len())
14833 .eq(comment_prefix.bytes())
14834 {
14835 // Include any whitespace that matches the comment prefix.
14836 let matching_whitespace_len = line_bytes
14837 .zip(comment_prefix_whitespace.bytes())
14838 .take_while(|(a, b)| a == b)
14839 .count() as u32;
14840 let end = Point::new(
14841 start.row,
14842 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14843 );
14844 start..end
14845 } else {
14846 start..start
14847 }
14848 }
14849
14850 fn comment_suffix_range(
14851 snapshot: &MultiBufferSnapshot,
14852 row: MultiBufferRow,
14853 comment_suffix: &str,
14854 comment_suffix_has_leading_space: bool,
14855 ) -> Range<Point> {
14856 let end = Point::new(row.0, snapshot.line_len(row));
14857 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14858
14859 let mut line_end_bytes = snapshot
14860 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14861 .flatten()
14862 .copied();
14863
14864 let leading_space_len = if suffix_start_column > 0
14865 && line_end_bytes.next() == Some(b' ')
14866 && comment_suffix_has_leading_space
14867 {
14868 1
14869 } else {
14870 0
14871 };
14872
14873 // If this line currently begins with the line comment prefix, then record
14874 // the range containing the prefix.
14875 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14876 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14877 start..end
14878 } else {
14879 end..end
14880 }
14881 }
14882
14883 // TODO: Handle selections that cross excerpts
14884 for selection in &mut selections {
14885 let start_column = snapshot
14886 .indent_size_for_line(MultiBufferRow(selection.start.row))
14887 .len;
14888 let language = if let Some(language) =
14889 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14890 {
14891 language
14892 } else {
14893 continue;
14894 };
14895
14896 selection_edit_ranges.clear();
14897
14898 // If multiple selections contain a given row, avoid processing that
14899 // row more than once.
14900 let mut start_row = MultiBufferRow(selection.start.row);
14901 if last_toggled_row == Some(start_row) {
14902 start_row = start_row.next_row();
14903 }
14904 let end_row =
14905 if selection.end.row > selection.start.row && selection.end.column == 0 {
14906 MultiBufferRow(selection.end.row - 1)
14907 } else {
14908 MultiBufferRow(selection.end.row)
14909 };
14910 last_toggled_row = Some(end_row);
14911
14912 if start_row > end_row {
14913 continue;
14914 }
14915
14916 // If the language has line comments, toggle those.
14917 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14918
14919 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14920 if ignore_indent {
14921 full_comment_prefixes = full_comment_prefixes
14922 .into_iter()
14923 .map(|s| Arc::from(s.trim_end()))
14924 .collect();
14925 }
14926
14927 if !full_comment_prefixes.is_empty() {
14928 let first_prefix = full_comment_prefixes
14929 .first()
14930 .expect("prefixes is non-empty");
14931 let prefix_trimmed_lengths = full_comment_prefixes
14932 .iter()
14933 .map(|p| p.trim_end_matches(' ').len())
14934 .collect::<SmallVec<[usize; 4]>>();
14935
14936 let mut all_selection_lines_are_comments = true;
14937
14938 for row in start_row.0..=end_row.0 {
14939 let row = MultiBufferRow(row);
14940 if start_row < end_row && snapshot.is_line_blank(row) {
14941 continue;
14942 }
14943
14944 let prefix_range = full_comment_prefixes
14945 .iter()
14946 .zip(prefix_trimmed_lengths.iter().copied())
14947 .map(|(prefix, trimmed_prefix_len)| {
14948 comment_prefix_range(
14949 snapshot.deref(),
14950 row,
14951 &prefix[..trimmed_prefix_len],
14952 &prefix[trimmed_prefix_len..],
14953 ignore_indent,
14954 )
14955 })
14956 .max_by_key(|range| range.end.column - range.start.column)
14957 .expect("prefixes is non-empty");
14958
14959 if prefix_range.is_empty() {
14960 all_selection_lines_are_comments = false;
14961 }
14962
14963 selection_edit_ranges.push(prefix_range);
14964 }
14965
14966 if all_selection_lines_are_comments {
14967 edits.extend(
14968 selection_edit_ranges
14969 .iter()
14970 .cloned()
14971 .map(|range| (range, empty_str.clone())),
14972 );
14973 } else {
14974 let min_column = selection_edit_ranges
14975 .iter()
14976 .map(|range| range.start.column)
14977 .min()
14978 .unwrap_or(0);
14979 edits.extend(selection_edit_ranges.iter().map(|range| {
14980 let position = Point::new(range.start.row, min_column);
14981 (position..position, first_prefix.clone())
14982 }));
14983 }
14984 } else if let Some(BlockCommentConfig {
14985 start: full_comment_prefix,
14986 end: comment_suffix,
14987 ..
14988 }) = language.block_comment()
14989 {
14990 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14991 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14992 let prefix_range = comment_prefix_range(
14993 snapshot.deref(),
14994 start_row,
14995 comment_prefix,
14996 comment_prefix_whitespace,
14997 ignore_indent,
14998 );
14999 let suffix_range = comment_suffix_range(
15000 snapshot.deref(),
15001 end_row,
15002 comment_suffix.trim_start_matches(' '),
15003 comment_suffix.starts_with(' '),
15004 );
15005
15006 if prefix_range.is_empty() || suffix_range.is_empty() {
15007 edits.push((
15008 prefix_range.start..prefix_range.start,
15009 full_comment_prefix.clone(),
15010 ));
15011 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15012 suffixes_inserted.push((end_row, comment_suffix.len()));
15013 } else {
15014 edits.push((prefix_range, empty_str.clone()));
15015 edits.push((suffix_range, empty_str.clone()));
15016 }
15017 } else {
15018 continue;
15019 }
15020 }
15021
15022 drop(snapshot);
15023 this.buffer.update(cx, |buffer, cx| {
15024 buffer.edit(edits, None, cx);
15025 });
15026
15027 // Adjust selections so that they end before any comment suffixes that
15028 // were inserted.
15029 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15030 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15031 let snapshot = this.buffer.read(cx).read(cx);
15032 for selection in &mut selections {
15033 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15034 match row.cmp(&MultiBufferRow(selection.end.row)) {
15035 Ordering::Less => {
15036 suffixes_inserted.next();
15037 continue;
15038 }
15039 Ordering::Greater => break,
15040 Ordering::Equal => {
15041 if selection.end.column == snapshot.line_len(row) {
15042 if selection.is_empty() {
15043 selection.start.column -= suffix_len as u32;
15044 }
15045 selection.end.column -= suffix_len as u32;
15046 }
15047 break;
15048 }
15049 }
15050 }
15051 }
15052
15053 drop(snapshot);
15054 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15055
15056 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15057 let selections_on_single_row = selections.windows(2).all(|selections| {
15058 selections[0].start.row == selections[1].start.row
15059 && selections[0].end.row == selections[1].end.row
15060 && selections[0].start.row == selections[0].end.row
15061 });
15062 let selections_selecting = selections
15063 .iter()
15064 .any(|selection| selection.start != selection.end);
15065 let advance_downwards = action.advance_downwards
15066 && selections_on_single_row
15067 && !selections_selecting
15068 && !matches!(this.mode, EditorMode::SingleLine);
15069
15070 if advance_downwards {
15071 let snapshot = this.buffer.read(cx).snapshot(cx);
15072
15073 this.change_selections(Default::default(), window, cx, |s| {
15074 s.move_cursors_with(|display_snapshot, display_point, _| {
15075 let mut point = display_point.to_point(display_snapshot);
15076 point.row += 1;
15077 point = snapshot.clip_point(point, Bias::Left);
15078 let display_point = point.to_display_point(display_snapshot);
15079 let goal = SelectionGoal::HorizontalPosition(
15080 display_snapshot
15081 .x_for_display_point(display_point, text_layout_details)
15082 .into(),
15083 );
15084 (display_point, goal)
15085 })
15086 });
15087 }
15088 });
15089 }
15090
15091 pub fn select_enclosing_symbol(
15092 &mut self,
15093 _: &SelectEnclosingSymbol,
15094 window: &mut Window,
15095 cx: &mut Context<Self>,
15096 ) {
15097 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15098
15099 let buffer = self.buffer.read(cx).snapshot(cx);
15100 let old_selections = self
15101 .selections
15102 .all::<usize>(&self.display_snapshot(cx))
15103 .into_boxed_slice();
15104
15105 fn update_selection(
15106 selection: &Selection<usize>,
15107 buffer_snap: &MultiBufferSnapshot,
15108 ) -> Option<Selection<usize>> {
15109 let cursor = selection.head();
15110 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15111 for symbol in symbols.iter().rev() {
15112 let start = symbol.range.start.to_offset(buffer_snap);
15113 let end = symbol.range.end.to_offset(buffer_snap);
15114 let new_range = start..end;
15115 if start < selection.start || end > selection.end {
15116 return Some(Selection {
15117 id: selection.id,
15118 start: new_range.start,
15119 end: new_range.end,
15120 goal: SelectionGoal::None,
15121 reversed: selection.reversed,
15122 });
15123 }
15124 }
15125 None
15126 }
15127
15128 let mut selected_larger_symbol = false;
15129 let new_selections = old_selections
15130 .iter()
15131 .map(|selection| match update_selection(selection, &buffer) {
15132 Some(new_selection) => {
15133 if new_selection.range() != selection.range() {
15134 selected_larger_symbol = true;
15135 }
15136 new_selection
15137 }
15138 None => selection.clone(),
15139 })
15140 .collect::<Vec<_>>();
15141
15142 if selected_larger_symbol {
15143 self.change_selections(Default::default(), window, cx, |s| {
15144 s.select(new_selections);
15145 });
15146 }
15147 }
15148
15149 pub fn select_larger_syntax_node(
15150 &mut self,
15151 _: &SelectLargerSyntaxNode,
15152 window: &mut Window,
15153 cx: &mut Context<Self>,
15154 ) {
15155 let Some(visible_row_count) = self.visible_row_count() else {
15156 return;
15157 };
15158 let old_selections: Box<[_]> = self
15159 .selections
15160 .all::<usize>(&self.display_snapshot(cx))
15161 .into();
15162 if old_selections.is_empty() {
15163 return;
15164 }
15165
15166 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15167
15168 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15169 let buffer = self.buffer.read(cx).snapshot(cx);
15170
15171 let mut selected_larger_node = false;
15172 let mut new_selections = old_selections
15173 .iter()
15174 .map(|selection| {
15175 let old_range = selection.start..selection.end;
15176
15177 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15178 // manually select word at selection
15179 if ["string_content", "inline"].contains(&node.kind()) {
15180 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15181 // ignore if word is already selected
15182 if !word_range.is_empty() && old_range != word_range {
15183 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15184 // only select word if start and end point belongs to same word
15185 if word_range == last_word_range {
15186 selected_larger_node = true;
15187 return Selection {
15188 id: selection.id,
15189 start: word_range.start,
15190 end: word_range.end,
15191 goal: SelectionGoal::None,
15192 reversed: selection.reversed,
15193 };
15194 }
15195 }
15196 }
15197 }
15198
15199 let mut new_range = old_range.clone();
15200 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15201 new_range = range;
15202 if !node.is_named() {
15203 continue;
15204 }
15205 if !display_map.intersects_fold(new_range.start)
15206 && !display_map.intersects_fold(new_range.end)
15207 {
15208 break;
15209 }
15210 }
15211
15212 selected_larger_node |= new_range != old_range;
15213 Selection {
15214 id: selection.id,
15215 start: new_range.start,
15216 end: new_range.end,
15217 goal: SelectionGoal::None,
15218 reversed: selection.reversed,
15219 }
15220 })
15221 .collect::<Vec<_>>();
15222
15223 if !selected_larger_node {
15224 return; // don't put this call in the history
15225 }
15226
15227 // scroll based on transformation done to the last selection created by the user
15228 let (last_old, last_new) = old_selections
15229 .last()
15230 .zip(new_selections.last().cloned())
15231 .expect("old_selections isn't empty");
15232
15233 // revert selection
15234 let is_selection_reversed = {
15235 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15236 new_selections.last_mut().expect("checked above").reversed =
15237 should_newest_selection_be_reversed;
15238 should_newest_selection_be_reversed
15239 };
15240
15241 if selected_larger_node {
15242 self.select_syntax_node_history.disable_clearing = true;
15243 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15244 s.select(new_selections.clone());
15245 });
15246 self.select_syntax_node_history.disable_clearing = false;
15247 }
15248
15249 let start_row = last_new.start.to_display_point(&display_map).row().0;
15250 let end_row = last_new.end.to_display_point(&display_map).row().0;
15251 let selection_height = end_row - start_row + 1;
15252 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15253
15254 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15255 let scroll_behavior = if fits_on_the_screen {
15256 self.request_autoscroll(Autoscroll::fit(), cx);
15257 SelectSyntaxNodeScrollBehavior::FitSelection
15258 } else if is_selection_reversed {
15259 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15260 SelectSyntaxNodeScrollBehavior::CursorTop
15261 } else {
15262 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15263 SelectSyntaxNodeScrollBehavior::CursorBottom
15264 };
15265
15266 self.select_syntax_node_history.push((
15267 old_selections,
15268 scroll_behavior,
15269 is_selection_reversed,
15270 ));
15271 }
15272
15273 pub fn select_smaller_syntax_node(
15274 &mut self,
15275 _: &SelectSmallerSyntaxNode,
15276 window: &mut Window,
15277 cx: &mut Context<Self>,
15278 ) {
15279 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15280
15281 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15282 self.select_syntax_node_history.pop()
15283 {
15284 if let Some(selection) = selections.last_mut() {
15285 selection.reversed = is_selection_reversed;
15286 }
15287
15288 self.select_syntax_node_history.disable_clearing = true;
15289 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15290 s.select(selections.to_vec());
15291 });
15292 self.select_syntax_node_history.disable_clearing = false;
15293
15294 match scroll_behavior {
15295 SelectSyntaxNodeScrollBehavior::CursorTop => {
15296 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15297 }
15298 SelectSyntaxNodeScrollBehavior::FitSelection => {
15299 self.request_autoscroll(Autoscroll::fit(), cx);
15300 }
15301 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15302 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15303 }
15304 }
15305 }
15306 }
15307
15308 pub fn unwrap_syntax_node(
15309 &mut self,
15310 _: &UnwrapSyntaxNode,
15311 window: &mut Window,
15312 cx: &mut Context<Self>,
15313 ) {
15314 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15315
15316 let buffer = self.buffer.read(cx).snapshot(cx);
15317 let selections = self
15318 .selections
15319 .all::<usize>(&self.display_snapshot(cx))
15320 .into_iter()
15321 // subtracting the offset requires sorting
15322 .sorted_by_key(|i| i.start);
15323
15324 let full_edits = selections
15325 .into_iter()
15326 .filter_map(|selection| {
15327 let child = if selection.is_empty()
15328 && let Some((_, ancestor_range)) =
15329 buffer.syntax_ancestor(selection.start..selection.end)
15330 {
15331 ancestor_range
15332 } else {
15333 selection.range()
15334 };
15335
15336 let mut parent = child.clone();
15337 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15338 parent = ancestor_range;
15339 if parent.start < child.start || parent.end > child.end {
15340 break;
15341 }
15342 }
15343
15344 if parent == child {
15345 return None;
15346 }
15347 let text = buffer.text_for_range(child).collect::<String>();
15348 Some((selection.id, parent, text))
15349 })
15350 .collect::<Vec<_>>();
15351 if full_edits.is_empty() {
15352 return;
15353 }
15354
15355 self.transact(window, cx, |this, window, cx| {
15356 this.buffer.update(cx, |buffer, cx| {
15357 buffer.edit(
15358 full_edits
15359 .iter()
15360 .map(|(_, p, t)| (p.clone(), t.clone()))
15361 .collect::<Vec<_>>(),
15362 None,
15363 cx,
15364 );
15365 });
15366 this.change_selections(Default::default(), window, cx, |s| {
15367 let mut offset = 0;
15368 let mut selections = vec![];
15369 for (id, parent, text) in full_edits {
15370 let start = parent.start - offset;
15371 offset += parent.len() - text.len();
15372 selections.push(Selection {
15373 id,
15374 start,
15375 end: start + text.len(),
15376 reversed: false,
15377 goal: Default::default(),
15378 });
15379 }
15380 s.select(selections);
15381 });
15382 });
15383 }
15384
15385 pub fn select_next_syntax_node(
15386 &mut self,
15387 _: &SelectNextSyntaxNode,
15388 window: &mut Window,
15389 cx: &mut Context<Self>,
15390 ) {
15391 let old_selections: Box<[_]> = self
15392 .selections
15393 .all::<usize>(&self.display_snapshot(cx))
15394 .into();
15395 if old_selections.is_empty() {
15396 return;
15397 }
15398
15399 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15400
15401 let buffer = self.buffer.read(cx).snapshot(cx);
15402 let mut selected_sibling = false;
15403
15404 let new_selections = old_selections
15405 .iter()
15406 .map(|selection| {
15407 let old_range = selection.start..selection.end;
15408
15409 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15410 let new_range = node.byte_range();
15411 selected_sibling = true;
15412 Selection {
15413 id: selection.id,
15414 start: new_range.start,
15415 end: new_range.end,
15416 goal: SelectionGoal::None,
15417 reversed: selection.reversed,
15418 }
15419 } else {
15420 selection.clone()
15421 }
15422 })
15423 .collect::<Vec<_>>();
15424
15425 if selected_sibling {
15426 self.change_selections(
15427 SelectionEffects::scroll(Autoscroll::fit()),
15428 window,
15429 cx,
15430 |s| {
15431 s.select(new_selections);
15432 },
15433 );
15434 }
15435 }
15436
15437 pub fn select_prev_syntax_node(
15438 &mut self,
15439 _: &SelectPreviousSyntaxNode,
15440 window: &mut Window,
15441 cx: &mut Context<Self>,
15442 ) {
15443 let old_selections: Box<[_]> = self
15444 .selections
15445 .all::<usize>(&self.display_snapshot(cx))
15446 .into();
15447 if old_selections.is_empty() {
15448 return;
15449 }
15450
15451 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15452
15453 let buffer = self.buffer.read(cx).snapshot(cx);
15454 let mut selected_sibling = false;
15455
15456 let new_selections = old_selections
15457 .iter()
15458 .map(|selection| {
15459 let old_range = selection.start..selection.end;
15460
15461 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15462 let new_range = node.byte_range();
15463 selected_sibling = true;
15464 Selection {
15465 id: selection.id,
15466 start: new_range.start,
15467 end: new_range.end,
15468 goal: SelectionGoal::None,
15469 reversed: selection.reversed,
15470 }
15471 } else {
15472 selection.clone()
15473 }
15474 })
15475 .collect::<Vec<_>>();
15476
15477 if selected_sibling {
15478 self.change_selections(
15479 SelectionEffects::scroll(Autoscroll::fit()),
15480 window,
15481 cx,
15482 |s| {
15483 s.select(new_selections);
15484 },
15485 );
15486 }
15487 }
15488
15489 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15490 if !EditorSettings::get_global(cx).gutter.runnables {
15491 self.clear_tasks();
15492 return Task::ready(());
15493 }
15494 let project = self.project().map(Entity::downgrade);
15495 let task_sources = self.lsp_task_sources(cx);
15496 let multi_buffer = self.buffer.downgrade();
15497 cx.spawn_in(window, async move |editor, cx| {
15498 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15499 let Some(project) = project.and_then(|p| p.upgrade()) else {
15500 return;
15501 };
15502 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15503 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15504 }) else {
15505 return;
15506 };
15507
15508 let hide_runnables = project
15509 .update(cx, |project, _| project.is_via_collab())
15510 .unwrap_or(true);
15511 if hide_runnables {
15512 return;
15513 }
15514 let new_rows =
15515 cx.background_spawn({
15516 let snapshot = display_snapshot.clone();
15517 async move {
15518 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15519 }
15520 })
15521 .await;
15522 let Ok(lsp_tasks) =
15523 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15524 else {
15525 return;
15526 };
15527 let lsp_tasks = lsp_tasks.await;
15528
15529 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15530 lsp_tasks
15531 .into_iter()
15532 .flat_map(|(kind, tasks)| {
15533 tasks.into_iter().filter_map(move |(location, task)| {
15534 Some((kind.clone(), location?, task))
15535 })
15536 })
15537 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15538 let buffer = location.target.buffer;
15539 let buffer_snapshot = buffer.read(cx).snapshot();
15540 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15541 |(excerpt_id, snapshot, _)| {
15542 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15543 display_snapshot
15544 .buffer_snapshot()
15545 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15546 } else {
15547 None
15548 }
15549 },
15550 );
15551 if let Some(offset) = offset {
15552 let task_buffer_range =
15553 location.target.range.to_point(&buffer_snapshot);
15554 let context_buffer_range =
15555 task_buffer_range.to_offset(&buffer_snapshot);
15556 let context_range = BufferOffset(context_buffer_range.start)
15557 ..BufferOffset(context_buffer_range.end);
15558
15559 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15560 .or_insert_with(|| RunnableTasks {
15561 templates: Vec::new(),
15562 offset,
15563 column: task_buffer_range.start.column,
15564 extra_variables: HashMap::default(),
15565 context_range,
15566 })
15567 .templates
15568 .push((kind, task.original_task().clone()));
15569 }
15570
15571 acc
15572 })
15573 }) else {
15574 return;
15575 };
15576
15577 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15578 buffer.language_settings(cx).tasks.prefer_lsp
15579 }) else {
15580 return;
15581 };
15582
15583 let rows = Self::runnable_rows(
15584 project,
15585 display_snapshot,
15586 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15587 new_rows,
15588 cx.clone(),
15589 )
15590 .await;
15591 editor
15592 .update(cx, |editor, _| {
15593 editor.clear_tasks();
15594 for (key, mut value) in rows {
15595 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15596 value.templates.extend(lsp_tasks.templates);
15597 }
15598
15599 editor.insert_tasks(key, value);
15600 }
15601 for (key, value) in lsp_tasks_by_rows {
15602 editor.insert_tasks(key, value);
15603 }
15604 })
15605 .ok();
15606 })
15607 }
15608 fn fetch_runnable_ranges(
15609 snapshot: &DisplaySnapshot,
15610 range: Range<Anchor>,
15611 ) -> Vec<language::RunnableRange> {
15612 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15613 }
15614
15615 fn runnable_rows(
15616 project: Entity<Project>,
15617 snapshot: DisplaySnapshot,
15618 prefer_lsp: bool,
15619 runnable_ranges: Vec<RunnableRange>,
15620 cx: AsyncWindowContext,
15621 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15622 cx.spawn(async move |cx| {
15623 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15624 for mut runnable in runnable_ranges {
15625 let Some(tasks) = cx
15626 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15627 .ok()
15628 else {
15629 continue;
15630 };
15631 let mut tasks = tasks.await;
15632
15633 if prefer_lsp {
15634 tasks.retain(|(task_kind, _)| {
15635 !matches!(task_kind, TaskSourceKind::Language { .. })
15636 });
15637 }
15638 if tasks.is_empty() {
15639 continue;
15640 }
15641
15642 let point = runnable
15643 .run_range
15644 .start
15645 .to_point(&snapshot.buffer_snapshot());
15646 let Some(row) = snapshot
15647 .buffer_snapshot()
15648 .buffer_line_for_row(MultiBufferRow(point.row))
15649 .map(|(_, range)| range.start.row)
15650 else {
15651 continue;
15652 };
15653
15654 let context_range =
15655 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15656 runnable_rows.push((
15657 (runnable.buffer_id, row),
15658 RunnableTasks {
15659 templates: tasks,
15660 offset: snapshot
15661 .buffer_snapshot()
15662 .anchor_before(runnable.run_range.start),
15663 context_range,
15664 column: point.column,
15665 extra_variables: runnable.extra_captures,
15666 },
15667 ));
15668 }
15669 runnable_rows
15670 })
15671 }
15672
15673 fn templates_with_tags(
15674 project: &Entity<Project>,
15675 runnable: &mut Runnable,
15676 cx: &mut App,
15677 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15678 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15679 let (worktree_id, file) = project
15680 .buffer_for_id(runnable.buffer, cx)
15681 .and_then(|buffer| buffer.read(cx).file())
15682 .map(|file| (file.worktree_id(cx), file.clone()))
15683 .unzip();
15684
15685 (
15686 project.task_store().read(cx).task_inventory().cloned(),
15687 worktree_id,
15688 file,
15689 )
15690 });
15691
15692 let tags = mem::take(&mut runnable.tags);
15693 let language = runnable.language.clone();
15694 cx.spawn(async move |cx| {
15695 let mut templates_with_tags = Vec::new();
15696 if let Some(inventory) = inventory {
15697 for RunnableTag(tag) in tags {
15698 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15699 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15700 }) else {
15701 return templates_with_tags;
15702 };
15703 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15704 move |(_, template)| {
15705 template.tags.iter().any(|source_tag| source_tag == &tag)
15706 },
15707 ));
15708 }
15709 }
15710 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15711
15712 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15713 // Strongest source wins; if we have worktree tag binding, prefer that to
15714 // global and language bindings;
15715 // if we have a global binding, prefer that to language binding.
15716 let first_mismatch = templates_with_tags
15717 .iter()
15718 .position(|(tag_source, _)| tag_source != leading_tag_source);
15719 if let Some(index) = first_mismatch {
15720 templates_with_tags.truncate(index);
15721 }
15722 }
15723
15724 templates_with_tags
15725 })
15726 }
15727
15728 pub fn move_to_enclosing_bracket(
15729 &mut self,
15730 _: &MoveToEnclosingBracket,
15731 window: &mut Window,
15732 cx: &mut Context<Self>,
15733 ) {
15734 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15735 self.change_selections(Default::default(), window, cx, |s| {
15736 s.move_offsets_with(|snapshot, selection| {
15737 let Some(enclosing_bracket_ranges) =
15738 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15739 else {
15740 return;
15741 };
15742
15743 let mut best_length = usize::MAX;
15744 let mut best_inside = false;
15745 let mut best_in_bracket_range = false;
15746 let mut best_destination = None;
15747 for (open, close) in enclosing_bracket_ranges {
15748 let close = close.to_inclusive();
15749 let length = close.end() - open.start;
15750 let inside = selection.start >= open.end && selection.end <= *close.start();
15751 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15752 || close.contains(&selection.head());
15753
15754 // If best is next to a bracket and current isn't, skip
15755 if !in_bracket_range && best_in_bracket_range {
15756 continue;
15757 }
15758
15759 // Prefer smaller lengths unless best is inside and current isn't
15760 if length > best_length && (best_inside || !inside) {
15761 continue;
15762 }
15763
15764 best_length = length;
15765 best_inside = inside;
15766 best_in_bracket_range = in_bracket_range;
15767 best_destination = Some(
15768 if close.contains(&selection.start) && close.contains(&selection.end) {
15769 if inside { open.end } else { open.start }
15770 } else if inside {
15771 *close.start()
15772 } else {
15773 *close.end()
15774 },
15775 );
15776 }
15777
15778 if let Some(destination) = best_destination {
15779 selection.collapse_to(destination, SelectionGoal::None);
15780 }
15781 })
15782 });
15783 }
15784
15785 pub fn undo_selection(
15786 &mut self,
15787 _: &UndoSelection,
15788 window: &mut Window,
15789 cx: &mut Context<Self>,
15790 ) {
15791 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15792 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15793 self.selection_history.mode = SelectionHistoryMode::Undoing;
15794 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15795 this.end_selection(window, cx);
15796 this.change_selections(
15797 SelectionEffects::scroll(Autoscroll::newest()),
15798 window,
15799 cx,
15800 |s| s.select_anchors(entry.selections.to_vec()),
15801 );
15802 });
15803 self.selection_history.mode = SelectionHistoryMode::Normal;
15804
15805 self.select_next_state = entry.select_next_state;
15806 self.select_prev_state = entry.select_prev_state;
15807 self.add_selections_state = entry.add_selections_state;
15808 }
15809 }
15810
15811 pub fn redo_selection(
15812 &mut self,
15813 _: &RedoSelection,
15814 window: &mut Window,
15815 cx: &mut Context<Self>,
15816 ) {
15817 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15818 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15819 self.selection_history.mode = SelectionHistoryMode::Redoing;
15820 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15821 this.end_selection(window, cx);
15822 this.change_selections(
15823 SelectionEffects::scroll(Autoscroll::newest()),
15824 window,
15825 cx,
15826 |s| s.select_anchors(entry.selections.to_vec()),
15827 );
15828 });
15829 self.selection_history.mode = SelectionHistoryMode::Normal;
15830
15831 self.select_next_state = entry.select_next_state;
15832 self.select_prev_state = entry.select_prev_state;
15833 self.add_selections_state = entry.add_selections_state;
15834 }
15835 }
15836
15837 pub fn expand_excerpts(
15838 &mut self,
15839 action: &ExpandExcerpts,
15840 _: &mut Window,
15841 cx: &mut Context<Self>,
15842 ) {
15843 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15844 }
15845
15846 pub fn expand_excerpts_down(
15847 &mut self,
15848 action: &ExpandExcerptsDown,
15849 _: &mut Window,
15850 cx: &mut Context<Self>,
15851 ) {
15852 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15853 }
15854
15855 pub fn expand_excerpts_up(
15856 &mut self,
15857 action: &ExpandExcerptsUp,
15858 _: &mut Window,
15859 cx: &mut Context<Self>,
15860 ) {
15861 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15862 }
15863
15864 pub fn expand_excerpts_for_direction(
15865 &mut self,
15866 lines: u32,
15867 direction: ExpandExcerptDirection,
15868
15869 cx: &mut Context<Self>,
15870 ) {
15871 let selections = self.selections.disjoint_anchors_arc();
15872
15873 let lines = if lines == 0 {
15874 EditorSettings::get_global(cx).expand_excerpt_lines
15875 } else {
15876 lines
15877 };
15878
15879 self.buffer.update(cx, |buffer, cx| {
15880 let snapshot = buffer.snapshot(cx);
15881 let mut excerpt_ids = selections
15882 .iter()
15883 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15884 .collect::<Vec<_>>();
15885 excerpt_ids.sort();
15886 excerpt_ids.dedup();
15887 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15888 })
15889 }
15890
15891 pub fn expand_excerpt(
15892 &mut self,
15893 excerpt: ExcerptId,
15894 direction: ExpandExcerptDirection,
15895 window: &mut Window,
15896 cx: &mut Context<Self>,
15897 ) {
15898 let current_scroll_position = self.scroll_position(cx);
15899 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15900 let mut scroll = None;
15901
15902 if direction == ExpandExcerptDirection::Down {
15903 let multi_buffer = self.buffer.read(cx);
15904 let snapshot = multi_buffer.snapshot(cx);
15905 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15906 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15907 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15908 {
15909 let buffer_snapshot = buffer.read(cx).snapshot();
15910 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15911 let last_row = buffer_snapshot.max_point().row;
15912 let lines_below = last_row.saturating_sub(excerpt_end_row);
15913 if lines_below >= lines_to_expand {
15914 scroll = Some(
15915 current_scroll_position
15916 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
15917 );
15918 }
15919 }
15920 }
15921 if direction == ExpandExcerptDirection::Up
15922 && self
15923 .buffer
15924 .read(cx)
15925 .snapshot(cx)
15926 .excerpt_before(excerpt)
15927 .is_none()
15928 {
15929 scroll = Some(current_scroll_position);
15930 }
15931
15932 self.buffer.update(cx, |buffer, cx| {
15933 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15934 });
15935
15936 if let Some(new_scroll_position) = scroll {
15937 self.set_scroll_position(new_scroll_position, window, cx);
15938 }
15939 }
15940
15941 pub fn go_to_singleton_buffer_point(
15942 &mut self,
15943 point: Point,
15944 window: &mut Window,
15945 cx: &mut Context<Self>,
15946 ) {
15947 self.go_to_singleton_buffer_range(point..point, window, cx);
15948 }
15949
15950 pub fn go_to_singleton_buffer_range(
15951 &mut self,
15952 range: Range<Point>,
15953 window: &mut Window,
15954 cx: &mut Context<Self>,
15955 ) {
15956 let multibuffer = self.buffer().read(cx);
15957 let Some(buffer) = multibuffer.as_singleton() else {
15958 return;
15959 };
15960 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15961 return;
15962 };
15963 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15964 return;
15965 };
15966 self.change_selections(
15967 SelectionEffects::default().nav_history(true),
15968 window,
15969 cx,
15970 |s| s.select_anchor_ranges([start..end]),
15971 );
15972 }
15973
15974 pub fn go_to_diagnostic(
15975 &mut self,
15976 action: &GoToDiagnostic,
15977 window: &mut Window,
15978 cx: &mut Context<Self>,
15979 ) {
15980 if !self.diagnostics_enabled() {
15981 return;
15982 }
15983 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15984 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15985 }
15986
15987 pub fn go_to_prev_diagnostic(
15988 &mut self,
15989 action: &GoToPreviousDiagnostic,
15990 window: &mut Window,
15991 cx: &mut Context<Self>,
15992 ) {
15993 if !self.diagnostics_enabled() {
15994 return;
15995 }
15996 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15997 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15998 }
15999
16000 pub fn go_to_diagnostic_impl(
16001 &mut self,
16002 direction: Direction,
16003 severity: GoToDiagnosticSeverityFilter,
16004 window: &mut Window,
16005 cx: &mut Context<Self>,
16006 ) {
16007 let buffer = self.buffer.read(cx).snapshot(cx);
16008 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16009
16010 let mut active_group_id = None;
16011 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16012 && active_group.active_range.start.to_offset(&buffer) == selection.start
16013 {
16014 active_group_id = Some(active_group.group_id);
16015 }
16016
16017 fn filtered<'a>(
16018 snapshot: EditorSnapshot,
16019 severity: GoToDiagnosticSeverityFilter,
16020 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16021 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16022 diagnostics
16023 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16024 .filter(|entry| entry.range.start != entry.range.end)
16025 .filter(|entry| !entry.diagnostic.is_unnecessary)
16026 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
16027 }
16028
16029 let snapshot = self.snapshot(window, cx);
16030 let before = filtered(
16031 snapshot.clone(),
16032 severity,
16033 buffer
16034 .diagnostics_in_range(0..selection.start)
16035 .filter(|entry| entry.range.start <= selection.start),
16036 );
16037 let after = filtered(
16038 snapshot,
16039 severity,
16040 buffer
16041 .diagnostics_in_range(selection.start..buffer.len())
16042 .filter(|entry| entry.range.start >= selection.start),
16043 );
16044
16045 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16046 if direction == Direction::Prev {
16047 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16048 {
16049 for diagnostic in prev_diagnostics.into_iter().rev() {
16050 if diagnostic.range.start != selection.start
16051 || active_group_id
16052 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16053 {
16054 found = Some(diagnostic);
16055 break 'outer;
16056 }
16057 }
16058 }
16059 } else {
16060 for diagnostic in after.chain(before) {
16061 if diagnostic.range.start != selection.start
16062 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16063 {
16064 found = Some(diagnostic);
16065 break;
16066 }
16067 }
16068 }
16069 let Some(next_diagnostic) = found else {
16070 return;
16071 };
16072
16073 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16074 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16075 return;
16076 };
16077 self.change_selections(Default::default(), window, cx, |s| {
16078 s.select_ranges(vec![
16079 next_diagnostic.range.start..next_diagnostic.range.start,
16080 ])
16081 });
16082 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16083 self.refresh_edit_prediction(false, true, window, cx);
16084 }
16085
16086 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16087 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16088 let snapshot = self.snapshot(window, cx);
16089 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16090 self.go_to_hunk_before_or_after_position(
16091 &snapshot,
16092 selection.head(),
16093 Direction::Next,
16094 window,
16095 cx,
16096 );
16097 }
16098
16099 pub fn go_to_hunk_before_or_after_position(
16100 &mut self,
16101 snapshot: &EditorSnapshot,
16102 position: Point,
16103 direction: Direction,
16104 window: &mut Window,
16105 cx: &mut Context<Editor>,
16106 ) {
16107 let row = if direction == Direction::Next {
16108 self.hunk_after_position(snapshot, position)
16109 .map(|hunk| hunk.row_range.start)
16110 } else {
16111 self.hunk_before_position(snapshot, position)
16112 };
16113
16114 if let Some(row) = row {
16115 let destination = Point::new(row.0, 0);
16116 let autoscroll = Autoscroll::center();
16117
16118 self.unfold_ranges(&[destination..destination], false, false, cx);
16119 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16120 s.select_ranges([destination..destination]);
16121 });
16122 }
16123 }
16124
16125 fn hunk_after_position(
16126 &mut self,
16127 snapshot: &EditorSnapshot,
16128 position: Point,
16129 ) -> Option<MultiBufferDiffHunk> {
16130 snapshot
16131 .buffer_snapshot()
16132 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16133 .find(|hunk| hunk.row_range.start.0 > position.row)
16134 .or_else(|| {
16135 snapshot
16136 .buffer_snapshot()
16137 .diff_hunks_in_range(Point::zero()..position)
16138 .find(|hunk| hunk.row_range.end.0 < position.row)
16139 })
16140 }
16141
16142 fn go_to_prev_hunk(
16143 &mut self,
16144 _: &GoToPreviousHunk,
16145 window: &mut Window,
16146 cx: &mut Context<Self>,
16147 ) {
16148 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16149 let snapshot = self.snapshot(window, cx);
16150 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16151 self.go_to_hunk_before_or_after_position(
16152 &snapshot,
16153 selection.head(),
16154 Direction::Prev,
16155 window,
16156 cx,
16157 );
16158 }
16159
16160 fn hunk_before_position(
16161 &mut self,
16162 snapshot: &EditorSnapshot,
16163 position: Point,
16164 ) -> Option<MultiBufferRow> {
16165 snapshot
16166 .buffer_snapshot()
16167 .diff_hunk_before(position)
16168 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16169 }
16170
16171 fn go_to_next_change(
16172 &mut self,
16173 _: &GoToNextChange,
16174 window: &mut Window,
16175 cx: &mut Context<Self>,
16176 ) {
16177 if let Some(selections) = self
16178 .change_list
16179 .next_change(1, Direction::Next)
16180 .map(|s| s.to_vec())
16181 {
16182 self.change_selections(Default::default(), window, cx, |s| {
16183 let map = s.display_map();
16184 s.select_display_ranges(selections.iter().map(|a| {
16185 let point = a.to_display_point(&map);
16186 point..point
16187 }))
16188 })
16189 }
16190 }
16191
16192 fn go_to_previous_change(
16193 &mut self,
16194 _: &GoToPreviousChange,
16195 window: &mut Window,
16196 cx: &mut Context<Self>,
16197 ) {
16198 if let Some(selections) = self
16199 .change_list
16200 .next_change(1, Direction::Prev)
16201 .map(|s| s.to_vec())
16202 {
16203 self.change_selections(Default::default(), window, cx, |s| {
16204 let map = s.display_map();
16205 s.select_display_ranges(selections.iter().map(|a| {
16206 let point = a.to_display_point(&map);
16207 point..point
16208 }))
16209 })
16210 }
16211 }
16212
16213 pub fn go_to_next_document_highlight(
16214 &mut self,
16215 _: &GoToNextDocumentHighlight,
16216 window: &mut Window,
16217 cx: &mut Context<Self>,
16218 ) {
16219 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16220 }
16221
16222 pub fn go_to_prev_document_highlight(
16223 &mut self,
16224 _: &GoToPreviousDocumentHighlight,
16225 window: &mut Window,
16226 cx: &mut Context<Self>,
16227 ) {
16228 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16229 }
16230
16231 pub fn go_to_document_highlight_before_or_after_position(
16232 &mut self,
16233 direction: Direction,
16234 window: &mut Window,
16235 cx: &mut Context<Editor>,
16236 ) {
16237 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16238 let snapshot = self.snapshot(window, cx);
16239 let buffer = &snapshot.buffer_snapshot();
16240 let position = self
16241 .selections
16242 .newest::<Point>(&snapshot.display_snapshot)
16243 .head();
16244 let anchor_position = buffer.anchor_after(position);
16245
16246 // Get all document highlights (both read and write)
16247 let mut all_highlights = Vec::new();
16248
16249 if let Some((_, read_highlights)) = self
16250 .background_highlights
16251 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16252 {
16253 all_highlights.extend(read_highlights.iter());
16254 }
16255
16256 if let Some((_, write_highlights)) = self
16257 .background_highlights
16258 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16259 {
16260 all_highlights.extend(write_highlights.iter());
16261 }
16262
16263 if all_highlights.is_empty() {
16264 return;
16265 }
16266
16267 // Sort highlights by position
16268 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16269
16270 let target_highlight = match direction {
16271 Direction::Next => {
16272 // Find the first highlight after the current position
16273 all_highlights
16274 .iter()
16275 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16276 }
16277 Direction::Prev => {
16278 // Find the last highlight before the current position
16279 all_highlights
16280 .iter()
16281 .rev()
16282 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16283 }
16284 };
16285
16286 if let Some(highlight) = target_highlight {
16287 let destination = highlight.start.to_point(buffer);
16288 let autoscroll = Autoscroll::center();
16289
16290 self.unfold_ranges(&[destination..destination], false, false, cx);
16291 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16292 s.select_ranges([destination..destination]);
16293 });
16294 }
16295 }
16296
16297 fn go_to_line<T: 'static>(
16298 &mut self,
16299 position: Anchor,
16300 highlight_color: Option<Hsla>,
16301 window: &mut Window,
16302 cx: &mut Context<Self>,
16303 ) {
16304 let snapshot = self.snapshot(window, cx).display_snapshot;
16305 let position = position.to_point(&snapshot.buffer_snapshot());
16306 let start = snapshot
16307 .buffer_snapshot()
16308 .clip_point(Point::new(position.row, 0), Bias::Left);
16309 let end = start + Point::new(1, 0);
16310 let start = snapshot.buffer_snapshot().anchor_before(start);
16311 let end = snapshot.buffer_snapshot().anchor_before(end);
16312
16313 self.highlight_rows::<T>(
16314 start..end,
16315 highlight_color
16316 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16317 Default::default(),
16318 cx,
16319 );
16320
16321 if self.buffer.read(cx).is_singleton() {
16322 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16323 }
16324 }
16325
16326 pub fn go_to_definition(
16327 &mut self,
16328 _: &GoToDefinition,
16329 window: &mut Window,
16330 cx: &mut Context<Self>,
16331 ) -> Task<Result<Navigated>> {
16332 let definition =
16333 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16334 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16335 cx.spawn_in(window, async move |editor, cx| {
16336 if definition.await? == Navigated::Yes {
16337 return Ok(Navigated::Yes);
16338 }
16339 match fallback_strategy {
16340 GoToDefinitionFallback::None => Ok(Navigated::No),
16341 GoToDefinitionFallback::FindAllReferences => {
16342 match editor.update_in(cx, |editor, window, cx| {
16343 editor.find_all_references(&FindAllReferences, window, cx)
16344 })? {
16345 Some(references) => references.await,
16346 None => Ok(Navigated::No),
16347 }
16348 }
16349 }
16350 })
16351 }
16352
16353 pub fn go_to_declaration(
16354 &mut self,
16355 _: &GoToDeclaration,
16356 window: &mut Window,
16357 cx: &mut Context<Self>,
16358 ) -> Task<Result<Navigated>> {
16359 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16360 }
16361
16362 pub fn go_to_declaration_split(
16363 &mut self,
16364 _: &GoToDeclaration,
16365 window: &mut Window,
16366 cx: &mut Context<Self>,
16367 ) -> Task<Result<Navigated>> {
16368 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16369 }
16370
16371 pub fn go_to_implementation(
16372 &mut self,
16373 _: &GoToImplementation,
16374 window: &mut Window,
16375 cx: &mut Context<Self>,
16376 ) -> Task<Result<Navigated>> {
16377 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16378 }
16379
16380 pub fn go_to_implementation_split(
16381 &mut self,
16382 _: &GoToImplementationSplit,
16383 window: &mut Window,
16384 cx: &mut Context<Self>,
16385 ) -> Task<Result<Navigated>> {
16386 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16387 }
16388
16389 pub fn go_to_type_definition(
16390 &mut self,
16391 _: &GoToTypeDefinition,
16392 window: &mut Window,
16393 cx: &mut Context<Self>,
16394 ) -> Task<Result<Navigated>> {
16395 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16396 }
16397
16398 pub fn go_to_definition_split(
16399 &mut self,
16400 _: &GoToDefinitionSplit,
16401 window: &mut Window,
16402 cx: &mut Context<Self>,
16403 ) -> Task<Result<Navigated>> {
16404 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16405 }
16406
16407 pub fn go_to_type_definition_split(
16408 &mut self,
16409 _: &GoToTypeDefinitionSplit,
16410 window: &mut Window,
16411 cx: &mut Context<Self>,
16412 ) -> Task<Result<Navigated>> {
16413 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16414 }
16415
16416 fn go_to_definition_of_kind(
16417 &mut self,
16418 kind: GotoDefinitionKind,
16419 split: bool,
16420 window: &mut Window,
16421 cx: &mut Context<Self>,
16422 ) -> Task<Result<Navigated>> {
16423 let Some(provider) = self.semantics_provider.clone() else {
16424 return Task::ready(Ok(Navigated::No));
16425 };
16426 let head = self
16427 .selections
16428 .newest::<usize>(&self.display_snapshot(cx))
16429 .head();
16430 let buffer = self.buffer.read(cx);
16431 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16432 return Task::ready(Ok(Navigated::No));
16433 };
16434 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16435 return Task::ready(Ok(Navigated::No));
16436 };
16437
16438 cx.spawn_in(window, async move |editor, cx| {
16439 let Some(definitions) = definitions.await? else {
16440 return Ok(Navigated::No);
16441 };
16442 let navigated = editor
16443 .update_in(cx, |editor, window, cx| {
16444 editor.navigate_to_hover_links(
16445 Some(kind),
16446 definitions
16447 .into_iter()
16448 .filter(|location| {
16449 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16450 })
16451 .map(HoverLink::Text)
16452 .collect::<Vec<_>>(),
16453 split,
16454 window,
16455 cx,
16456 )
16457 })?
16458 .await?;
16459 anyhow::Ok(navigated)
16460 })
16461 }
16462
16463 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16464 let selection = self.selections.newest_anchor();
16465 let head = selection.head();
16466 let tail = selection.tail();
16467
16468 let Some((buffer, start_position)) =
16469 self.buffer.read(cx).text_anchor_for_position(head, cx)
16470 else {
16471 return;
16472 };
16473
16474 let end_position = if head != tail {
16475 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16476 return;
16477 };
16478 Some(pos)
16479 } else {
16480 None
16481 };
16482
16483 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16484 let url = if let Some(end_pos) = end_position {
16485 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16486 } else {
16487 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16488 };
16489
16490 if let Some(url) = url {
16491 cx.update(|window, cx| {
16492 if parse_zed_link(&url, cx).is_some() {
16493 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16494 } else {
16495 cx.open_url(&url);
16496 }
16497 })?;
16498 }
16499
16500 anyhow::Ok(())
16501 });
16502
16503 url_finder.detach();
16504 }
16505
16506 pub fn open_selected_filename(
16507 &mut self,
16508 _: &OpenSelectedFilename,
16509 window: &mut Window,
16510 cx: &mut Context<Self>,
16511 ) {
16512 let Some(workspace) = self.workspace() else {
16513 return;
16514 };
16515
16516 let position = self.selections.newest_anchor().head();
16517
16518 let Some((buffer, buffer_position)) =
16519 self.buffer.read(cx).text_anchor_for_position(position, cx)
16520 else {
16521 return;
16522 };
16523
16524 let project = self.project.clone();
16525
16526 cx.spawn_in(window, async move |_, cx| {
16527 let result = find_file(&buffer, project, buffer_position, cx).await;
16528
16529 if let Some((_, path)) = result {
16530 workspace
16531 .update_in(cx, |workspace, window, cx| {
16532 workspace.open_resolved_path(path, window, cx)
16533 })?
16534 .await?;
16535 }
16536 anyhow::Ok(())
16537 })
16538 .detach();
16539 }
16540
16541 pub(crate) fn navigate_to_hover_links(
16542 &mut self,
16543 kind: Option<GotoDefinitionKind>,
16544 definitions: Vec<HoverLink>,
16545 split: bool,
16546 window: &mut Window,
16547 cx: &mut Context<Editor>,
16548 ) -> Task<Result<Navigated>> {
16549 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16550 let mut first_url_or_file = None;
16551 let definitions: Vec<_> = definitions
16552 .into_iter()
16553 .filter_map(|def| match def {
16554 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16555 HoverLink::InlayHint(lsp_location, server_id) => {
16556 let computation =
16557 self.compute_target_location(lsp_location, server_id, window, cx);
16558 Some(cx.background_spawn(computation))
16559 }
16560 HoverLink::Url(url) => {
16561 first_url_or_file = Some(Either::Left(url));
16562 None
16563 }
16564 HoverLink::File(path) => {
16565 first_url_or_file = Some(Either::Right(path));
16566 None
16567 }
16568 })
16569 .collect();
16570
16571 let workspace = self.workspace();
16572
16573 cx.spawn_in(window, async move |editor, cx| {
16574 let locations: Vec<Location> = future::join_all(definitions)
16575 .await
16576 .into_iter()
16577 .filter_map(|location| location.transpose())
16578 .collect::<Result<_>>()
16579 .context("location tasks")?;
16580 let mut locations = cx.update(|_, cx| {
16581 locations
16582 .into_iter()
16583 .map(|location| {
16584 let buffer = location.buffer.read(cx);
16585 (location.buffer, location.range.to_point(buffer))
16586 })
16587 .into_group_map()
16588 })?;
16589 let mut num_locations = 0;
16590 for ranges in locations.values_mut() {
16591 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16592 ranges.dedup();
16593 num_locations += ranges.len();
16594 }
16595
16596 if num_locations > 1 {
16597 let Some(workspace) = workspace else {
16598 return Ok(Navigated::No);
16599 };
16600
16601 let tab_kind = match kind {
16602 Some(GotoDefinitionKind::Implementation) => "Implementations",
16603 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16604 Some(GotoDefinitionKind::Declaration) => "Declarations",
16605 Some(GotoDefinitionKind::Type) => "Types",
16606 };
16607 let title = editor
16608 .update_in(cx, |_, _, cx| {
16609 let target = locations
16610 .iter()
16611 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16612 .map(|(buffer, location)| {
16613 buffer
16614 .read(cx)
16615 .text_for_range(location.clone())
16616 .collect::<String>()
16617 })
16618 .filter(|text| !text.contains('\n'))
16619 .unique()
16620 .take(3)
16621 .join(", ");
16622 if target.is_empty() {
16623 tab_kind.to_owned()
16624 } else {
16625 format!("{tab_kind} for {target}")
16626 }
16627 })
16628 .context("buffer title")?;
16629
16630 let opened = workspace
16631 .update_in(cx, |workspace, window, cx| {
16632 Self::open_locations_in_multibuffer(
16633 workspace,
16634 locations,
16635 title,
16636 split,
16637 MultibufferSelectionMode::First,
16638 window,
16639 cx,
16640 )
16641 })
16642 .is_ok();
16643
16644 anyhow::Ok(Navigated::from_bool(opened))
16645 } else if num_locations == 0 {
16646 // If there is one url or file, open it directly
16647 match first_url_or_file {
16648 Some(Either::Left(url)) => {
16649 cx.update(|_, cx| cx.open_url(&url))?;
16650 Ok(Navigated::Yes)
16651 }
16652 Some(Either::Right(path)) => {
16653 let Some(workspace) = workspace else {
16654 return Ok(Navigated::No);
16655 };
16656
16657 workspace
16658 .update_in(cx, |workspace, window, cx| {
16659 workspace.open_resolved_path(path, window, cx)
16660 })?
16661 .await?;
16662 Ok(Navigated::Yes)
16663 }
16664 None => Ok(Navigated::No),
16665 }
16666 } else {
16667 let Some(workspace) = workspace else {
16668 return Ok(Navigated::No);
16669 };
16670
16671 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16672 let target_range = target_ranges.first().unwrap().clone();
16673
16674 editor.update_in(cx, |editor, window, cx| {
16675 let range = target_range.to_point(target_buffer.read(cx));
16676 let range = editor.range_for_match(&range, false);
16677 let range = collapse_multiline_range(range);
16678
16679 if !split
16680 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16681 {
16682 editor.go_to_singleton_buffer_range(range, window, cx);
16683 } else {
16684 let pane = workspace.read(cx).active_pane().clone();
16685 window.defer(cx, move |window, cx| {
16686 let target_editor: Entity<Self> =
16687 workspace.update(cx, |workspace, cx| {
16688 let pane = if split {
16689 workspace.adjacent_pane(window, cx)
16690 } else {
16691 workspace.active_pane().clone()
16692 };
16693
16694 workspace.open_project_item(
16695 pane,
16696 target_buffer.clone(),
16697 true,
16698 true,
16699 window,
16700 cx,
16701 )
16702 });
16703 target_editor.update(cx, |target_editor, cx| {
16704 // When selecting a definition in a different buffer, disable the nav history
16705 // to avoid creating a history entry at the previous cursor location.
16706 pane.update(cx, |pane, _| pane.disable_history());
16707 target_editor.go_to_singleton_buffer_range(range, window, cx);
16708 pane.update(cx, |pane, _| pane.enable_history());
16709 });
16710 });
16711 }
16712 Navigated::Yes
16713 })
16714 }
16715 })
16716 }
16717
16718 fn compute_target_location(
16719 &self,
16720 lsp_location: lsp::Location,
16721 server_id: LanguageServerId,
16722 window: &mut Window,
16723 cx: &mut Context<Self>,
16724 ) -> Task<anyhow::Result<Option<Location>>> {
16725 let Some(project) = self.project.clone() else {
16726 return Task::ready(Ok(None));
16727 };
16728
16729 cx.spawn_in(window, async move |editor, cx| {
16730 let location_task = editor.update(cx, |_, cx| {
16731 project.update(cx, |project, cx| {
16732 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16733 })
16734 })?;
16735 let location = Some({
16736 let target_buffer_handle = location_task.await.context("open local buffer")?;
16737 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16738 let target_start = target_buffer
16739 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16740 let target_end = target_buffer
16741 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16742 target_buffer.anchor_after(target_start)
16743 ..target_buffer.anchor_before(target_end)
16744 })?;
16745 Location {
16746 buffer: target_buffer_handle,
16747 range,
16748 }
16749 });
16750 Ok(location)
16751 })
16752 }
16753
16754 fn go_to_next_reference(
16755 &mut self,
16756 _: &GoToNextReference,
16757 window: &mut Window,
16758 cx: &mut Context<Self>,
16759 ) {
16760 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
16761 if let Some(task) = task {
16762 task.detach();
16763 };
16764 }
16765
16766 fn go_to_prev_reference(
16767 &mut self,
16768 _: &GoToPreviousReference,
16769 window: &mut Window,
16770 cx: &mut Context<Self>,
16771 ) {
16772 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
16773 if let Some(task) = task {
16774 task.detach();
16775 };
16776 }
16777
16778 pub fn go_to_reference_before_or_after_position(
16779 &mut self,
16780 direction: Direction,
16781 count: usize,
16782 window: &mut Window,
16783 cx: &mut Context<Self>,
16784 ) -> Option<Task<Result<()>>> {
16785 let selection = self.selections.newest_anchor();
16786 let head = selection.head();
16787
16788 let multi_buffer = self.buffer.read(cx);
16789
16790 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
16791 let workspace = self.workspace()?;
16792 let project = workspace.read(cx).project().clone();
16793 let references =
16794 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
16795 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
16796 let Some(locations) = references.await? else {
16797 return Ok(());
16798 };
16799
16800 if locations.is_empty() {
16801 // totally normal - the cursor may be on something which is not
16802 // a symbol (e.g. a keyword)
16803 log::info!("no references found under cursor");
16804 return Ok(());
16805 }
16806
16807 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
16808
16809 let multi_buffer_snapshot =
16810 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
16811
16812 let (locations, current_location_index) =
16813 multi_buffer.update(cx, |multi_buffer, cx| {
16814 let mut locations = locations
16815 .into_iter()
16816 .filter_map(|loc| {
16817 let start = multi_buffer.buffer_anchor_to_anchor(
16818 &loc.buffer,
16819 loc.range.start,
16820 cx,
16821 )?;
16822 let end = multi_buffer.buffer_anchor_to_anchor(
16823 &loc.buffer,
16824 loc.range.end,
16825 cx,
16826 )?;
16827 Some(start..end)
16828 })
16829 .collect::<Vec<_>>();
16830
16831 // There is an O(n) implementation, but given this list will be
16832 // small (usually <100 items), the extra O(log(n)) factor isn't
16833 // worth the (surprisingly large amount of) extra complexity.
16834 locations
16835 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
16836
16837 let head_offset = head.to_offset(&multi_buffer_snapshot);
16838
16839 let current_location_index = locations.iter().position(|loc| {
16840 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
16841 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
16842 });
16843
16844 (locations, current_location_index)
16845 })?;
16846
16847 let Some(current_location_index) = current_location_index else {
16848 // This indicates something has gone wrong, because we already
16849 // handle the "no references" case above
16850 log::error!(
16851 "failed to find current reference under cursor. Total references: {}",
16852 locations.len()
16853 );
16854 return Ok(());
16855 };
16856
16857 let destination_location_index = match direction {
16858 Direction::Next => (current_location_index + count) % locations.len(),
16859 Direction::Prev => {
16860 (current_location_index + locations.len() - count % locations.len())
16861 % locations.len()
16862 }
16863 };
16864
16865 // TODO(cameron): is this needed?
16866 // the thinking is to avoid "jumping to the current location" (avoid
16867 // polluting "jumplist" in vim terms)
16868 if current_location_index == destination_location_index {
16869 return Ok(());
16870 }
16871
16872 let Range { start, end } = locations[destination_location_index];
16873
16874 editor.update_in(cx, |editor, window, cx| {
16875 let effects = SelectionEffects::default();
16876
16877 editor.unfold_ranges(&[start..end], false, false, cx);
16878 editor.change_selections(effects, window, cx, |s| {
16879 s.select_ranges([start..start]);
16880 });
16881 })?;
16882
16883 Ok(())
16884 }))
16885 }
16886
16887 pub fn find_all_references(
16888 &mut self,
16889 _: &FindAllReferences,
16890 window: &mut Window,
16891 cx: &mut Context<Self>,
16892 ) -> Option<Task<Result<Navigated>>> {
16893 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16894 let multi_buffer = self.buffer.read(cx);
16895 let head = selection.head();
16896
16897 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16898 let head_anchor = multi_buffer_snapshot.anchor_at(
16899 head,
16900 if head < selection.tail() {
16901 Bias::Right
16902 } else {
16903 Bias::Left
16904 },
16905 );
16906
16907 match self
16908 .find_all_references_task_sources
16909 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16910 {
16911 Ok(_) => {
16912 log::info!(
16913 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16914 );
16915 return None;
16916 }
16917 Err(i) => {
16918 self.find_all_references_task_sources.insert(i, head_anchor);
16919 }
16920 }
16921
16922 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16923 let workspace = self.workspace()?;
16924 let project = workspace.read(cx).project().clone();
16925 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16926 Some(cx.spawn_in(window, async move |editor, cx| {
16927 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16928 if let Ok(i) = editor
16929 .find_all_references_task_sources
16930 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16931 {
16932 editor.find_all_references_task_sources.remove(i);
16933 }
16934 });
16935
16936 let Some(locations) = references.await? else {
16937 return anyhow::Ok(Navigated::No);
16938 };
16939 let mut locations = cx.update(|_, cx| {
16940 locations
16941 .into_iter()
16942 .map(|location| {
16943 let buffer = location.buffer.read(cx);
16944 (location.buffer, location.range.to_point(buffer))
16945 })
16946 .into_group_map()
16947 })?;
16948 if locations.is_empty() {
16949 return anyhow::Ok(Navigated::No);
16950 }
16951 for ranges in locations.values_mut() {
16952 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16953 ranges.dedup();
16954 }
16955
16956 workspace.update_in(cx, |workspace, window, cx| {
16957 let target = locations
16958 .iter()
16959 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16960 .map(|(buffer, location)| {
16961 buffer
16962 .read(cx)
16963 .text_for_range(location.clone())
16964 .collect::<String>()
16965 })
16966 .filter(|text| !text.contains('\n'))
16967 .unique()
16968 .take(3)
16969 .join(", ");
16970 let title = if target.is_empty() {
16971 "References".to_owned()
16972 } else {
16973 format!("References to {target}")
16974 };
16975 Self::open_locations_in_multibuffer(
16976 workspace,
16977 locations,
16978 title,
16979 false,
16980 MultibufferSelectionMode::First,
16981 window,
16982 cx,
16983 );
16984 Navigated::Yes
16985 })
16986 }))
16987 }
16988
16989 /// Opens a multibuffer with the given project locations in it
16990 pub fn open_locations_in_multibuffer(
16991 workspace: &mut Workspace,
16992 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16993 title: String,
16994 split: bool,
16995 multibuffer_selection_mode: MultibufferSelectionMode,
16996 window: &mut Window,
16997 cx: &mut Context<Workspace>,
16998 ) {
16999 if locations.is_empty() {
17000 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17001 return;
17002 }
17003
17004 let capability = workspace.project().read(cx).capability();
17005 let mut ranges = <Vec<Range<Anchor>>>::new();
17006
17007 // a key to find existing multibuffer editors with the same set of locations
17008 // to prevent us from opening more and more multibuffer tabs for searches and the like
17009 let mut key = (title.clone(), vec![]);
17010 let excerpt_buffer = cx.new(|cx| {
17011 let key = &mut key.1;
17012 let mut multibuffer = MultiBuffer::new(capability);
17013 for (buffer, mut ranges_for_buffer) in locations {
17014 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17015 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17016 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17017 PathKey::for_buffer(&buffer, cx),
17018 buffer.clone(),
17019 ranges_for_buffer,
17020 multibuffer_context_lines(cx),
17021 cx,
17022 );
17023 ranges.extend(new_ranges)
17024 }
17025
17026 multibuffer.with_title(title)
17027 });
17028 let existing = workspace.active_pane().update(cx, |pane, cx| {
17029 pane.items()
17030 .filter_map(|item| item.downcast::<Editor>())
17031 .find(|editor| {
17032 editor
17033 .read(cx)
17034 .lookup_key
17035 .as_ref()
17036 .and_then(|it| {
17037 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17038 })
17039 .is_some_and(|it| *it == key)
17040 })
17041 });
17042 let editor = existing.unwrap_or_else(|| {
17043 cx.new(|cx| {
17044 let mut editor = Editor::for_multibuffer(
17045 excerpt_buffer,
17046 Some(workspace.project().clone()),
17047 window,
17048 cx,
17049 );
17050 editor.lookup_key = Some(Box::new(key));
17051 editor
17052 })
17053 });
17054 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17055 MultibufferSelectionMode::First => {
17056 if let Some(first_range) = ranges.first() {
17057 editor.change_selections(
17058 SelectionEffects::no_scroll(),
17059 window,
17060 cx,
17061 |selections| {
17062 selections.clear_disjoint();
17063 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17064 },
17065 );
17066 }
17067 editor.highlight_background::<Self>(
17068 &ranges,
17069 |theme| theme.colors().editor_highlighted_line_background,
17070 cx,
17071 );
17072 }
17073 MultibufferSelectionMode::All => {
17074 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17075 selections.clear_disjoint();
17076 selections.select_anchor_ranges(ranges);
17077 });
17078 }
17079 });
17080
17081 let item = Box::new(editor);
17082 let item_id = item.item_id();
17083
17084 if split {
17085 let pane = workspace.adjacent_pane(window, cx);
17086 workspace.add_item(pane, item, None, true, true, window, cx);
17087 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17088 let (preview_item_id, preview_item_idx) =
17089 workspace.active_pane().read_with(cx, |pane, _| {
17090 (pane.preview_item_id(), pane.preview_item_idx())
17091 });
17092
17093 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17094
17095 if let Some(preview_item_id) = preview_item_id {
17096 workspace.active_pane().update(cx, |pane, cx| {
17097 pane.remove_item(preview_item_id, false, false, window, cx);
17098 });
17099 }
17100 } else {
17101 workspace.add_item_to_active_pane(item, None, true, window, cx);
17102 }
17103 workspace.active_pane().update(cx, |pane, cx| {
17104 pane.set_preview_item_id(Some(item_id), cx);
17105 });
17106 }
17107
17108 pub fn rename(
17109 &mut self,
17110 _: &Rename,
17111 window: &mut Window,
17112 cx: &mut Context<Self>,
17113 ) -> Option<Task<Result<()>>> {
17114 use language::ToOffset as _;
17115
17116 let provider = self.semantics_provider.clone()?;
17117 let selection = self.selections.newest_anchor().clone();
17118 let (cursor_buffer, cursor_buffer_position) = self
17119 .buffer
17120 .read(cx)
17121 .text_anchor_for_position(selection.head(), cx)?;
17122 let (tail_buffer, cursor_buffer_position_end) = self
17123 .buffer
17124 .read(cx)
17125 .text_anchor_for_position(selection.tail(), cx)?;
17126 if tail_buffer != cursor_buffer {
17127 return None;
17128 }
17129
17130 let snapshot = cursor_buffer.read(cx).snapshot();
17131 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17132 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17133 let prepare_rename = provider
17134 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17135 .unwrap_or_else(|| Task::ready(Ok(None)));
17136 drop(snapshot);
17137
17138 Some(cx.spawn_in(window, async move |this, cx| {
17139 let rename_range = if let Some(range) = prepare_rename.await? {
17140 Some(range)
17141 } else {
17142 this.update(cx, |this, cx| {
17143 let buffer = this.buffer.read(cx).snapshot(cx);
17144 let mut buffer_highlights = this
17145 .document_highlights_for_position(selection.head(), &buffer)
17146 .filter(|highlight| {
17147 highlight.start.excerpt_id == selection.head().excerpt_id
17148 && highlight.end.excerpt_id == selection.head().excerpt_id
17149 });
17150 buffer_highlights
17151 .next()
17152 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17153 })?
17154 };
17155 if let Some(rename_range) = rename_range {
17156 this.update_in(cx, |this, window, cx| {
17157 let snapshot = cursor_buffer.read(cx).snapshot();
17158 let rename_buffer_range = rename_range.to_offset(&snapshot);
17159 let cursor_offset_in_rename_range =
17160 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17161 let cursor_offset_in_rename_range_end =
17162 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17163
17164 this.take_rename(false, window, cx);
17165 let buffer = this.buffer.read(cx).read(cx);
17166 let cursor_offset = selection.head().to_offset(&buffer);
17167 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17168 let rename_end = rename_start + rename_buffer_range.len();
17169 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17170 let mut old_highlight_id = None;
17171 let old_name: Arc<str> = buffer
17172 .chunks(rename_start..rename_end, true)
17173 .map(|chunk| {
17174 if old_highlight_id.is_none() {
17175 old_highlight_id = chunk.syntax_highlight_id;
17176 }
17177 chunk.text
17178 })
17179 .collect::<String>()
17180 .into();
17181
17182 drop(buffer);
17183
17184 // Position the selection in the rename editor so that it matches the current selection.
17185 this.show_local_selections = false;
17186 let rename_editor = cx.new(|cx| {
17187 let mut editor = Editor::single_line(window, cx);
17188 editor.buffer.update(cx, |buffer, cx| {
17189 buffer.edit([(0..0, old_name.clone())], None, cx)
17190 });
17191 let rename_selection_range = match cursor_offset_in_rename_range
17192 .cmp(&cursor_offset_in_rename_range_end)
17193 {
17194 Ordering::Equal => {
17195 editor.select_all(&SelectAll, window, cx);
17196 return editor;
17197 }
17198 Ordering::Less => {
17199 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17200 }
17201 Ordering::Greater => {
17202 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17203 }
17204 };
17205 if rename_selection_range.end > old_name.len() {
17206 editor.select_all(&SelectAll, window, cx);
17207 } else {
17208 editor.change_selections(Default::default(), window, cx, |s| {
17209 s.select_ranges([rename_selection_range]);
17210 });
17211 }
17212 editor
17213 });
17214 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17215 if e == &EditorEvent::Focused {
17216 cx.emit(EditorEvent::FocusedIn)
17217 }
17218 })
17219 .detach();
17220
17221 let write_highlights =
17222 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17223 let read_highlights =
17224 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17225 let ranges = write_highlights
17226 .iter()
17227 .flat_map(|(_, ranges)| ranges.iter())
17228 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17229 .cloned()
17230 .collect();
17231
17232 this.highlight_text::<Rename>(
17233 ranges,
17234 HighlightStyle {
17235 fade_out: Some(0.6),
17236 ..Default::default()
17237 },
17238 cx,
17239 );
17240 let rename_focus_handle = rename_editor.focus_handle(cx);
17241 window.focus(&rename_focus_handle);
17242 let block_id = this.insert_blocks(
17243 [BlockProperties {
17244 style: BlockStyle::Flex,
17245 placement: BlockPlacement::Below(range.start),
17246 height: Some(1),
17247 render: Arc::new({
17248 let rename_editor = rename_editor.clone();
17249 move |cx: &mut BlockContext| {
17250 let mut text_style = cx.editor_style.text.clone();
17251 if let Some(highlight_style) = old_highlight_id
17252 .and_then(|h| h.style(&cx.editor_style.syntax))
17253 {
17254 text_style = text_style.highlight(highlight_style);
17255 }
17256 div()
17257 .block_mouse_except_scroll()
17258 .pl(cx.anchor_x)
17259 .child(EditorElement::new(
17260 &rename_editor,
17261 EditorStyle {
17262 background: cx.theme().system().transparent,
17263 local_player: cx.editor_style.local_player,
17264 text: text_style,
17265 scrollbar_width: cx.editor_style.scrollbar_width,
17266 syntax: cx.editor_style.syntax.clone(),
17267 status: cx.editor_style.status.clone(),
17268 inlay_hints_style: HighlightStyle {
17269 font_weight: Some(FontWeight::BOLD),
17270 ..make_inlay_hints_style(cx.app)
17271 },
17272 edit_prediction_styles: make_suggestion_styles(
17273 cx.app,
17274 ),
17275 ..EditorStyle::default()
17276 },
17277 ))
17278 .into_any_element()
17279 }
17280 }),
17281 priority: 0,
17282 }],
17283 Some(Autoscroll::fit()),
17284 cx,
17285 )[0];
17286 this.pending_rename = Some(RenameState {
17287 range,
17288 old_name,
17289 editor: rename_editor,
17290 block_id,
17291 });
17292 })?;
17293 }
17294
17295 Ok(())
17296 }))
17297 }
17298
17299 pub fn confirm_rename(
17300 &mut self,
17301 _: &ConfirmRename,
17302 window: &mut Window,
17303 cx: &mut Context<Self>,
17304 ) -> Option<Task<Result<()>>> {
17305 let rename = self.take_rename(false, window, cx)?;
17306 let workspace = self.workspace()?.downgrade();
17307 let (buffer, start) = self
17308 .buffer
17309 .read(cx)
17310 .text_anchor_for_position(rename.range.start, cx)?;
17311 let (end_buffer, _) = self
17312 .buffer
17313 .read(cx)
17314 .text_anchor_for_position(rename.range.end, cx)?;
17315 if buffer != end_buffer {
17316 return None;
17317 }
17318
17319 let old_name = rename.old_name;
17320 let new_name = rename.editor.read(cx).text(cx);
17321
17322 let rename = self.semantics_provider.as_ref()?.perform_rename(
17323 &buffer,
17324 start,
17325 new_name.clone(),
17326 cx,
17327 )?;
17328
17329 Some(cx.spawn_in(window, async move |editor, cx| {
17330 let project_transaction = rename.await?;
17331 Self::open_project_transaction(
17332 &editor,
17333 workspace,
17334 project_transaction,
17335 format!("Rename: {} → {}", old_name, new_name),
17336 cx,
17337 )
17338 .await?;
17339
17340 editor.update(cx, |editor, cx| {
17341 editor.refresh_document_highlights(cx);
17342 })?;
17343 Ok(())
17344 }))
17345 }
17346
17347 fn take_rename(
17348 &mut self,
17349 moving_cursor: bool,
17350 window: &mut Window,
17351 cx: &mut Context<Self>,
17352 ) -> Option<RenameState> {
17353 let rename = self.pending_rename.take()?;
17354 if rename.editor.focus_handle(cx).is_focused(window) {
17355 window.focus(&self.focus_handle);
17356 }
17357
17358 self.remove_blocks(
17359 [rename.block_id].into_iter().collect(),
17360 Some(Autoscroll::fit()),
17361 cx,
17362 );
17363 self.clear_highlights::<Rename>(cx);
17364 self.show_local_selections = true;
17365
17366 if moving_cursor {
17367 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17368 editor
17369 .selections
17370 .newest::<usize>(&editor.display_snapshot(cx))
17371 .head()
17372 });
17373
17374 // Update the selection to match the position of the selection inside
17375 // the rename editor.
17376 let snapshot = self.buffer.read(cx).read(cx);
17377 let rename_range = rename.range.to_offset(&snapshot);
17378 let cursor_in_editor = snapshot
17379 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17380 .min(rename_range.end);
17381 drop(snapshot);
17382
17383 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17384 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17385 });
17386 } else {
17387 self.refresh_document_highlights(cx);
17388 }
17389
17390 Some(rename)
17391 }
17392
17393 pub fn pending_rename(&self) -> Option<&RenameState> {
17394 self.pending_rename.as_ref()
17395 }
17396
17397 fn format(
17398 &mut self,
17399 _: &Format,
17400 window: &mut Window,
17401 cx: &mut Context<Self>,
17402 ) -> Option<Task<Result<()>>> {
17403 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17404
17405 let project = match &self.project {
17406 Some(project) => project.clone(),
17407 None => return None,
17408 };
17409
17410 Some(self.perform_format(
17411 project,
17412 FormatTrigger::Manual,
17413 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17414 window,
17415 cx,
17416 ))
17417 }
17418
17419 fn format_selections(
17420 &mut self,
17421 _: &FormatSelections,
17422 window: &mut Window,
17423 cx: &mut Context<Self>,
17424 ) -> Option<Task<Result<()>>> {
17425 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17426
17427 let project = match &self.project {
17428 Some(project) => project.clone(),
17429 None => return None,
17430 };
17431
17432 let ranges = self
17433 .selections
17434 .all_adjusted(&self.display_snapshot(cx))
17435 .into_iter()
17436 .map(|selection| selection.range())
17437 .collect_vec();
17438
17439 Some(self.perform_format(
17440 project,
17441 FormatTrigger::Manual,
17442 FormatTarget::Ranges(ranges),
17443 window,
17444 cx,
17445 ))
17446 }
17447
17448 fn perform_format(
17449 &mut self,
17450 project: Entity<Project>,
17451 trigger: FormatTrigger,
17452 target: FormatTarget,
17453 window: &mut Window,
17454 cx: &mut Context<Self>,
17455 ) -> Task<Result<()>> {
17456 let buffer = self.buffer.clone();
17457 let (buffers, target) = match target {
17458 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17459 FormatTarget::Ranges(selection_ranges) => {
17460 let multi_buffer = buffer.read(cx);
17461 let snapshot = multi_buffer.read(cx);
17462 let mut buffers = HashSet::default();
17463 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17464 BTreeMap::new();
17465 for selection_range in selection_ranges {
17466 for (buffer, buffer_range, _) in
17467 snapshot.range_to_buffer_ranges(selection_range)
17468 {
17469 let buffer_id = buffer.remote_id();
17470 let start = buffer.anchor_before(buffer_range.start);
17471 let end = buffer.anchor_after(buffer_range.end);
17472 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17473 buffer_id_to_ranges
17474 .entry(buffer_id)
17475 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17476 .or_insert_with(|| vec![start..end]);
17477 }
17478 }
17479 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17480 }
17481 };
17482
17483 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17484 let selections_prev = transaction_id_prev
17485 .and_then(|transaction_id_prev| {
17486 // default to selections as they were after the last edit, if we have them,
17487 // instead of how they are now.
17488 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17489 // will take you back to where you made the last edit, instead of staying where you scrolled
17490 self.selection_history
17491 .transaction(transaction_id_prev)
17492 .map(|t| t.0.clone())
17493 })
17494 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17495
17496 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17497 let format = project.update(cx, |project, cx| {
17498 project.format(buffers, target, true, trigger, cx)
17499 });
17500
17501 cx.spawn_in(window, async move |editor, cx| {
17502 let transaction = futures::select_biased! {
17503 transaction = format.log_err().fuse() => transaction,
17504 () = timeout => {
17505 log::warn!("timed out waiting for formatting");
17506 None
17507 }
17508 };
17509
17510 buffer
17511 .update(cx, |buffer, cx| {
17512 if let Some(transaction) = transaction
17513 && !buffer.is_singleton()
17514 {
17515 buffer.push_transaction(&transaction.0, cx);
17516 }
17517 cx.notify();
17518 })
17519 .ok();
17520
17521 if let Some(transaction_id_now) =
17522 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17523 {
17524 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17525 if has_new_transaction {
17526 _ = editor.update(cx, |editor, _| {
17527 editor
17528 .selection_history
17529 .insert_transaction(transaction_id_now, selections_prev);
17530 });
17531 }
17532 }
17533
17534 Ok(())
17535 })
17536 }
17537
17538 fn organize_imports(
17539 &mut self,
17540 _: &OrganizeImports,
17541 window: &mut Window,
17542 cx: &mut Context<Self>,
17543 ) -> Option<Task<Result<()>>> {
17544 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17545 let project = match &self.project {
17546 Some(project) => project.clone(),
17547 None => return None,
17548 };
17549 Some(self.perform_code_action_kind(
17550 project,
17551 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17552 window,
17553 cx,
17554 ))
17555 }
17556
17557 fn perform_code_action_kind(
17558 &mut self,
17559 project: Entity<Project>,
17560 kind: CodeActionKind,
17561 window: &mut Window,
17562 cx: &mut Context<Self>,
17563 ) -> Task<Result<()>> {
17564 let buffer = self.buffer.clone();
17565 let buffers = buffer.read(cx).all_buffers();
17566 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17567 let apply_action = project.update(cx, |project, cx| {
17568 project.apply_code_action_kind(buffers, kind, true, cx)
17569 });
17570 cx.spawn_in(window, async move |_, cx| {
17571 let transaction = futures::select_biased! {
17572 () = timeout => {
17573 log::warn!("timed out waiting for executing code action");
17574 None
17575 }
17576 transaction = apply_action.log_err().fuse() => transaction,
17577 };
17578 buffer
17579 .update(cx, |buffer, cx| {
17580 // check if we need this
17581 if let Some(transaction) = transaction
17582 && !buffer.is_singleton()
17583 {
17584 buffer.push_transaction(&transaction.0, cx);
17585 }
17586 cx.notify();
17587 })
17588 .ok();
17589 Ok(())
17590 })
17591 }
17592
17593 pub fn restart_language_server(
17594 &mut self,
17595 _: &RestartLanguageServer,
17596 _: &mut Window,
17597 cx: &mut Context<Self>,
17598 ) {
17599 if let Some(project) = self.project.clone() {
17600 self.buffer.update(cx, |multi_buffer, cx| {
17601 project.update(cx, |project, cx| {
17602 project.restart_language_servers_for_buffers(
17603 multi_buffer.all_buffers().into_iter().collect(),
17604 HashSet::default(),
17605 cx,
17606 );
17607 });
17608 })
17609 }
17610 }
17611
17612 pub fn stop_language_server(
17613 &mut self,
17614 _: &StopLanguageServer,
17615 _: &mut Window,
17616 cx: &mut Context<Self>,
17617 ) {
17618 if let Some(project) = self.project.clone() {
17619 self.buffer.update(cx, |multi_buffer, cx| {
17620 project.update(cx, |project, cx| {
17621 project.stop_language_servers_for_buffers(
17622 multi_buffer.all_buffers().into_iter().collect(),
17623 HashSet::default(),
17624 cx,
17625 );
17626 });
17627 });
17628 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17629 }
17630 }
17631
17632 fn cancel_language_server_work(
17633 workspace: &mut Workspace,
17634 _: &actions::CancelLanguageServerWork,
17635 _: &mut Window,
17636 cx: &mut Context<Workspace>,
17637 ) {
17638 let project = workspace.project();
17639 let buffers = workspace
17640 .active_item(cx)
17641 .and_then(|item| item.act_as::<Editor>(cx))
17642 .map_or(HashSet::default(), |editor| {
17643 editor.read(cx).buffer.read(cx).all_buffers()
17644 });
17645 project.update(cx, |project, cx| {
17646 project.cancel_language_server_work_for_buffers(buffers, cx);
17647 });
17648 }
17649
17650 fn show_character_palette(
17651 &mut self,
17652 _: &ShowCharacterPalette,
17653 window: &mut Window,
17654 _: &mut Context<Self>,
17655 ) {
17656 window.show_character_palette();
17657 }
17658
17659 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17660 if !self.diagnostics_enabled() {
17661 return;
17662 }
17663
17664 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17665 let buffer = self.buffer.read(cx).snapshot(cx);
17666 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17667 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17668 let is_valid = buffer
17669 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17670 .any(|entry| {
17671 entry.diagnostic.is_primary
17672 && !entry.range.is_empty()
17673 && entry.range.start == primary_range_start
17674 && entry.diagnostic.message == active_diagnostics.active_message
17675 });
17676
17677 if !is_valid {
17678 self.dismiss_diagnostics(cx);
17679 }
17680 }
17681 }
17682
17683 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17684 match &self.active_diagnostics {
17685 ActiveDiagnostic::Group(group) => Some(group),
17686 _ => None,
17687 }
17688 }
17689
17690 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17691 if !self.diagnostics_enabled() {
17692 return;
17693 }
17694 self.dismiss_diagnostics(cx);
17695 self.active_diagnostics = ActiveDiagnostic::All;
17696 }
17697
17698 fn activate_diagnostics(
17699 &mut self,
17700 buffer_id: BufferId,
17701 diagnostic: DiagnosticEntryRef<'_, usize>,
17702 window: &mut Window,
17703 cx: &mut Context<Self>,
17704 ) {
17705 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17706 return;
17707 }
17708 self.dismiss_diagnostics(cx);
17709 let snapshot = self.snapshot(window, cx);
17710 let buffer = self.buffer.read(cx).snapshot(cx);
17711 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17712 return;
17713 };
17714
17715 let diagnostic_group = buffer
17716 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17717 .collect::<Vec<_>>();
17718
17719 let blocks =
17720 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17721
17722 let blocks = self.display_map.update(cx, |display_map, cx| {
17723 display_map.insert_blocks(blocks, cx).into_iter().collect()
17724 });
17725 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17726 active_range: buffer.anchor_before(diagnostic.range.start)
17727 ..buffer.anchor_after(diagnostic.range.end),
17728 active_message: diagnostic.diagnostic.message.clone(),
17729 group_id: diagnostic.diagnostic.group_id,
17730 blocks,
17731 });
17732 cx.notify();
17733 }
17734
17735 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17736 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17737 return;
17738 };
17739
17740 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17741 if let ActiveDiagnostic::Group(group) = prev {
17742 self.display_map.update(cx, |display_map, cx| {
17743 display_map.remove_blocks(group.blocks, cx);
17744 });
17745 cx.notify();
17746 }
17747 }
17748
17749 /// Disable inline diagnostics rendering for this editor.
17750 pub fn disable_inline_diagnostics(&mut self) {
17751 self.inline_diagnostics_enabled = false;
17752 self.inline_diagnostics_update = Task::ready(());
17753 self.inline_diagnostics.clear();
17754 }
17755
17756 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17757 self.diagnostics_enabled = false;
17758 self.dismiss_diagnostics(cx);
17759 self.inline_diagnostics_update = Task::ready(());
17760 self.inline_diagnostics.clear();
17761 }
17762
17763 pub fn disable_word_completions(&mut self) {
17764 self.word_completions_enabled = false;
17765 }
17766
17767 pub fn diagnostics_enabled(&self) -> bool {
17768 self.diagnostics_enabled && self.mode.is_full()
17769 }
17770
17771 pub fn inline_diagnostics_enabled(&self) -> bool {
17772 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17773 }
17774
17775 pub fn show_inline_diagnostics(&self) -> bool {
17776 self.show_inline_diagnostics
17777 }
17778
17779 pub fn toggle_inline_diagnostics(
17780 &mut self,
17781 _: &ToggleInlineDiagnostics,
17782 window: &mut Window,
17783 cx: &mut Context<Editor>,
17784 ) {
17785 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17786 self.refresh_inline_diagnostics(false, window, cx);
17787 }
17788
17789 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17790 self.diagnostics_max_severity = severity;
17791 self.display_map.update(cx, |display_map, _| {
17792 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17793 });
17794 }
17795
17796 pub fn toggle_diagnostics(
17797 &mut self,
17798 _: &ToggleDiagnostics,
17799 window: &mut Window,
17800 cx: &mut Context<Editor>,
17801 ) {
17802 if !self.diagnostics_enabled() {
17803 return;
17804 }
17805
17806 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17807 EditorSettings::get_global(cx)
17808 .diagnostics_max_severity
17809 .filter(|severity| severity != &DiagnosticSeverity::Off)
17810 .unwrap_or(DiagnosticSeverity::Hint)
17811 } else {
17812 DiagnosticSeverity::Off
17813 };
17814 self.set_max_diagnostics_severity(new_severity, cx);
17815 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17816 self.active_diagnostics = ActiveDiagnostic::None;
17817 self.inline_diagnostics_update = Task::ready(());
17818 self.inline_diagnostics.clear();
17819 } else {
17820 self.refresh_inline_diagnostics(false, window, cx);
17821 }
17822
17823 cx.notify();
17824 }
17825
17826 pub fn toggle_minimap(
17827 &mut self,
17828 _: &ToggleMinimap,
17829 window: &mut Window,
17830 cx: &mut Context<Editor>,
17831 ) {
17832 if self.supports_minimap(cx) {
17833 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17834 }
17835 }
17836
17837 fn refresh_inline_diagnostics(
17838 &mut self,
17839 debounce: bool,
17840 window: &mut Window,
17841 cx: &mut Context<Self>,
17842 ) {
17843 let max_severity = ProjectSettings::get_global(cx)
17844 .diagnostics
17845 .inline
17846 .max_severity
17847 .unwrap_or(self.diagnostics_max_severity);
17848
17849 if !self.inline_diagnostics_enabled()
17850 || !self.diagnostics_enabled()
17851 || !self.show_inline_diagnostics
17852 || max_severity == DiagnosticSeverity::Off
17853 {
17854 self.inline_diagnostics_update = Task::ready(());
17855 self.inline_diagnostics.clear();
17856 return;
17857 }
17858
17859 let debounce_ms = ProjectSettings::get_global(cx)
17860 .diagnostics
17861 .inline
17862 .update_debounce_ms;
17863 let debounce = if debounce && debounce_ms > 0 {
17864 Some(Duration::from_millis(debounce_ms))
17865 } else {
17866 None
17867 };
17868 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17869 if let Some(debounce) = debounce {
17870 cx.background_executor().timer(debounce).await;
17871 }
17872 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17873 editor
17874 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17875 .ok()
17876 }) else {
17877 return;
17878 };
17879
17880 let new_inline_diagnostics = cx
17881 .background_spawn(async move {
17882 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17883 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17884 let message = diagnostic_entry
17885 .diagnostic
17886 .message
17887 .split_once('\n')
17888 .map(|(line, _)| line)
17889 .map(SharedString::new)
17890 .unwrap_or_else(|| {
17891 SharedString::new(&*diagnostic_entry.diagnostic.message)
17892 });
17893 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17894 let (Ok(i) | Err(i)) = inline_diagnostics
17895 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17896 inline_diagnostics.insert(
17897 i,
17898 (
17899 start_anchor,
17900 InlineDiagnostic {
17901 message,
17902 group_id: diagnostic_entry.diagnostic.group_id,
17903 start: diagnostic_entry.range.start.to_point(&snapshot),
17904 is_primary: diagnostic_entry.diagnostic.is_primary,
17905 severity: diagnostic_entry.diagnostic.severity,
17906 },
17907 ),
17908 );
17909 }
17910 inline_diagnostics
17911 })
17912 .await;
17913
17914 editor
17915 .update(cx, |editor, cx| {
17916 editor.inline_diagnostics = new_inline_diagnostics;
17917 cx.notify();
17918 })
17919 .ok();
17920 });
17921 }
17922
17923 fn pull_diagnostics(
17924 &mut self,
17925 buffer_id: Option<BufferId>,
17926 window: &Window,
17927 cx: &mut Context<Self>,
17928 ) -> Option<()> {
17929 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
17930 return None;
17931 }
17932 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17933 .diagnostics
17934 .lsp_pull_diagnostics;
17935 if !pull_diagnostics_settings.enabled {
17936 return None;
17937 }
17938 let project = self.project()?.downgrade();
17939 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17940 let mut buffers = self.buffer.read(cx).all_buffers();
17941 buffers.retain(|buffer| {
17942 let buffer_id_to_retain = buffer.read(cx).remote_id();
17943 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
17944 && self.registered_buffers.contains_key(&buffer_id_to_retain)
17945 });
17946 if buffers.is_empty() {
17947 self.pull_diagnostics_task = Task::ready(());
17948 return None;
17949 }
17950
17951 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17952 cx.background_executor().timer(debounce).await;
17953
17954 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17955 buffers
17956 .into_iter()
17957 .filter_map(|buffer| {
17958 project
17959 .update(cx, |project, cx| {
17960 project.lsp_store().update(cx, |lsp_store, cx| {
17961 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17962 })
17963 })
17964 .ok()
17965 })
17966 .collect::<FuturesUnordered<_>>()
17967 }) else {
17968 return;
17969 };
17970
17971 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17972 match pull_task {
17973 Ok(()) => {
17974 if editor
17975 .update_in(cx, |editor, window, cx| {
17976 editor.update_diagnostics_state(window, cx);
17977 })
17978 .is_err()
17979 {
17980 return;
17981 }
17982 }
17983 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17984 }
17985 }
17986 });
17987
17988 Some(())
17989 }
17990
17991 pub fn set_selections_from_remote(
17992 &mut self,
17993 selections: Vec<Selection<Anchor>>,
17994 pending_selection: Option<Selection<Anchor>>,
17995 window: &mut Window,
17996 cx: &mut Context<Self>,
17997 ) {
17998 let old_cursor_position = self.selections.newest_anchor().head();
17999 self.selections.change_with(cx, |s| {
18000 s.select_anchors(selections);
18001 if let Some(pending_selection) = pending_selection {
18002 s.set_pending(pending_selection, SelectMode::Character);
18003 } else {
18004 s.clear_pending();
18005 }
18006 });
18007 self.selections_did_change(
18008 false,
18009 &old_cursor_position,
18010 SelectionEffects::default(),
18011 window,
18012 cx,
18013 );
18014 }
18015
18016 pub fn transact(
18017 &mut self,
18018 window: &mut Window,
18019 cx: &mut Context<Self>,
18020 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18021 ) -> Option<TransactionId> {
18022 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18023 this.start_transaction_at(Instant::now(), window, cx);
18024 update(this, window, cx);
18025 this.end_transaction_at(Instant::now(), cx)
18026 })
18027 }
18028
18029 pub fn start_transaction_at(
18030 &mut self,
18031 now: Instant,
18032 window: &mut Window,
18033 cx: &mut Context<Self>,
18034 ) -> Option<TransactionId> {
18035 self.end_selection(window, cx);
18036 if let Some(tx_id) = self
18037 .buffer
18038 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18039 {
18040 self.selection_history
18041 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18042 cx.emit(EditorEvent::TransactionBegun {
18043 transaction_id: tx_id,
18044 });
18045 Some(tx_id)
18046 } else {
18047 None
18048 }
18049 }
18050
18051 pub fn end_transaction_at(
18052 &mut self,
18053 now: Instant,
18054 cx: &mut Context<Self>,
18055 ) -> Option<TransactionId> {
18056 if let Some(transaction_id) = self
18057 .buffer
18058 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18059 {
18060 if let Some((_, end_selections)) =
18061 self.selection_history.transaction_mut(transaction_id)
18062 {
18063 *end_selections = Some(self.selections.disjoint_anchors_arc());
18064 } else {
18065 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18066 }
18067
18068 cx.emit(EditorEvent::Edited { transaction_id });
18069 Some(transaction_id)
18070 } else {
18071 None
18072 }
18073 }
18074
18075 pub fn modify_transaction_selection_history(
18076 &mut self,
18077 transaction_id: TransactionId,
18078 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18079 ) -> bool {
18080 self.selection_history
18081 .transaction_mut(transaction_id)
18082 .map(modify)
18083 .is_some()
18084 }
18085
18086 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18087 if self.selection_mark_mode {
18088 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18089 s.move_with(|_, sel| {
18090 sel.collapse_to(sel.head(), SelectionGoal::None);
18091 });
18092 })
18093 }
18094 self.selection_mark_mode = true;
18095 cx.notify();
18096 }
18097
18098 pub fn swap_selection_ends(
18099 &mut self,
18100 _: &actions::SwapSelectionEnds,
18101 window: &mut Window,
18102 cx: &mut Context<Self>,
18103 ) {
18104 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18105 s.move_with(|_, sel| {
18106 if sel.start != sel.end {
18107 sel.reversed = !sel.reversed
18108 }
18109 });
18110 });
18111 self.request_autoscroll(Autoscroll::newest(), cx);
18112 cx.notify();
18113 }
18114
18115 pub fn toggle_focus(
18116 workspace: &mut Workspace,
18117 _: &actions::ToggleFocus,
18118 window: &mut Window,
18119 cx: &mut Context<Workspace>,
18120 ) {
18121 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18122 return;
18123 };
18124 workspace.activate_item(&item, true, true, window, cx);
18125 }
18126
18127 pub fn toggle_fold(
18128 &mut self,
18129 _: &actions::ToggleFold,
18130 window: &mut Window,
18131 cx: &mut Context<Self>,
18132 ) {
18133 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18134 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18135 let selection = self.selections.newest::<Point>(&display_map);
18136
18137 let range = if selection.is_empty() {
18138 let point = selection.head().to_display_point(&display_map);
18139 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18140 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18141 .to_point(&display_map);
18142 start..end
18143 } else {
18144 selection.range()
18145 };
18146 if display_map.folds_in_range(range).next().is_some() {
18147 self.unfold_lines(&Default::default(), window, cx)
18148 } else {
18149 self.fold(&Default::default(), window, cx)
18150 }
18151 } else {
18152 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18153 let buffer_ids: HashSet<_> = self
18154 .selections
18155 .disjoint_anchor_ranges()
18156 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18157 .collect();
18158
18159 let should_unfold = buffer_ids
18160 .iter()
18161 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18162
18163 for buffer_id in buffer_ids {
18164 if should_unfold {
18165 self.unfold_buffer(buffer_id, cx);
18166 } else {
18167 self.fold_buffer(buffer_id, cx);
18168 }
18169 }
18170 }
18171 }
18172
18173 pub fn toggle_fold_recursive(
18174 &mut self,
18175 _: &actions::ToggleFoldRecursive,
18176 window: &mut Window,
18177 cx: &mut Context<Self>,
18178 ) {
18179 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18180
18181 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18182 let range = if selection.is_empty() {
18183 let point = selection.head().to_display_point(&display_map);
18184 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18185 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18186 .to_point(&display_map);
18187 start..end
18188 } else {
18189 selection.range()
18190 };
18191 if display_map.folds_in_range(range).next().is_some() {
18192 self.unfold_recursive(&Default::default(), window, cx)
18193 } else {
18194 self.fold_recursive(&Default::default(), window, cx)
18195 }
18196 }
18197
18198 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18199 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18200 let mut to_fold = Vec::new();
18201 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18202 let selections = self.selections.all_adjusted(&display_map);
18203
18204 for selection in selections {
18205 let range = selection.range().sorted();
18206 let buffer_start_row = range.start.row;
18207
18208 if range.start.row != range.end.row {
18209 let mut found = false;
18210 let mut row = range.start.row;
18211 while row <= range.end.row {
18212 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18213 {
18214 found = true;
18215 row = crease.range().end.row + 1;
18216 to_fold.push(crease);
18217 } else {
18218 row += 1
18219 }
18220 }
18221 if found {
18222 continue;
18223 }
18224 }
18225
18226 for row in (0..=range.start.row).rev() {
18227 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18228 && crease.range().end.row >= buffer_start_row
18229 {
18230 to_fold.push(crease);
18231 if row <= range.start.row {
18232 break;
18233 }
18234 }
18235 }
18236 }
18237
18238 self.fold_creases(to_fold, true, window, cx);
18239 } else {
18240 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18241 let buffer_ids = self
18242 .selections
18243 .disjoint_anchor_ranges()
18244 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18245 .collect::<HashSet<_>>();
18246 for buffer_id in buffer_ids {
18247 self.fold_buffer(buffer_id, cx);
18248 }
18249 }
18250 }
18251
18252 pub fn toggle_fold_all(
18253 &mut self,
18254 _: &actions::ToggleFoldAll,
18255 window: &mut Window,
18256 cx: &mut Context<Self>,
18257 ) {
18258 if self.buffer.read(cx).is_singleton() {
18259 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18260 let has_folds = display_map
18261 .folds_in_range(0..display_map.buffer_snapshot().len())
18262 .next()
18263 .is_some();
18264
18265 if has_folds {
18266 self.unfold_all(&actions::UnfoldAll, window, cx);
18267 } else {
18268 self.fold_all(&actions::FoldAll, window, cx);
18269 }
18270 } else {
18271 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18272 let should_unfold = buffer_ids
18273 .iter()
18274 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18275
18276 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18277 editor
18278 .update_in(cx, |editor, _, cx| {
18279 for buffer_id in buffer_ids {
18280 if should_unfold {
18281 editor.unfold_buffer(buffer_id, cx);
18282 } else {
18283 editor.fold_buffer(buffer_id, cx);
18284 }
18285 }
18286 })
18287 .ok();
18288 });
18289 }
18290 }
18291
18292 fn fold_at_level(
18293 &mut self,
18294 fold_at: &FoldAtLevel,
18295 window: &mut Window,
18296 cx: &mut Context<Self>,
18297 ) {
18298 if !self.buffer.read(cx).is_singleton() {
18299 return;
18300 }
18301
18302 let fold_at_level = fold_at.0;
18303 let snapshot = self.buffer.read(cx).snapshot(cx);
18304 let mut to_fold = Vec::new();
18305 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18306
18307 let row_ranges_to_keep: Vec<Range<u32>> = self
18308 .selections
18309 .all::<Point>(&self.display_snapshot(cx))
18310 .into_iter()
18311 .map(|sel| sel.start.row..sel.end.row)
18312 .collect();
18313
18314 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18315 while start_row < end_row {
18316 match self
18317 .snapshot(window, cx)
18318 .crease_for_buffer_row(MultiBufferRow(start_row))
18319 {
18320 Some(crease) => {
18321 let nested_start_row = crease.range().start.row + 1;
18322 let nested_end_row = crease.range().end.row;
18323
18324 if current_level < fold_at_level {
18325 stack.push((nested_start_row, nested_end_row, current_level + 1));
18326 } else if current_level == fold_at_level {
18327 // Fold iff there is no selection completely contained within the fold region
18328 if !row_ranges_to_keep.iter().any(|selection| {
18329 selection.end >= nested_start_row
18330 && selection.start <= nested_end_row
18331 }) {
18332 to_fold.push(crease);
18333 }
18334 }
18335
18336 start_row = nested_end_row + 1;
18337 }
18338 None => start_row += 1,
18339 }
18340 }
18341 }
18342
18343 self.fold_creases(to_fold, true, window, cx);
18344 }
18345
18346 pub fn fold_at_level_1(
18347 &mut self,
18348 _: &actions::FoldAtLevel1,
18349 window: &mut Window,
18350 cx: &mut Context<Self>,
18351 ) {
18352 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18353 }
18354
18355 pub fn fold_at_level_2(
18356 &mut self,
18357 _: &actions::FoldAtLevel2,
18358 window: &mut Window,
18359 cx: &mut Context<Self>,
18360 ) {
18361 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18362 }
18363
18364 pub fn fold_at_level_3(
18365 &mut self,
18366 _: &actions::FoldAtLevel3,
18367 window: &mut Window,
18368 cx: &mut Context<Self>,
18369 ) {
18370 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18371 }
18372
18373 pub fn fold_at_level_4(
18374 &mut self,
18375 _: &actions::FoldAtLevel4,
18376 window: &mut Window,
18377 cx: &mut Context<Self>,
18378 ) {
18379 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18380 }
18381
18382 pub fn fold_at_level_5(
18383 &mut self,
18384 _: &actions::FoldAtLevel5,
18385 window: &mut Window,
18386 cx: &mut Context<Self>,
18387 ) {
18388 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18389 }
18390
18391 pub fn fold_at_level_6(
18392 &mut self,
18393 _: &actions::FoldAtLevel6,
18394 window: &mut Window,
18395 cx: &mut Context<Self>,
18396 ) {
18397 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18398 }
18399
18400 pub fn fold_at_level_7(
18401 &mut self,
18402 _: &actions::FoldAtLevel7,
18403 window: &mut Window,
18404 cx: &mut Context<Self>,
18405 ) {
18406 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18407 }
18408
18409 pub fn fold_at_level_8(
18410 &mut self,
18411 _: &actions::FoldAtLevel8,
18412 window: &mut Window,
18413 cx: &mut Context<Self>,
18414 ) {
18415 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18416 }
18417
18418 pub fn fold_at_level_9(
18419 &mut self,
18420 _: &actions::FoldAtLevel9,
18421 window: &mut Window,
18422 cx: &mut Context<Self>,
18423 ) {
18424 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18425 }
18426
18427 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18428 if self.buffer.read(cx).is_singleton() {
18429 let mut fold_ranges = Vec::new();
18430 let snapshot = self.buffer.read(cx).snapshot(cx);
18431
18432 for row in 0..snapshot.max_row().0 {
18433 if let Some(foldable_range) = self
18434 .snapshot(window, cx)
18435 .crease_for_buffer_row(MultiBufferRow(row))
18436 {
18437 fold_ranges.push(foldable_range);
18438 }
18439 }
18440
18441 self.fold_creases(fold_ranges, true, window, cx);
18442 } else {
18443 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18444 editor
18445 .update_in(cx, |editor, _, cx| {
18446 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18447 editor.fold_buffer(buffer_id, cx);
18448 }
18449 })
18450 .ok();
18451 });
18452 }
18453 }
18454
18455 pub fn fold_function_bodies(
18456 &mut self,
18457 _: &actions::FoldFunctionBodies,
18458 window: &mut Window,
18459 cx: &mut Context<Self>,
18460 ) {
18461 let snapshot = self.buffer.read(cx).snapshot(cx);
18462
18463 let ranges = snapshot
18464 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18465 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18466 .collect::<Vec<_>>();
18467
18468 let creases = ranges
18469 .into_iter()
18470 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18471 .collect();
18472
18473 self.fold_creases(creases, true, window, cx);
18474 }
18475
18476 pub fn fold_recursive(
18477 &mut self,
18478 _: &actions::FoldRecursive,
18479 window: &mut Window,
18480 cx: &mut Context<Self>,
18481 ) {
18482 let mut to_fold = Vec::new();
18483 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18484 let selections = self.selections.all_adjusted(&display_map);
18485
18486 for selection in selections {
18487 let range = selection.range().sorted();
18488 let buffer_start_row = range.start.row;
18489
18490 if range.start.row != range.end.row {
18491 let mut found = false;
18492 for row in range.start.row..=range.end.row {
18493 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18494 found = true;
18495 to_fold.push(crease);
18496 }
18497 }
18498 if found {
18499 continue;
18500 }
18501 }
18502
18503 for row in (0..=range.start.row).rev() {
18504 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18505 if crease.range().end.row >= buffer_start_row {
18506 to_fold.push(crease);
18507 } else {
18508 break;
18509 }
18510 }
18511 }
18512 }
18513
18514 self.fold_creases(to_fold, true, window, cx);
18515 }
18516
18517 pub fn fold_at(
18518 &mut self,
18519 buffer_row: MultiBufferRow,
18520 window: &mut Window,
18521 cx: &mut Context<Self>,
18522 ) {
18523 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18524
18525 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18526 let autoscroll = self
18527 .selections
18528 .all::<Point>(&display_map)
18529 .iter()
18530 .any(|selection| crease.range().overlaps(&selection.range()));
18531
18532 self.fold_creases(vec![crease], autoscroll, window, cx);
18533 }
18534 }
18535
18536 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18537 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18538 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18539 let buffer = display_map.buffer_snapshot();
18540 let selections = self.selections.all::<Point>(&display_map);
18541 let ranges = selections
18542 .iter()
18543 .map(|s| {
18544 let range = s.display_range(&display_map).sorted();
18545 let mut start = range.start.to_point(&display_map);
18546 let mut end = range.end.to_point(&display_map);
18547 start.column = 0;
18548 end.column = buffer.line_len(MultiBufferRow(end.row));
18549 start..end
18550 })
18551 .collect::<Vec<_>>();
18552
18553 self.unfold_ranges(&ranges, true, true, cx);
18554 } else {
18555 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18556 let buffer_ids = self
18557 .selections
18558 .disjoint_anchor_ranges()
18559 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18560 .collect::<HashSet<_>>();
18561 for buffer_id in buffer_ids {
18562 self.unfold_buffer(buffer_id, cx);
18563 }
18564 }
18565 }
18566
18567 pub fn unfold_recursive(
18568 &mut self,
18569 _: &UnfoldRecursive,
18570 _window: &mut Window,
18571 cx: &mut Context<Self>,
18572 ) {
18573 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18574 let selections = self.selections.all::<Point>(&display_map);
18575 let ranges = selections
18576 .iter()
18577 .map(|s| {
18578 let mut range = s.display_range(&display_map).sorted();
18579 *range.start.column_mut() = 0;
18580 *range.end.column_mut() = display_map.line_len(range.end.row());
18581 let start = range.start.to_point(&display_map);
18582 let end = range.end.to_point(&display_map);
18583 start..end
18584 })
18585 .collect::<Vec<_>>();
18586
18587 self.unfold_ranges(&ranges, true, true, cx);
18588 }
18589
18590 pub fn unfold_at(
18591 &mut self,
18592 buffer_row: MultiBufferRow,
18593 _window: &mut Window,
18594 cx: &mut Context<Self>,
18595 ) {
18596 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18597
18598 let intersection_range = Point::new(buffer_row.0, 0)
18599 ..Point::new(
18600 buffer_row.0,
18601 display_map.buffer_snapshot().line_len(buffer_row),
18602 );
18603
18604 let autoscroll = self
18605 .selections
18606 .all::<Point>(&display_map)
18607 .iter()
18608 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18609
18610 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18611 }
18612
18613 pub fn unfold_all(
18614 &mut self,
18615 _: &actions::UnfoldAll,
18616 _window: &mut Window,
18617 cx: &mut Context<Self>,
18618 ) {
18619 if self.buffer.read(cx).is_singleton() {
18620 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18621 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18622 } else {
18623 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18624 editor
18625 .update(cx, |editor, cx| {
18626 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18627 editor.unfold_buffer(buffer_id, cx);
18628 }
18629 })
18630 .ok();
18631 });
18632 }
18633 }
18634
18635 pub fn fold_selected_ranges(
18636 &mut self,
18637 _: &FoldSelectedRanges,
18638 window: &mut Window,
18639 cx: &mut Context<Self>,
18640 ) {
18641 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18642 let selections = self.selections.all_adjusted(&display_map);
18643 let ranges = selections
18644 .into_iter()
18645 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18646 .collect::<Vec<_>>();
18647 self.fold_creases(ranges, true, window, cx);
18648 }
18649
18650 pub fn fold_ranges<T: ToOffset + Clone>(
18651 &mut self,
18652 ranges: Vec<Range<T>>,
18653 auto_scroll: bool,
18654 window: &mut Window,
18655 cx: &mut Context<Self>,
18656 ) {
18657 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18658 let ranges = ranges
18659 .into_iter()
18660 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18661 .collect::<Vec<_>>();
18662 self.fold_creases(ranges, auto_scroll, window, cx);
18663 }
18664
18665 pub fn fold_creases<T: ToOffset + Clone>(
18666 &mut self,
18667 creases: Vec<Crease<T>>,
18668 auto_scroll: bool,
18669 _window: &mut Window,
18670 cx: &mut Context<Self>,
18671 ) {
18672 if creases.is_empty() {
18673 return;
18674 }
18675
18676 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18677
18678 if auto_scroll {
18679 self.request_autoscroll(Autoscroll::fit(), cx);
18680 }
18681
18682 cx.notify();
18683
18684 self.scrollbar_marker_state.dirty = true;
18685 self.folds_did_change(cx);
18686 }
18687
18688 /// Removes any folds whose ranges intersect any of the given ranges.
18689 pub fn unfold_ranges<T: ToOffset + Clone>(
18690 &mut self,
18691 ranges: &[Range<T>],
18692 inclusive: bool,
18693 auto_scroll: bool,
18694 cx: &mut Context<Self>,
18695 ) {
18696 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18697 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18698 });
18699 self.folds_did_change(cx);
18700 }
18701
18702 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18703 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18704 return;
18705 }
18706 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18707 self.display_map.update(cx, |display_map, cx| {
18708 display_map.fold_buffers([buffer_id], cx)
18709 });
18710 cx.emit(EditorEvent::BufferFoldToggled {
18711 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18712 folded: true,
18713 });
18714 cx.notify();
18715 }
18716
18717 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18718 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18719 return;
18720 }
18721 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18722 self.display_map.update(cx, |display_map, cx| {
18723 display_map.unfold_buffers([buffer_id], cx);
18724 });
18725 cx.emit(EditorEvent::BufferFoldToggled {
18726 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18727 folded: false,
18728 });
18729 cx.notify();
18730 }
18731
18732 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18733 self.display_map.read(cx).is_buffer_folded(buffer)
18734 }
18735
18736 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18737 self.display_map.read(cx).folded_buffers()
18738 }
18739
18740 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18741 self.display_map.update(cx, |display_map, cx| {
18742 display_map.disable_header_for_buffer(buffer_id, cx);
18743 });
18744 cx.notify();
18745 }
18746
18747 /// Removes any folds with the given ranges.
18748 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18749 &mut self,
18750 ranges: &[Range<T>],
18751 type_id: TypeId,
18752 auto_scroll: bool,
18753 cx: &mut Context<Self>,
18754 ) {
18755 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18756 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18757 });
18758 self.folds_did_change(cx);
18759 }
18760
18761 fn remove_folds_with<T: ToOffset + Clone>(
18762 &mut self,
18763 ranges: &[Range<T>],
18764 auto_scroll: bool,
18765 cx: &mut Context<Self>,
18766 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18767 ) {
18768 if ranges.is_empty() {
18769 return;
18770 }
18771
18772 let mut buffers_affected = HashSet::default();
18773 let multi_buffer = self.buffer().read(cx);
18774 for range in ranges {
18775 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18776 buffers_affected.insert(buffer.read(cx).remote_id());
18777 };
18778 }
18779
18780 self.display_map.update(cx, update);
18781
18782 if auto_scroll {
18783 self.request_autoscroll(Autoscroll::fit(), cx);
18784 }
18785
18786 cx.notify();
18787 self.scrollbar_marker_state.dirty = true;
18788 self.active_indent_guides_state.dirty = true;
18789 }
18790
18791 pub fn update_renderer_widths(
18792 &mut self,
18793 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18794 cx: &mut Context<Self>,
18795 ) -> bool {
18796 self.display_map
18797 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18798 }
18799
18800 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18801 self.display_map.read(cx).fold_placeholder.clone()
18802 }
18803
18804 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18805 self.buffer.update(cx, |buffer, cx| {
18806 buffer.set_all_diff_hunks_expanded(cx);
18807 });
18808 }
18809
18810 pub fn expand_all_diff_hunks(
18811 &mut self,
18812 _: &ExpandAllDiffHunks,
18813 _window: &mut Window,
18814 cx: &mut Context<Self>,
18815 ) {
18816 self.buffer.update(cx, |buffer, cx| {
18817 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18818 });
18819 }
18820
18821 pub fn collapse_all_diff_hunks(
18822 &mut self,
18823 _: &CollapseAllDiffHunks,
18824 _window: &mut Window,
18825 cx: &mut Context<Self>,
18826 ) {
18827 self.buffer.update(cx, |buffer, cx| {
18828 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18829 });
18830 }
18831
18832 pub fn toggle_selected_diff_hunks(
18833 &mut self,
18834 _: &ToggleSelectedDiffHunks,
18835 _window: &mut Window,
18836 cx: &mut Context<Self>,
18837 ) {
18838 let ranges: Vec<_> = self
18839 .selections
18840 .disjoint_anchors()
18841 .iter()
18842 .map(|s| s.range())
18843 .collect();
18844 self.toggle_diff_hunks_in_ranges(ranges, cx);
18845 }
18846
18847 pub fn diff_hunks_in_ranges<'a>(
18848 &'a self,
18849 ranges: &'a [Range<Anchor>],
18850 buffer: &'a MultiBufferSnapshot,
18851 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18852 ranges.iter().flat_map(move |range| {
18853 let end_excerpt_id = range.end.excerpt_id;
18854 let range = range.to_point(buffer);
18855 let mut peek_end = range.end;
18856 if range.end.row < buffer.max_row().0 {
18857 peek_end = Point::new(range.end.row + 1, 0);
18858 }
18859 buffer
18860 .diff_hunks_in_range(range.start..peek_end)
18861 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18862 })
18863 }
18864
18865 pub fn has_stageable_diff_hunks_in_ranges(
18866 &self,
18867 ranges: &[Range<Anchor>],
18868 snapshot: &MultiBufferSnapshot,
18869 ) -> bool {
18870 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18871 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18872 }
18873
18874 pub fn toggle_staged_selected_diff_hunks(
18875 &mut self,
18876 _: &::git::ToggleStaged,
18877 _: &mut Window,
18878 cx: &mut Context<Self>,
18879 ) {
18880 let snapshot = self.buffer.read(cx).snapshot(cx);
18881 let ranges: Vec<_> = self
18882 .selections
18883 .disjoint_anchors()
18884 .iter()
18885 .map(|s| s.range())
18886 .collect();
18887 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18888 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18889 }
18890
18891 pub fn set_render_diff_hunk_controls(
18892 &mut self,
18893 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18894 cx: &mut Context<Self>,
18895 ) {
18896 self.render_diff_hunk_controls = render_diff_hunk_controls;
18897 cx.notify();
18898 }
18899
18900 pub fn stage_and_next(
18901 &mut self,
18902 _: &::git::StageAndNext,
18903 window: &mut Window,
18904 cx: &mut Context<Self>,
18905 ) {
18906 self.do_stage_or_unstage_and_next(true, window, cx);
18907 }
18908
18909 pub fn unstage_and_next(
18910 &mut self,
18911 _: &::git::UnstageAndNext,
18912 window: &mut Window,
18913 cx: &mut Context<Self>,
18914 ) {
18915 self.do_stage_or_unstage_and_next(false, window, cx);
18916 }
18917
18918 pub fn stage_or_unstage_diff_hunks(
18919 &mut self,
18920 stage: bool,
18921 ranges: Vec<Range<Anchor>>,
18922 cx: &mut Context<Self>,
18923 ) {
18924 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18925 cx.spawn(async move |this, cx| {
18926 task.await?;
18927 this.update(cx, |this, cx| {
18928 let snapshot = this.buffer.read(cx).snapshot(cx);
18929 let chunk_by = this
18930 .diff_hunks_in_ranges(&ranges, &snapshot)
18931 .chunk_by(|hunk| hunk.buffer_id);
18932 for (buffer_id, hunks) in &chunk_by {
18933 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18934 }
18935 })
18936 })
18937 .detach_and_log_err(cx);
18938 }
18939
18940 fn save_buffers_for_ranges_if_needed(
18941 &mut self,
18942 ranges: &[Range<Anchor>],
18943 cx: &mut Context<Editor>,
18944 ) -> Task<Result<()>> {
18945 let multibuffer = self.buffer.read(cx);
18946 let snapshot = multibuffer.read(cx);
18947 let buffer_ids: HashSet<_> = ranges
18948 .iter()
18949 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18950 .collect();
18951 drop(snapshot);
18952
18953 let mut buffers = HashSet::default();
18954 for buffer_id in buffer_ids {
18955 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18956 let buffer = buffer_entity.read(cx);
18957 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18958 {
18959 buffers.insert(buffer_entity);
18960 }
18961 }
18962 }
18963
18964 if let Some(project) = &self.project {
18965 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18966 } else {
18967 Task::ready(Ok(()))
18968 }
18969 }
18970
18971 fn do_stage_or_unstage_and_next(
18972 &mut self,
18973 stage: bool,
18974 window: &mut Window,
18975 cx: &mut Context<Self>,
18976 ) {
18977 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18978
18979 if ranges.iter().any(|range| range.start != range.end) {
18980 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18981 return;
18982 }
18983
18984 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18985 let snapshot = self.snapshot(window, cx);
18986 let position = self
18987 .selections
18988 .newest::<Point>(&snapshot.display_snapshot)
18989 .head();
18990 let mut row = snapshot
18991 .buffer_snapshot()
18992 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18993 .find(|hunk| hunk.row_range.start.0 > position.row)
18994 .map(|hunk| hunk.row_range.start);
18995
18996 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18997 // Outside of the project diff editor, wrap around to the beginning.
18998 if !all_diff_hunks_expanded {
18999 row = row.or_else(|| {
19000 snapshot
19001 .buffer_snapshot()
19002 .diff_hunks_in_range(Point::zero()..position)
19003 .find(|hunk| hunk.row_range.end.0 < position.row)
19004 .map(|hunk| hunk.row_range.start)
19005 });
19006 }
19007
19008 if let Some(row) = row {
19009 let destination = Point::new(row.0, 0);
19010 let autoscroll = Autoscroll::center();
19011
19012 self.unfold_ranges(&[destination..destination], false, false, cx);
19013 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19014 s.select_ranges([destination..destination]);
19015 });
19016 }
19017 }
19018
19019 fn do_stage_or_unstage(
19020 &self,
19021 stage: bool,
19022 buffer_id: BufferId,
19023 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19024 cx: &mut App,
19025 ) -> Option<()> {
19026 let project = self.project()?;
19027 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19028 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19029 let buffer_snapshot = buffer.read(cx).snapshot();
19030 let file_exists = buffer_snapshot
19031 .file()
19032 .is_some_and(|file| file.disk_state().exists());
19033 diff.update(cx, |diff, cx| {
19034 diff.stage_or_unstage_hunks(
19035 stage,
19036 &hunks
19037 .map(|hunk| buffer_diff::DiffHunk {
19038 buffer_range: hunk.buffer_range,
19039 diff_base_byte_range: hunk.diff_base_byte_range,
19040 secondary_status: hunk.secondary_status,
19041 range: Point::zero()..Point::zero(), // unused
19042 })
19043 .collect::<Vec<_>>(),
19044 &buffer_snapshot,
19045 file_exists,
19046 cx,
19047 )
19048 });
19049 None
19050 }
19051
19052 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19053 let ranges: Vec<_> = self
19054 .selections
19055 .disjoint_anchors()
19056 .iter()
19057 .map(|s| s.range())
19058 .collect();
19059 self.buffer
19060 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19061 }
19062
19063 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19064 self.buffer.update(cx, |buffer, cx| {
19065 let ranges = vec![Anchor::min()..Anchor::max()];
19066 if !buffer.all_diff_hunks_expanded()
19067 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19068 {
19069 buffer.collapse_diff_hunks(ranges, cx);
19070 true
19071 } else {
19072 false
19073 }
19074 })
19075 }
19076
19077 fn toggle_diff_hunks_in_ranges(
19078 &mut self,
19079 ranges: Vec<Range<Anchor>>,
19080 cx: &mut Context<Editor>,
19081 ) {
19082 self.buffer.update(cx, |buffer, cx| {
19083 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19084 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19085 })
19086 }
19087
19088 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19089 self.buffer.update(cx, |buffer, cx| {
19090 let snapshot = buffer.snapshot(cx);
19091 let excerpt_id = range.end.excerpt_id;
19092 let point_range = range.to_point(&snapshot);
19093 let expand = !buffer.single_hunk_is_expanded(range, cx);
19094 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19095 })
19096 }
19097
19098 pub(crate) fn apply_all_diff_hunks(
19099 &mut self,
19100 _: &ApplyAllDiffHunks,
19101 window: &mut Window,
19102 cx: &mut Context<Self>,
19103 ) {
19104 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19105
19106 let buffers = self.buffer.read(cx).all_buffers();
19107 for branch_buffer in buffers {
19108 branch_buffer.update(cx, |branch_buffer, cx| {
19109 branch_buffer.merge_into_base(Vec::new(), cx);
19110 });
19111 }
19112
19113 if let Some(project) = self.project.clone() {
19114 self.save(
19115 SaveOptions {
19116 format: true,
19117 autosave: false,
19118 },
19119 project,
19120 window,
19121 cx,
19122 )
19123 .detach_and_log_err(cx);
19124 }
19125 }
19126
19127 pub(crate) fn apply_selected_diff_hunks(
19128 &mut self,
19129 _: &ApplyDiffHunk,
19130 window: &mut Window,
19131 cx: &mut Context<Self>,
19132 ) {
19133 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19134 let snapshot = self.snapshot(window, cx);
19135 let hunks = snapshot.hunks_for_ranges(
19136 self.selections
19137 .all(&snapshot.display_snapshot)
19138 .into_iter()
19139 .map(|selection| selection.range()),
19140 );
19141 let mut ranges_by_buffer = HashMap::default();
19142 self.transact(window, cx, |editor, _window, cx| {
19143 for hunk in hunks {
19144 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19145 ranges_by_buffer
19146 .entry(buffer.clone())
19147 .or_insert_with(Vec::new)
19148 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19149 }
19150 }
19151
19152 for (buffer, ranges) in ranges_by_buffer {
19153 buffer.update(cx, |buffer, cx| {
19154 buffer.merge_into_base(ranges, cx);
19155 });
19156 }
19157 });
19158
19159 if let Some(project) = self.project.clone() {
19160 self.save(
19161 SaveOptions {
19162 format: true,
19163 autosave: false,
19164 },
19165 project,
19166 window,
19167 cx,
19168 )
19169 .detach_and_log_err(cx);
19170 }
19171 }
19172
19173 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19174 if hovered != self.gutter_hovered {
19175 self.gutter_hovered = hovered;
19176 cx.notify();
19177 }
19178 }
19179
19180 pub fn insert_blocks(
19181 &mut self,
19182 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19183 autoscroll: Option<Autoscroll>,
19184 cx: &mut Context<Self>,
19185 ) -> Vec<CustomBlockId> {
19186 let blocks = self
19187 .display_map
19188 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19189 if let Some(autoscroll) = autoscroll {
19190 self.request_autoscroll(autoscroll, cx);
19191 }
19192 cx.notify();
19193 blocks
19194 }
19195
19196 pub fn resize_blocks(
19197 &mut self,
19198 heights: HashMap<CustomBlockId, u32>,
19199 autoscroll: Option<Autoscroll>,
19200 cx: &mut Context<Self>,
19201 ) {
19202 self.display_map
19203 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19204 if let Some(autoscroll) = autoscroll {
19205 self.request_autoscroll(autoscroll, cx);
19206 }
19207 cx.notify();
19208 }
19209
19210 pub fn replace_blocks(
19211 &mut self,
19212 renderers: HashMap<CustomBlockId, RenderBlock>,
19213 autoscroll: Option<Autoscroll>,
19214 cx: &mut Context<Self>,
19215 ) {
19216 self.display_map
19217 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19218 if let Some(autoscroll) = autoscroll {
19219 self.request_autoscroll(autoscroll, cx);
19220 }
19221 cx.notify();
19222 }
19223
19224 pub fn remove_blocks(
19225 &mut self,
19226 block_ids: HashSet<CustomBlockId>,
19227 autoscroll: Option<Autoscroll>,
19228 cx: &mut Context<Self>,
19229 ) {
19230 self.display_map.update(cx, |display_map, cx| {
19231 display_map.remove_blocks(block_ids, cx)
19232 });
19233 if let Some(autoscroll) = autoscroll {
19234 self.request_autoscroll(autoscroll, cx);
19235 }
19236 cx.notify();
19237 }
19238
19239 pub fn row_for_block(
19240 &self,
19241 block_id: CustomBlockId,
19242 cx: &mut Context<Self>,
19243 ) -> Option<DisplayRow> {
19244 self.display_map
19245 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19246 }
19247
19248 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19249 self.focused_block = Some(focused_block);
19250 }
19251
19252 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19253 self.focused_block.take()
19254 }
19255
19256 pub fn insert_creases(
19257 &mut self,
19258 creases: impl IntoIterator<Item = Crease<Anchor>>,
19259 cx: &mut Context<Self>,
19260 ) -> Vec<CreaseId> {
19261 self.display_map
19262 .update(cx, |map, cx| map.insert_creases(creases, cx))
19263 }
19264
19265 pub fn remove_creases(
19266 &mut self,
19267 ids: impl IntoIterator<Item = CreaseId>,
19268 cx: &mut Context<Self>,
19269 ) -> Vec<(CreaseId, Range<Anchor>)> {
19270 self.display_map
19271 .update(cx, |map, cx| map.remove_creases(ids, cx))
19272 }
19273
19274 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19275 self.display_map
19276 .update(cx, |map, cx| map.snapshot(cx))
19277 .longest_row()
19278 }
19279
19280 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19281 self.display_map
19282 .update(cx, |map, cx| map.snapshot(cx))
19283 .max_point()
19284 }
19285
19286 pub fn text(&self, cx: &App) -> String {
19287 self.buffer.read(cx).read(cx).text()
19288 }
19289
19290 pub fn is_empty(&self, cx: &App) -> bool {
19291 self.buffer.read(cx).read(cx).is_empty()
19292 }
19293
19294 pub fn text_option(&self, cx: &App) -> Option<String> {
19295 let text = self.text(cx);
19296 let text = text.trim();
19297
19298 if text.is_empty() {
19299 return None;
19300 }
19301
19302 Some(text.to_string())
19303 }
19304
19305 pub fn set_text(
19306 &mut self,
19307 text: impl Into<Arc<str>>,
19308 window: &mut Window,
19309 cx: &mut Context<Self>,
19310 ) {
19311 self.transact(window, cx, |this, _, cx| {
19312 this.buffer
19313 .read(cx)
19314 .as_singleton()
19315 .expect("you can only call set_text on editors for singleton buffers")
19316 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19317 });
19318 }
19319
19320 pub fn display_text(&self, cx: &mut App) -> String {
19321 self.display_map
19322 .update(cx, |map, cx| map.snapshot(cx))
19323 .text()
19324 }
19325
19326 fn create_minimap(
19327 &self,
19328 minimap_settings: MinimapSettings,
19329 window: &mut Window,
19330 cx: &mut Context<Self>,
19331 ) -> Option<Entity<Self>> {
19332 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19333 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19334 }
19335
19336 fn initialize_new_minimap(
19337 &self,
19338 minimap_settings: MinimapSettings,
19339 window: &mut Window,
19340 cx: &mut Context<Self>,
19341 ) -> Entity<Self> {
19342 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19343
19344 let mut minimap = Editor::new_internal(
19345 EditorMode::Minimap {
19346 parent: cx.weak_entity(),
19347 },
19348 self.buffer.clone(),
19349 None,
19350 Some(self.display_map.clone()),
19351 window,
19352 cx,
19353 );
19354 minimap.scroll_manager.clone_state(&self.scroll_manager);
19355 minimap.set_text_style_refinement(TextStyleRefinement {
19356 font_size: Some(MINIMAP_FONT_SIZE),
19357 font_weight: Some(MINIMAP_FONT_WEIGHT),
19358 ..Default::default()
19359 });
19360 minimap.update_minimap_configuration(minimap_settings, cx);
19361 cx.new(|_| minimap)
19362 }
19363
19364 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19365 let current_line_highlight = minimap_settings
19366 .current_line_highlight
19367 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19368 self.set_current_line_highlight(Some(current_line_highlight));
19369 }
19370
19371 pub fn minimap(&self) -> Option<&Entity<Self>> {
19372 self.minimap
19373 .as_ref()
19374 .filter(|_| self.minimap_visibility.visible())
19375 }
19376
19377 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19378 let mut wrap_guides = smallvec![];
19379
19380 if self.show_wrap_guides == Some(false) {
19381 return wrap_guides;
19382 }
19383
19384 let settings = self.buffer.read(cx).language_settings(cx);
19385 if settings.show_wrap_guides {
19386 match self.soft_wrap_mode(cx) {
19387 SoftWrap::Column(soft_wrap) => {
19388 wrap_guides.push((soft_wrap as usize, true));
19389 }
19390 SoftWrap::Bounded(soft_wrap) => {
19391 wrap_guides.push((soft_wrap as usize, true));
19392 }
19393 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19394 }
19395 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19396 }
19397
19398 wrap_guides
19399 }
19400
19401 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19402 let settings = self.buffer.read(cx).language_settings(cx);
19403 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19404 match mode {
19405 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19406 SoftWrap::None
19407 }
19408 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19409 language_settings::SoftWrap::PreferredLineLength => {
19410 SoftWrap::Column(settings.preferred_line_length)
19411 }
19412 language_settings::SoftWrap::Bounded => {
19413 SoftWrap::Bounded(settings.preferred_line_length)
19414 }
19415 }
19416 }
19417
19418 pub fn set_soft_wrap_mode(
19419 &mut self,
19420 mode: language_settings::SoftWrap,
19421
19422 cx: &mut Context<Self>,
19423 ) {
19424 self.soft_wrap_mode_override = Some(mode);
19425 cx.notify();
19426 }
19427
19428 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19429 self.hard_wrap = hard_wrap;
19430 cx.notify();
19431 }
19432
19433 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19434 self.text_style_refinement = Some(style);
19435 }
19436
19437 /// called by the Element so we know what style we were most recently rendered with.
19438 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19439 // We intentionally do not inform the display map about the minimap style
19440 // so that wrapping is not recalculated and stays consistent for the editor
19441 // and its linked minimap.
19442 if !self.mode.is_minimap() {
19443 let font = style.text.font();
19444 let font_size = style.text.font_size.to_pixels(window.rem_size());
19445 let display_map = self
19446 .placeholder_display_map
19447 .as_ref()
19448 .filter(|_| self.is_empty(cx))
19449 .unwrap_or(&self.display_map);
19450
19451 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19452 }
19453 self.style = Some(style);
19454 }
19455
19456 pub fn style(&self) -> Option<&EditorStyle> {
19457 self.style.as_ref()
19458 }
19459
19460 // Called by the element. This method is not designed to be called outside of the editor
19461 // element's layout code because it does not notify when rewrapping is computed synchronously.
19462 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19463 if self.is_empty(cx) {
19464 self.placeholder_display_map
19465 .as_ref()
19466 .map_or(false, |display_map| {
19467 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19468 })
19469 } else {
19470 self.display_map
19471 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19472 }
19473 }
19474
19475 pub fn set_soft_wrap(&mut self) {
19476 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19477 }
19478
19479 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19480 if self.soft_wrap_mode_override.is_some() {
19481 self.soft_wrap_mode_override.take();
19482 } else {
19483 let soft_wrap = match self.soft_wrap_mode(cx) {
19484 SoftWrap::GitDiff => return,
19485 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19486 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19487 language_settings::SoftWrap::None
19488 }
19489 };
19490 self.soft_wrap_mode_override = Some(soft_wrap);
19491 }
19492 cx.notify();
19493 }
19494
19495 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19496 let Some(workspace) = self.workspace() else {
19497 return;
19498 };
19499 let fs = workspace.read(cx).app_state().fs.clone();
19500 let current_show = TabBarSettings::get_global(cx).show;
19501 update_settings_file(fs, cx, move |setting, _| {
19502 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19503 });
19504 }
19505
19506 pub fn toggle_indent_guides(
19507 &mut self,
19508 _: &ToggleIndentGuides,
19509 _: &mut Window,
19510 cx: &mut Context<Self>,
19511 ) {
19512 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19513 self.buffer
19514 .read(cx)
19515 .language_settings(cx)
19516 .indent_guides
19517 .enabled
19518 });
19519 self.show_indent_guides = Some(!currently_enabled);
19520 cx.notify();
19521 }
19522
19523 fn should_show_indent_guides(&self) -> Option<bool> {
19524 self.show_indent_guides
19525 }
19526
19527 pub fn toggle_line_numbers(
19528 &mut self,
19529 _: &ToggleLineNumbers,
19530 _: &mut Window,
19531 cx: &mut Context<Self>,
19532 ) {
19533 let mut editor_settings = EditorSettings::get_global(cx).clone();
19534 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19535 EditorSettings::override_global(editor_settings, cx);
19536 }
19537
19538 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19539 if let Some(show_line_numbers) = self.show_line_numbers {
19540 return show_line_numbers;
19541 }
19542 EditorSettings::get_global(cx).gutter.line_numbers
19543 }
19544
19545 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19546 self.use_relative_line_numbers
19547 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19548 }
19549
19550 pub fn toggle_relative_line_numbers(
19551 &mut self,
19552 _: &ToggleRelativeLineNumbers,
19553 _: &mut Window,
19554 cx: &mut Context<Self>,
19555 ) {
19556 let is_relative = self.should_use_relative_line_numbers(cx);
19557 self.set_relative_line_number(Some(!is_relative), cx)
19558 }
19559
19560 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19561 self.use_relative_line_numbers = is_relative;
19562 cx.notify();
19563 }
19564
19565 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19566 self.show_gutter = show_gutter;
19567 cx.notify();
19568 }
19569
19570 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19571 self.show_scrollbars = ScrollbarAxes {
19572 horizontal: show,
19573 vertical: show,
19574 };
19575 cx.notify();
19576 }
19577
19578 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19579 self.show_scrollbars.vertical = show;
19580 cx.notify();
19581 }
19582
19583 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19584 self.show_scrollbars.horizontal = show;
19585 cx.notify();
19586 }
19587
19588 pub fn set_minimap_visibility(
19589 &mut self,
19590 minimap_visibility: MinimapVisibility,
19591 window: &mut Window,
19592 cx: &mut Context<Self>,
19593 ) {
19594 if self.minimap_visibility != minimap_visibility {
19595 if minimap_visibility.visible() && self.minimap.is_none() {
19596 let minimap_settings = EditorSettings::get_global(cx).minimap;
19597 self.minimap =
19598 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19599 }
19600 self.minimap_visibility = minimap_visibility;
19601 cx.notify();
19602 }
19603 }
19604
19605 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19606 self.set_show_scrollbars(false, cx);
19607 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19608 }
19609
19610 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19611 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19612 }
19613
19614 /// Normally the text in full mode and auto height editors is padded on the
19615 /// left side by roughly half a character width for improved hit testing.
19616 ///
19617 /// Use this method to disable this for cases where this is not wanted (e.g.
19618 /// if you want to align the editor text with some other text above or below)
19619 /// or if you want to add this padding to single-line editors.
19620 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19621 self.offset_content = offset_content;
19622 cx.notify();
19623 }
19624
19625 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19626 self.show_line_numbers = Some(show_line_numbers);
19627 cx.notify();
19628 }
19629
19630 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19631 self.disable_expand_excerpt_buttons = true;
19632 cx.notify();
19633 }
19634
19635 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19636 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19637 cx.notify();
19638 }
19639
19640 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19641 self.show_code_actions = Some(show_code_actions);
19642 cx.notify();
19643 }
19644
19645 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19646 self.show_runnables = Some(show_runnables);
19647 cx.notify();
19648 }
19649
19650 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19651 self.show_breakpoints = Some(show_breakpoints);
19652 cx.notify();
19653 }
19654
19655 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19656 if self.display_map.read(cx).masked != masked {
19657 self.display_map.update(cx, |map, _| map.masked = masked);
19658 }
19659 cx.notify()
19660 }
19661
19662 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19663 self.show_wrap_guides = Some(show_wrap_guides);
19664 cx.notify();
19665 }
19666
19667 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19668 self.show_indent_guides = Some(show_indent_guides);
19669 cx.notify();
19670 }
19671
19672 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19673 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19674 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19675 && let Some(dir) = file.abs_path(cx).parent()
19676 {
19677 return Some(dir.to_owned());
19678 }
19679 }
19680
19681 None
19682 }
19683
19684 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19685 self.active_excerpt(cx)?
19686 .1
19687 .read(cx)
19688 .file()
19689 .and_then(|f| f.as_local())
19690 }
19691
19692 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19693 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19694 let buffer = buffer.read(cx);
19695 if let Some(project_path) = buffer.project_path(cx) {
19696 let project = self.project()?.read(cx);
19697 project.absolute_path(&project_path, cx)
19698 } else {
19699 buffer
19700 .file()
19701 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19702 }
19703 })
19704 }
19705
19706 pub fn reveal_in_finder(
19707 &mut self,
19708 _: &RevealInFileManager,
19709 _window: &mut Window,
19710 cx: &mut Context<Self>,
19711 ) {
19712 if let Some(target) = self.target_file(cx) {
19713 cx.reveal_path(&target.abs_path(cx));
19714 }
19715 }
19716
19717 pub fn copy_path(
19718 &mut self,
19719 _: &zed_actions::workspace::CopyPath,
19720 _window: &mut Window,
19721 cx: &mut Context<Self>,
19722 ) {
19723 if let Some(path) = self.target_file_abs_path(cx)
19724 && let Some(path) = path.to_str()
19725 {
19726 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19727 } else {
19728 cx.propagate();
19729 }
19730 }
19731
19732 pub fn copy_relative_path(
19733 &mut self,
19734 _: &zed_actions::workspace::CopyRelativePath,
19735 _window: &mut Window,
19736 cx: &mut Context<Self>,
19737 ) {
19738 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19739 let project = self.project()?.read(cx);
19740 let path = buffer.read(cx).file()?.path();
19741 let path = path.display(project.path_style(cx));
19742 Some(path)
19743 }) {
19744 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19745 } else {
19746 cx.propagate();
19747 }
19748 }
19749
19750 /// Returns the project path for the editor's buffer, if any buffer is
19751 /// opened in the editor.
19752 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19753 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19754 buffer.read(cx).project_path(cx)
19755 } else {
19756 None
19757 }
19758 }
19759
19760 // Returns true if the editor handled a go-to-line request
19761 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19762 maybe!({
19763 let breakpoint_store = self.breakpoint_store.as_ref()?;
19764
19765 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19766 else {
19767 self.clear_row_highlights::<ActiveDebugLine>();
19768 return None;
19769 };
19770
19771 let position = active_stack_frame.position;
19772 let buffer_id = position.buffer_id?;
19773 let snapshot = self
19774 .project
19775 .as_ref()?
19776 .read(cx)
19777 .buffer_for_id(buffer_id, cx)?
19778 .read(cx)
19779 .snapshot();
19780
19781 let mut handled = false;
19782 for (id, ExcerptRange { context, .. }) in
19783 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19784 {
19785 if context.start.cmp(&position, &snapshot).is_ge()
19786 || context.end.cmp(&position, &snapshot).is_lt()
19787 {
19788 continue;
19789 }
19790 let snapshot = self.buffer.read(cx).snapshot(cx);
19791 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19792
19793 handled = true;
19794 self.clear_row_highlights::<ActiveDebugLine>();
19795
19796 self.go_to_line::<ActiveDebugLine>(
19797 multibuffer_anchor,
19798 Some(cx.theme().colors().editor_debugger_active_line_background),
19799 window,
19800 cx,
19801 );
19802
19803 cx.notify();
19804 }
19805
19806 handled.then_some(())
19807 })
19808 .is_some()
19809 }
19810
19811 pub fn copy_file_name_without_extension(
19812 &mut self,
19813 _: &CopyFileNameWithoutExtension,
19814 _: &mut Window,
19815 cx: &mut Context<Self>,
19816 ) {
19817 if let Some(file) = self.target_file(cx)
19818 && let Some(file_stem) = file.path().file_stem()
19819 {
19820 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19821 }
19822 }
19823
19824 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19825 if let Some(file) = self.target_file(cx)
19826 && let Some(name) = file.path().file_name()
19827 {
19828 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19829 }
19830 }
19831
19832 pub fn toggle_git_blame(
19833 &mut self,
19834 _: &::git::Blame,
19835 window: &mut Window,
19836 cx: &mut Context<Self>,
19837 ) {
19838 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19839
19840 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19841 self.start_git_blame(true, window, cx);
19842 }
19843
19844 cx.notify();
19845 }
19846
19847 pub fn toggle_git_blame_inline(
19848 &mut self,
19849 _: &ToggleGitBlameInline,
19850 window: &mut Window,
19851 cx: &mut Context<Self>,
19852 ) {
19853 self.toggle_git_blame_inline_internal(true, window, cx);
19854 cx.notify();
19855 }
19856
19857 pub fn open_git_blame_commit(
19858 &mut self,
19859 _: &OpenGitBlameCommit,
19860 window: &mut Window,
19861 cx: &mut Context<Self>,
19862 ) {
19863 self.open_git_blame_commit_internal(window, cx);
19864 }
19865
19866 fn open_git_blame_commit_internal(
19867 &mut self,
19868 window: &mut Window,
19869 cx: &mut Context<Self>,
19870 ) -> Option<()> {
19871 let blame = self.blame.as_ref()?;
19872 let snapshot = self.snapshot(window, cx);
19873 let cursor = self
19874 .selections
19875 .newest::<Point>(&snapshot.display_snapshot)
19876 .head();
19877 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19878 let (_, blame_entry) = blame
19879 .update(cx, |blame, cx| {
19880 blame
19881 .blame_for_rows(
19882 &[RowInfo {
19883 buffer_id: Some(buffer.remote_id()),
19884 buffer_row: Some(point.row),
19885 ..Default::default()
19886 }],
19887 cx,
19888 )
19889 .next()
19890 })
19891 .flatten()?;
19892 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19893 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19894 let workspace = self.workspace()?.downgrade();
19895 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19896 None
19897 }
19898
19899 pub fn git_blame_inline_enabled(&self) -> bool {
19900 self.git_blame_inline_enabled
19901 }
19902
19903 pub fn toggle_selection_menu(
19904 &mut self,
19905 _: &ToggleSelectionMenu,
19906 _: &mut Window,
19907 cx: &mut Context<Self>,
19908 ) {
19909 self.show_selection_menu = self
19910 .show_selection_menu
19911 .map(|show_selections_menu| !show_selections_menu)
19912 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19913
19914 cx.notify();
19915 }
19916
19917 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19918 self.show_selection_menu
19919 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19920 }
19921
19922 fn start_git_blame(
19923 &mut self,
19924 user_triggered: bool,
19925 window: &mut Window,
19926 cx: &mut Context<Self>,
19927 ) {
19928 if let Some(project) = self.project() {
19929 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19930 && buffer.read(cx).file().is_none()
19931 {
19932 return;
19933 }
19934
19935 let focused = self.focus_handle(cx).contains_focused(window, cx);
19936
19937 let project = project.clone();
19938 let blame = cx
19939 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19940 self.blame_subscription =
19941 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19942 self.blame = Some(blame);
19943 }
19944 }
19945
19946 fn toggle_git_blame_inline_internal(
19947 &mut self,
19948 user_triggered: bool,
19949 window: &mut Window,
19950 cx: &mut Context<Self>,
19951 ) {
19952 if self.git_blame_inline_enabled {
19953 self.git_blame_inline_enabled = false;
19954 self.show_git_blame_inline = false;
19955 self.show_git_blame_inline_delay_task.take();
19956 } else {
19957 self.git_blame_inline_enabled = true;
19958 self.start_git_blame_inline(user_triggered, window, cx);
19959 }
19960
19961 cx.notify();
19962 }
19963
19964 fn start_git_blame_inline(
19965 &mut self,
19966 user_triggered: bool,
19967 window: &mut Window,
19968 cx: &mut Context<Self>,
19969 ) {
19970 self.start_git_blame(user_triggered, window, cx);
19971
19972 if ProjectSettings::get_global(cx)
19973 .git
19974 .inline_blame_delay()
19975 .is_some()
19976 {
19977 self.start_inline_blame_timer(window, cx);
19978 } else {
19979 self.show_git_blame_inline = true
19980 }
19981 }
19982
19983 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19984 self.blame.as_ref()
19985 }
19986
19987 pub fn show_git_blame_gutter(&self) -> bool {
19988 self.show_git_blame_gutter
19989 }
19990
19991 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19992 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19993 }
19994
19995 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19996 self.show_git_blame_inline
19997 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19998 && !self.newest_selection_head_on_empty_line(cx)
19999 && self.has_blame_entries(cx)
20000 }
20001
20002 fn has_blame_entries(&self, cx: &App) -> bool {
20003 self.blame()
20004 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20005 }
20006
20007 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20008 let cursor_anchor = self.selections.newest_anchor().head();
20009
20010 let snapshot = self.buffer.read(cx).snapshot(cx);
20011 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20012
20013 snapshot.line_len(buffer_row) == 0
20014 }
20015
20016 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20017 let buffer_and_selection = maybe!({
20018 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20019 let selection_range = selection.range();
20020
20021 let multi_buffer = self.buffer().read(cx);
20022 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20023 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20024
20025 let (buffer, range, _) = if selection.reversed {
20026 buffer_ranges.first()
20027 } else {
20028 buffer_ranges.last()
20029 }?;
20030
20031 let selection = text::ToPoint::to_point(&range.start, buffer).row
20032 ..text::ToPoint::to_point(&range.end, buffer).row;
20033 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20034 });
20035
20036 let Some((buffer, selection)) = buffer_and_selection else {
20037 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20038 };
20039
20040 let Some(project) = self.project() else {
20041 return Task::ready(Err(anyhow!("editor does not have project")));
20042 };
20043
20044 project.update(cx, |project, cx| {
20045 project.get_permalink_to_line(&buffer, selection, cx)
20046 })
20047 }
20048
20049 pub fn copy_permalink_to_line(
20050 &mut self,
20051 _: &CopyPermalinkToLine,
20052 window: &mut Window,
20053 cx: &mut Context<Self>,
20054 ) {
20055 let permalink_task = self.get_permalink_to_line(cx);
20056 let workspace = self.workspace();
20057
20058 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20059 Ok(permalink) => {
20060 cx.update(|_, cx| {
20061 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20062 })
20063 .ok();
20064 }
20065 Err(err) => {
20066 let message = format!("Failed to copy permalink: {err}");
20067
20068 anyhow::Result::<()>::Err(err).log_err();
20069
20070 if let Some(workspace) = workspace {
20071 workspace
20072 .update_in(cx, |workspace, _, cx| {
20073 struct CopyPermalinkToLine;
20074
20075 workspace.show_toast(
20076 Toast::new(
20077 NotificationId::unique::<CopyPermalinkToLine>(),
20078 message,
20079 ),
20080 cx,
20081 )
20082 })
20083 .ok();
20084 }
20085 }
20086 })
20087 .detach();
20088 }
20089
20090 pub fn copy_file_location(
20091 &mut self,
20092 _: &CopyFileLocation,
20093 _: &mut Window,
20094 cx: &mut Context<Self>,
20095 ) {
20096 let selection = self
20097 .selections
20098 .newest::<Point>(&self.display_snapshot(cx))
20099 .start
20100 .row
20101 + 1;
20102 if let Some(file) = self.target_file(cx) {
20103 let path = file.path().display(file.path_style(cx));
20104 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20105 }
20106 }
20107
20108 pub fn open_permalink_to_line(
20109 &mut self,
20110 _: &OpenPermalinkToLine,
20111 window: &mut Window,
20112 cx: &mut Context<Self>,
20113 ) {
20114 let permalink_task = self.get_permalink_to_line(cx);
20115 let workspace = self.workspace();
20116
20117 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20118 Ok(permalink) => {
20119 cx.update(|_, cx| {
20120 cx.open_url(permalink.as_ref());
20121 })
20122 .ok();
20123 }
20124 Err(err) => {
20125 let message = format!("Failed to open permalink: {err}");
20126
20127 anyhow::Result::<()>::Err(err).log_err();
20128
20129 if let Some(workspace) = workspace {
20130 workspace
20131 .update(cx, |workspace, cx| {
20132 struct OpenPermalinkToLine;
20133
20134 workspace.show_toast(
20135 Toast::new(
20136 NotificationId::unique::<OpenPermalinkToLine>(),
20137 message,
20138 ),
20139 cx,
20140 )
20141 })
20142 .ok();
20143 }
20144 }
20145 })
20146 .detach();
20147 }
20148
20149 pub fn insert_uuid_v4(
20150 &mut self,
20151 _: &InsertUuidV4,
20152 window: &mut Window,
20153 cx: &mut Context<Self>,
20154 ) {
20155 self.insert_uuid(UuidVersion::V4, window, cx);
20156 }
20157
20158 pub fn insert_uuid_v7(
20159 &mut self,
20160 _: &InsertUuidV7,
20161 window: &mut Window,
20162 cx: &mut Context<Self>,
20163 ) {
20164 self.insert_uuid(UuidVersion::V7, window, cx);
20165 }
20166
20167 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20168 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20169 self.transact(window, cx, |this, window, cx| {
20170 let edits = this
20171 .selections
20172 .all::<Point>(&this.display_snapshot(cx))
20173 .into_iter()
20174 .map(|selection| {
20175 let uuid = match version {
20176 UuidVersion::V4 => uuid::Uuid::new_v4(),
20177 UuidVersion::V7 => uuid::Uuid::now_v7(),
20178 };
20179
20180 (selection.range(), uuid.to_string())
20181 });
20182 this.edit(edits, cx);
20183 this.refresh_edit_prediction(true, false, window, cx);
20184 });
20185 }
20186
20187 pub fn open_selections_in_multibuffer(
20188 &mut self,
20189 _: &OpenSelectionsInMultibuffer,
20190 window: &mut Window,
20191 cx: &mut Context<Self>,
20192 ) {
20193 let multibuffer = self.buffer.read(cx);
20194
20195 let Some(buffer) = multibuffer.as_singleton() else {
20196 return;
20197 };
20198
20199 let Some(workspace) = self.workspace() else {
20200 return;
20201 };
20202
20203 let title = multibuffer.title(cx).to_string();
20204
20205 let locations = self
20206 .selections
20207 .all_anchors(cx)
20208 .iter()
20209 .map(|selection| {
20210 (
20211 buffer.clone(),
20212 (selection.start.text_anchor..selection.end.text_anchor)
20213 .to_point(buffer.read(cx)),
20214 )
20215 })
20216 .into_group_map();
20217
20218 cx.spawn_in(window, async move |_, cx| {
20219 workspace.update_in(cx, |workspace, window, cx| {
20220 Self::open_locations_in_multibuffer(
20221 workspace,
20222 locations,
20223 format!("Selections for '{title}'"),
20224 false,
20225 MultibufferSelectionMode::All,
20226 window,
20227 cx,
20228 );
20229 })
20230 })
20231 .detach();
20232 }
20233
20234 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20235 /// last highlight added will be used.
20236 ///
20237 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20238 pub fn highlight_rows<T: 'static>(
20239 &mut self,
20240 range: Range<Anchor>,
20241 color: Hsla,
20242 options: RowHighlightOptions,
20243 cx: &mut Context<Self>,
20244 ) {
20245 let snapshot = self.buffer().read(cx).snapshot(cx);
20246 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20247 let ix = row_highlights.binary_search_by(|highlight| {
20248 Ordering::Equal
20249 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20250 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20251 });
20252
20253 if let Err(mut ix) = ix {
20254 let index = post_inc(&mut self.highlight_order);
20255
20256 // If this range intersects with the preceding highlight, then merge it with
20257 // the preceding highlight. Otherwise insert a new highlight.
20258 let mut merged = false;
20259 if ix > 0 {
20260 let prev_highlight = &mut row_highlights[ix - 1];
20261 if prev_highlight
20262 .range
20263 .end
20264 .cmp(&range.start, &snapshot)
20265 .is_ge()
20266 {
20267 ix -= 1;
20268 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20269 prev_highlight.range.end = range.end;
20270 }
20271 merged = true;
20272 prev_highlight.index = index;
20273 prev_highlight.color = color;
20274 prev_highlight.options = options;
20275 }
20276 }
20277
20278 if !merged {
20279 row_highlights.insert(
20280 ix,
20281 RowHighlight {
20282 range,
20283 index,
20284 color,
20285 options,
20286 type_id: TypeId::of::<T>(),
20287 },
20288 );
20289 }
20290
20291 // If any of the following highlights intersect with this one, merge them.
20292 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20293 let highlight = &row_highlights[ix];
20294 if next_highlight
20295 .range
20296 .start
20297 .cmp(&highlight.range.end, &snapshot)
20298 .is_le()
20299 {
20300 if next_highlight
20301 .range
20302 .end
20303 .cmp(&highlight.range.end, &snapshot)
20304 .is_gt()
20305 {
20306 row_highlights[ix].range.end = next_highlight.range.end;
20307 }
20308 row_highlights.remove(ix + 1);
20309 } else {
20310 break;
20311 }
20312 }
20313 }
20314 }
20315
20316 /// Remove any highlighted row ranges of the given type that intersect the
20317 /// given ranges.
20318 pub fn remove_highlighted_rows<T: 'static>(
20319 &mut self,
20320 ranges_to_remove: Vec<Range<Anchor>>,
20321 cx: &mut Context<Self>,
20322 ) {
20323 let snapshot = self.buffer().read(cx).snapshot(cx);
20324 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20325 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20326 row_highlights.retain(|highlight| {
20327 while let Some(range_to_remove) = ranges_to_remove.peek() {
20328 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20329 Ordering::Less | Ordering::Equal => {
20330 ranges_to_remove.next();
20331 }
20332 Ordering::Greater => {
20333 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20334 Ordering::Less | Ordering::Equal => {
20335 return false;
20336 }
20337 Ordering::Greater => break,
20338 }
20339 }
20340 }
20341 }
20342
20343 true
20344 })
20345 }
20346
20347 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20348 pub fn clear_row_highlights<T: 'static>(&mut self) {
20349 self.highlighted_rows.remove(&TypeId::of::<T>());
20350 }
20351
20352 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20353 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20354 self.highlighted_rows
20355 .get(&TypeId::of::<T>())
20356 .map_or(&[] as &[_], |vec| vec.as_slice())
20357 .iter()
20358 .map(|highlight| (highlight.range.clone(), highlight.color))
20359 }
20360
20361 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20362 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20363 /// Allows to ignore certain kinds of highlights.
20364 pub fn highlighted_display_rows(
20365 &self,
20366 window: &mut Window,
20367 cx: &mut App,
20368 ) -> BTreeMap<DisplayRow, LineHighlight> {
20369 let snapshot = self.snapshot(window, cx);
20370 let mut used_highlight_orders = HashMap::default();
20371 self.highlighted_rows
20372 .iter()
20373 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20374 .fold(
20375 BTreeMap::<DisplayRow, LineHighlight>::new(),
20376 |mut unique_rows, highlight| {
20377 let start = highlight.range.start.to_display_point(&snapshot);
20378 let end = highlight.range.end.to_display_point(&snapshot);
20379 let start_row = start.row().0;
20380 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20381 && end.column() == 0
20382 {
20383 end.row().0.saturating_sub(1)
20384 } else {
20385 end.row().0
20386 };
20387 for row in start_row..=end_row {
20388 let used_index =
20389 used_highlight_orders.entry(row).or_insert(highlight.index);
20390 if highlight.index >= *used_index {
20391 *used_index = highlight.index;
20392 unique_rows.insert(
20393 DisplayRow(row),
20394 LineHighlight {
20395 include_gutter: highlight.options.include_gutter,
20396 border: None,
20397 background: highlight.color.into(),
20398 type_id: Some(highlight.type_id),
20399 },
20400 );
20401 }
20402 }
20403 unique_rows
20404 },
20405 )
20406 }
20407
20408 pub fn highlighted_display_row_for_autoscroll(
20409 &self,
20410 snapshot: &DisplaySnapshot,
20411 ) -> Option<DisplayRow> {
20412 self.highlighted_rows
20413 .values()
20414 .flat_map(|highlighted_rows| highlighted_rows.iter())
20415 .filter_map(|highlight| {
20416 if highlight.options.autoscroll {
20417 Some(highlight.range.start.to_display_point(snapshot).row())
20418 } else {
20419 None
20420 }
20421 })
20422 .min()
20423 }
20424
20425 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20426 self.highlight_background::<SearchWithinRange>(
20427 ranges,
20428 |colors| colors.colors().editor_document_highlight_read_background,
20429 cx,
20430 )
20431 }
20432
20433 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20434 self.breadcrumb_header = Some(new_header);
20435 }
20436
20437 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20438 self.clear_background_highlights::<SearchWithinRange>(cx);
20439 }
20440
20441 pub fn highlight_background<T: 'static>(
20442 &mut self,
20443 ranges: &[Range<Anchor>],
20444 color_fetcher: fn(&Theme) -> Hsla,
20445 cx: &mut Context<Self>,
20446 ) {
20447 self.background_highlights.insert(
20448 HighlightKey::Type(TypeId::of::<T>()),
20449 (color_fetcher, Arc::from(ranges)),
20450 );
20451 self.scrollbar_marker_state.dirty = true;
20452 cx.notify();
20453 }
20454
20455 pub fn highlight_background_key<T: 'static>(
20456 &mut self,
20457 key: usize,
20458 ranges: &[Range<Anchor>],
20459 color_fetcher: fn(&Theme) -> Hsla,
20460 cx: &mut Context<Self>,
20461 ) {
20462 self.background_highlights.insert(
20463 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20464 (color_fetcher, Arc::from(ranges)),
20465 );
20466 self.scrollbar_marker_state.dirty = true;
20467 cx.notify();
20468 }
20469
20470 pub fn clear_background_highlights<T: 'static>(
20471 &mut self,
20472 cx: &mut Context<Self>,
20473 ) -> Option<BackgroundHighlight> {
20474 let text_highlights = self
20475 .background_highlights
20476 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20477 if !text_highlights.1.is_empty() {
20478 self.scrollbar_marker_state.dirty = true;
20479 cx.notify();
20480 }
20481 Some(text_highlights)
20482 }
20483
20484 pub fn highlight_gutter<T: 'static>(
20485 &mut self,
20486 ranges: impl Into<Vec<Range<Anchor>>>,
20487 color_fetcher: fn(&App) -> Hsla,
20488 cx: &mut Context<Self>,
20489 ) {
20490 self.gutter_highlights
20491 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20492 cx.notify();
20493 }
20494
20495 pub fn clear_gutter_highlights<T: 'static>(
20496 &mut self,
20497 cx: &mut Context<Self>,
20498 ) -> Option<GutterHighlight> {
20499 cx.notify();
20500 self.gutter_highlights.remove(&TypeId::of::<T>())
20501 }
20502
20503 pub fn insert_gutter_highlight<T: 'static>(
20504 &mut self,
20505 range: Range<Anchor>,
20506 color_fetcher: fn(&App) -> Hsla,
20507 cx: &mut Context<Self>,
20508 ) {
20509 let snapshot = self.buffer().read(cx).snapshot(cx);
20510 let mut highlights = self
20511 .gutter_highlights
20512 .remove(&TypeId::of::<T>())
20513 .map(|(_, highlights)| highlights)
20514 .unwrap_or_default();
20515 let ix = highlights.binary_search_by(|highlight| {
20516 Ordering::Equal
20517 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20518 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20519 });
20520 if let Err(ix) = ix {
20521 highlights.insert(ix, range);
20522 }
20523 self.gutter_highlights
20524 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20525 }
20526
20527 pub fn remove_gutter_highlights<T: 'static>(
20528 &mut self,
20529 ranges_to_remove: Vec<Range<Anchor>>,
20530 cx: &mut Context<Self>,
20531 ) {
20532 let snapshot = self.buffer().read(cx).snapshot(cx);
20533 let Some((color_fetcher, mut gutter_highlights)) =
20534 self.gutter_highlights.remove(&TypeId::of::<T>())
20535 else {
20536 return;
20537 };
20538 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20539 gutter_highlights.retain(|highlight| {
20540 while let Some(range_to_remove) = ranges_to_remove.peek() {
20541 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20542 Ordering::Less | Ordering::Equal => {
20543 ranges_to_remove.next();
20544 }
20545 Ordering::Greater => {
20546 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20547 Ordering::Less | Ordering::Equal => {
20548 return false;
20549 }
20550 Ordering::Greater => break,
20551 }
20552 }
20553 }
20554 }
20555
20556 true
20557 });
20558 self.gutter_highlights
20559 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20560 }
20561
20562 #[cfg(feature = "test-support")]
20563 pub fn all_text_highlights(
20564 &self,
20565 window: &mut Window,
20566 cx: &mut Context<Self>,
20567 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20568 let snapshot = self.snapshot(window, cx);
20569 self.display_map.update(cx, |display_map, _| {
20570 display_map
20571 .all_text_highlights()
20572 .map(|highlight| {
20573 let (style, ranges) = highlight.as_ref();
20574 (
20575 *style,
20576 ranges
20577 .iter()
20578 .map(|range| range.clone().to_display_points(&snapshot))
20579 .collect(),
20580 )
20581 })
20582 .collect()
20583 })
20584 }
20585
20586 #[cfg(feature = "test-support")]
20587 pub fn all_text_background_highlights(
20588 &self,
20589 window: &mut Window,
20590 cx: &mut Context<Self>,
20591 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20592 let snapshot = self.snapshot(window, cx);
20593 let buffer = &snapshot.buffer_snapshot();
20594 let start = buffer.anchor_before(0);
20595 let end = buffer.anchor_after(buffer.len());
20596 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20597 }
20598
20599 #[cfg(any(test, feature = "test-support"))]
20600 pub fn sorted_background_highlights_in_range(
20601 &self,
20602 search_range: Range<Anchor>,
20603 display_snapshot: &DisplaySnapshot,
20604 theme: &Theme,
20605 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20606 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20607 res.sort_by(|a, b| {
20608 a.0.start
20609 .cmp(&b.0.start)
20610 .then_with(|| a.0.end.cmp(&b.0.end))
20611 .then_with(|| a.1.cmp(&b.1))
20612 });
20613 res
20614 }
20615
20616 #[cfg(feature = "test-support")]
20617 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20618 let snapshot = self.buffer().read(cx).snapshot(cx);
20619
20620 let highlights = self
20621 .background_highlights
20622 .get(&HighlightKey::Type(TypeId::of::<
20623 items::BufferSearchHighlights,
20624 >()));
20625
20626 if let Some((_color, ranges)) = highlights {
20627 ranges
20628 .iter()
20629 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20630 .collect_vec()
20631 } else {
20632 vec![]
20633 }
20634 }
20635
20636 fn document_highlights_for_position<'a>(
20637 &'a self,
20638 position: Anchor,
20639 buffer: &'a MultiBufferSnapshot,
20640 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20641 let read_highlights = self
20642 .background_highlights
20643 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20644 .map(|h| &h.1);
20645 let write_highlights = self
20646 .background_highlights
20647 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20648 .map(|h| &h.1);
20649 let left_position = position.bias_left(buffer);
20650 let right_position = position.bias_right(buffer);
20651 read_highlights
20652 .into_iter()
20653 .chain(write_highlights)
20654 .flat_map(move |ranges| {
20655 let start_ix = match ranges.binary_search_by(|probe| {
20656 let cmp = probe.end.cmp(&left_position, buffer);
20657 if cmp.is_ge() {
20658 Ordering::Greater
20659 } else {
20660 Ordering::Less
20661 }
20662 }) {
20663 Ok(i) | Err(i) => i,
20664 };
20665
20666 ranges[start_ix..]
20667 .iter()
20668 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20669 })
20670 }
20671
20672 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20673 self.background_highlights
20674 .get(&HighlightKey::Type(TypeId::of::<T>()))
20675 .is_some_and(|(_, highlights)| !highlights.is_empty())
20676 }
20677
20678 /// Returns all background highlights for a given range.
20679 ///
20680 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20681 pub fn background_highlights_in_range(
20682 &self,
20683 search_range: Range<Anchor>,
20684 display_snapshot: &DisplaySnapshot,
20685 theme: &Theme,
20686 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20687 let mut results = Vec::new();
20688 for (color_fetcher, ranges) in self.background_highlights.values() {
20689 let color = color_fetcher(theme);
20690 let start_ix = match ranges.binary_search_by(|probe| {
20691 let cmp = probe
20692 .end
20693 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20694 if cmp.is_gt() {
20695 Ordering::Greater
20696 } else {
20697 Ordering::Less
20698 }
20699 }) {
20700 Ok(i) | Err(i) => i,
20701 };
20702 for range in &ranges[start_ix..] {
20703 if range
20704 .start
20705 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20706 .is_ge()
20707 {
20708 break;
20709 }
20710
20711 let start = range.start.to_display_point(display_snapshot);
20712 let end = range.end.to_display_point(display_snapshot);
20713 results.push((start..end, color))
20714 }
20715 }
20716 results
20717 }
20718
20719 pub fn gutter_highlights_in_range(
20720 &self,
20721 search_range: Range<Anchor>,
20722 display_snapshot: &DisplaySnapshot,
20723 cx: &App,
20724 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20725 let mut results = Vec::new();
20726 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20727 let color = color_fetcher(cx);
20728 let start_ix = match ranges.binary_search_by(|probe| {
20729 let cmp = probe
20730 .end
20731 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20732 if cmp.is_gt() {
20733 Ordering::Greater
20734 } else {
20735 Ordering::Less
20736 }
20737 }) {
20738 Ok(i) | Err(i) => i,
20739 };
20740 for range in &ranges[start_ix..] {
20741 if range
20742 .start
20743 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20744 .is_ge()
20745 {
20746 break;
20747 }
20748
20749 let start = range.start.to_display_point(display_snapshot);
20750 let end = range.end.to_display_point(display_snapshot);
20751 results.push((start..end, color))
20752 }
20753 }
20754 results
20755 }
20756
20757 /// Get the text ranges corresponding to the redaction query
20758 pub fn redacted_ranges(
20759 &self,
20760 search_range: Range<Anchor>,
20761 display_snapshot: &DisplaySnapshot,
20762 cx: &App,
20763 ) -> Vec<Range<DisplayPoint>> {
20764 display_snapshot
20765 .buffer_snapshot()
20766 .redacted_ranges(search_range, |file| {
20767 if let Some(file) = file {
20768 file.is_private()
20769 && EditorSettings::get(
20770 Some(SettingsLocation {
20771 worktree_id: file.worktree_id(cx),
20772 path: file.path().as_ref(),
20773 }),
20774 cx,
20775 )
20776 .redact_private_values
20777 } else {
20778 false
20779 }
20780 })
20781 .map(|range| {
20782 range.start.to_display_point(display_snapshot)
20783 ..range.end.to_display_point(display_snapshot)
20784 })
20785 .collect()
20786 }
20787
20788 pub fn highlight_text_key<T: 'static>(
20789 &mut self,
20790 key: usize,
20791 ranges: Vec<Range<Anchor>>,
20792 style: HighlightStyle,
20793 cx: &mut Context<Self>,
20794 ) {
20795 self.display_map.update(cx, |map, _| {
20796 map.highlight_text(
20797 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20798 ranges,
20799 style,
20800 );
20801 });
20802 cx.notify();
20803 }
20804
20805 pub fn highlight_text<T: 'static>(
20806 &mut self,
20807 ranges: Vec<Range<Anchor>>,
20808 style: HighlightStyle,
20809 cx: &mut Context<Self>,
20810 ) {
20811 self.display_map.update(cx, |map, _| {
20812 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20813 });
20814 cx.notify();
20815 }
20816
20817 pub fn text_highlights<'a, T: 'static>(
20818 &'a self,
20819 cx: &'a App,
20820 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20821 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20822 }
20823
20824 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20825 let cleared = self
20826 .display_map
20827 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20828 if cleared {
20829 cx.notify();
20830 }
20831 }
20832
20833 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20834 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20835 && self.focus_handle.is_focused(window)
20836 }
20837
20838 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20839 self.show_cursor_when_unfocused = is_enabled;
20840 cx.notify();
20841 }
20842
20843 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20844 cx.notify();
20845 }
20846
20847 fn on_debug_session_event(
20848 &mut self,
20849 _session: Entity<Session>,
20850 event: &SessionEvent,
20851 cx: &mut Context<Self>,
20852 ) {
20853 if let SessionEvent::InvalidateInlineValue = event {
20854 self.refresh_inline_values(cx);
20855 }
20856 }
20857
20858 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20859 let Some(project) = self.project.clone() else {
20860 return;
20861 };
20862
20863 if !self.inline_value_cache.enabled {
20864 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20865 self.splice_inlays(&inlays, Vec::new(), cx);
20866 return;
20867 }
20868
20869 let current_execution_position = self
20870 .highlighted_rows
20871 .get(&TypeId::of::<ActiveDebugLine>())
20872 .and_then(|lines| lines.last().map(|line| line.range.end));
20873
20874 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20875 let inline_values = editor
20876 .update(cx, |editor, cx| {
20877 let Some(current_execution_position) = current_execution_position else {
20878 return Some(Task::ready(Ok(Vec::new())));
20879 };
20880
20881 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20882 let snapshot = buffer.snapshot(cx);
20883
20884 let excerpt = snapshot.excerpt_containing(
20885 current_execution_position..current_execution_position,
20886 )?;
20887
20888 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20889 })?;
20890
20891 let range =
20892 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20893
20894 project.inline_values(buffer, range, cx)
20895 })
20896 .ok()
20897 .flatten()?
20898 .await
20899 .context("refreshing debugger inlays")
20900 .log_err()?;
20901
20902 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20903
20904 for (buffer_id, inline_value) in inline_values
20905 .into_iter()
20906 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20907 {
20908 buffer_inline_values
20909 .entry(buffer_id)
20910 .or_default()
20911 .push(inline_value);
20912 }
20913
20914 editor
20915 .update(cx, |editor, cx| {
20916 let snapshot = editor.buffer.read(cx).snapshot(cx);
20917 let mut new_inlays = Vec::default();
20918
20919 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20920 let buffer_id = buffer_snapshot.remote_id();
20921 buffer_inline_values
20922 .get(&buffer_id)
20923 .into_iter()
20924 .flatten()
20925 .for_each(|hint| {
20926 let inlay = Inlay::debugger(
20927 post_inc(&mut editor.next_inlay_id),
20928 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20929 hint.text(),
20930 );
20931 if !inlay.text().chars().contains(&'\n') {
20932 new_inlays.push(inlay);
20933 }
20934 });
20935 }
20936
20937 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20938 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20939
20940 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20941 })
20942 .ok()?;
20943 Some(())
20944 });
20945 }
20946
20947 fn on_buffer_event(
20948 &mut self,
20949 multibuffer: &Entity<MultiBuffer>,
20950 event: &multi_buffer::Event,
20951 window: &mut Window,
20952 cx: &mut Context<Self>,
20953 ) {
20954 match event {
20955 multi_buffer::Event::Edited { edited_buffer } => {
20956 self.scrollbar_marker_state.dirty = true;
20957 self.active_indent_guides_state.dirty = true;
20958 self.refresh_active_diagnostics(cx);
20959 self.refresh_code_actions(window, cx);
20960 self.refresh_selected_text_highlights(true, window, cx);
20961 self.refresh_single_line_folds(window, cx);
20962 self.refresh_matching_bracket_highlights(window, cx);
20963 if self.has_active_edit_prediction() {
20964 self.update_visible_edit_prediction(window, cx);
20965 }
20966
20967 if let Some(buffer) = edited_buffer {
20968 if buffer.read(cx).file().is_none() {
20969 cx.emit(EditorEvent::TitleChanged);
20970 }
20971
20972 if self.project.is_some() {
20973 let buffer_id = buffer.read(cx).remote_id();
20974 self.register_buffer(buffer_id, cx);
20975 self.update_lsp_data(Some(buffer_id), window, cx);
20976 self.refresh_inlay_hints(
20977 InlayHintRefreshReason::BufferEdited(buffer_id),
20978 cx,
20979 );
20980 }
20981 }
20982
20983 cx.emit(EditorEvent::BufferEdited);
20984 cx.emit(SearchEvent::MatchesInvalidated);
20985
20986 let Some(project) = &self.project else { return };
20987 let (telemetry, is_via_ssh) = {
20988 let project = project.read(cx);
20989 let telemetry = project.client().telemetry().clone();
20990 let is_via_ssh = project.is_via_remote_server();
20991 (telemetry, is_via_ssh)
20992 };
20993 telemetry.log_edit_event("editor", is_via_ssh);
20994 }
20995 multi_buffer::Event::ExcerptsAdded {
20996 buffer,
20997 predecessor,
20998 excerpts,
20999 } => {
21000 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21001 let buffer_id = buffer.read(cx).remote_id();
21002 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21003 && let Some(project) = &self.project
21004 {
21005 update_uncommitted_diff_for_buffer(
21006 cx.entity(),
21007 project,
21008 [buffer.clone()],
21009 self.buffer.clone(),
21010 cx,
21011 )
21012 .detach();
21013 }
21014 self.update_lsp_data(Some(buffer_id), window, cx);
21015 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21016 cx.emit(EditorEvent::ExcerptsAdded {
21017 buffer: buffer.clone(),
21018 predecessor: *predecessor,
21019 excerpts: excerpts.clone(),
21020 });
21021 }
21022 multi_buffer::Event::ExcerptsRemoved {
21023 ids,
21024 removed_buffer_ids,
21025 } => {
21026 if let Some(inlay_hints) = &mut self.inlay_hints {
21027 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21028 }
21029 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21030 for buffer_id in removed_buffer_ids {
21031 self.registered_buffers.remove(buffer_id);
21032 }
21033 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21034 cx.emit(EditorEvent::ExcerptsRemoved {
21035 ids: ids.clone(),
21036 removed_buffer_ids: removed_buffer_ids.clone(),
21037 });
21038 }
21039 multi_buffer::Event::ExcerptsEdited {
21040 excerpt_ids,
21041 buffer_ids,
21042 } => {
21043 self.display_map.update(cx, |map, cx| {
21044 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21045 });
21046 cx.emit(EditorEvent::ExcerptsEdited {
21047 ids: excerpt_ids.clone(),
21048 });
21049 }
21050 multi_buffer::Event::ExcerptsExpanded { ids } => {
21051 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21052 self.refresh_document_highlights(cx);
21053 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21054 }
21055 multi_buffer::Event::Reparsed(buffer_id) => {
21056 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21057 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21058
21059 cx.emit(EditorEvent::Reparsed(*buffer_id));
21060 }
21061 multi_buffer::Event::DiffHunksToggled => {
21062 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21063 }
21064 multi_buffer::Event::LanguageChanged(buffer_id) => {
21065 self.registered_buffers.remove(&buffer_id);
21066 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21067 cx.emit(EditorEvent::Reparsed(*buffer_id));
21068 cx.notify();
21069 }
21070 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21071 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21072 multi_buffer::Event::FileHandleChanged
21073 | multi_buffer::Event::Reloaded
21074 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21075 multi_buffer::Event::DiagnosticsUpdated => {
21076 self.update_diagnostics_state(window, cx);
21077 }
21078 _ => {}
21079 };
21080 }
21081
21082 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21083 if !self.diagnostics_enabled() {
21084 return;
21085 }
21086 self.refresh_active_diagnostics(cx);
21087 self.refresh_inline_diagnostics(true, window, cx);
21088 self.scrollbar_marker_state.dirty = true;
21089 cx.notify();
21090 }
21091
21092 pub fn start_temporary_diff_override(&mut self) {
21093 self.load_diff_task.take();
21094 self.temporary_diff_override = true;
21095 }
21096
21097 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21098 self.temporary_diff_override = false;
21099 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21100 self.buffer.update(cx, |buffer, cx| {
21101 buffer.set_all_diff_hunks_collapsed(cx);
21102 });
21103
21104 if let Some(project) = self.project.clone() {
21105 self.load_diff_task = Some(
21106 update_uncommitted_diff_for_buffer(
21107 cx.entity(),
21108 &project,
21109 self.buffer.read(cx).all_buffers(),
21110 self.buffer.clone(),
21111 cx,
21112 )
21113 .shared(),
21114 );
21115 }
21116 }
21117
21118 fn on_display_map_changed(
21119 &mut self,
21120 _: Entity<DisplayMap>,
21121 _: &mut Window,
21122 cx: &mut Context<Self>,
21123 ) {
21124 cx.notify();
21125 }
21126
21127 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21128 if self.diagnostics_enabled() {
21129 let new_severity = EditorSettings::get_global(cx)
21130 .diagnostics_max_severity
21131 .unwrap_or(DiagnosticSeverity::Hint);
21132 self.set_max_diagnostics_severity(new_severity, cx);
21133 }
21134 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21135 self.update_edit_prediction_settings(cx);
21136 self.refresh_edit_prediction(true, false, window, cx);
21137 self.refresh_inline_values(cx);
21138 self.refresh_inlay_hints(
21139 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21140 self.selections.newest_anchor().head(),
21141 &self.buffer.read(cx).snapshot(cx),
21142 cx,
21143 )),
21144 cx,
21145 );
21146
21147 let old_cursor_shape = self.cursor_shape;
21148 let old_show_breadcrumbs = self.show_breadcrumbs;
21149
21150 {
21151 let editor_settings = EditorSettings::get_global(cx);
21152 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21153 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21154 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21155 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21156 }
21157
21158 if old_cursor_shape != self.cursor_shape {
21159 cx.emit(EditorEvent::CursorShapeChanged);
21160 }
21161
21162 if old_show_breadcrumbs != self.show_breadcrumbs {
21163 cx.emit(EditorEvent::BreadcrumbsChanged);
21164 }
21165
21166 let project_settings = ProjectSettings::get_global(cx);
21167 self.serialize_dirty_buffers =
21168 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21169
21170 if self.mode.is_full() {
21171 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21172 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21173 if self.show_inline_diagnostics != show_inline_diagnostics {
21174 self.show_inline_diagnostics = show_inline_diagnostics;
21175 self.refresh_inline_diagnostics(false, window, cx);
21176 }
21177
21178 if self.git_blame_inline_enabled != inline_blame_enabled {
21179 self.toggle_git_blame_inline_internal(false, window, cx);
21180 }
21181
21182 let minimap_settings = EditorSettings::get_global(cx).minimap;
21183 if self.minimap_visibility != MinimapVisibility::Disabled {
21184 if self.minimap_visibility.settings_visibility()
21185 != minimap_settings.minimap_enabled()
21186 {
21187 self.set_minimap_visibility(
21188 MinimapVisibility::for_mode(self.mode(), cx),
21189 window,
21190 cx,
21191 );
21192 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21193 minimap_entity.update(cx, |minimap_editor, cx| {
21194 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21195 })
21196 }
21197 }
21198 }
21199
21200 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21201 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21202 }) {
21203 if !inlay_splice.is_empty() {
21204 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21205 }
21206 self.refresh_colors_for_visible_range(None, window, cx);
21207 }
21208
21209 cx.notify();
21210 }
21211
21212 pub fn set_searchable(&mut self, searchable: bool) {
21213 self.searchable = searchable;
21214 }
21215
21216 pub fn searchable(&self) -> bool {
21217 self.searchable
21218 }
21219
21220 pub fn open_excerpts_in_split(
21221 &mut self,
21222 _: &OpenExcerptsSplit,
21223 window: &mut Window,
21224 cx: &mut Context<Self>,
21225 ) {
21226 self.open_excerpts_common(None, true, window, cx)
21227 }
21228
21229 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21230 self.open_excerpts_common(None, false, window, cx)
21231 }
21232
21233 fn open_excerpts_common(
21234 &mut self,
21235 jump_data: Option<JumpData>,
21236 split: bool,
21237 window: &mut Window,
21238 cx: &mut Context<Self>,
21239 ) {
21240 let Some(workspace) = self.workspace() else {
21241 cx.propagate();
21242 return;
21243 };
21244
21245 if self.buffer.read(cx).is_singleton() {
21246 cx.propagate();
21247 return;
21248 }
21249
21250 let mut new_selections_by_buffer = HashMap::default();
21251 match &jump_data {
21252 Some(JumpData::MultiBufferPoint {
21253 excerpt_id,
21254 position,
21255 anchor,
21256 line_offset_from_top,
21257 }) => {
21258 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21259 if let Some(buffer) = multi_buffer_snapshot
21260 .buffer_id_for_excerpt(*excerpt_id)
21261 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21262 {
21263 let buffer_snapshot = buffer.read(cx).snapshot();
21264 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21265 language::ToPoint::to_point(anchor, &buffer_snapshot)
21266 } else {
21267 buffer_snapshot.clip_point(*position, Bias::Left)
21268 };
21269 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21270 new_selections_by_buffer.insert(
21271 buffer,
21272 (
21273 vec![jump_to_offset..jump_to_offset],
21274 Some(*line_offset_from_top),
21275 ),
21276 );
21277 }
21278 }
21279 Some(JumpData::MultiBufferRow {
21280 row,
21281 line_offset_from_top,
21282 }) => {
21283 let point = MultiBufferPoint::new(row.0, 0);
21284 if let Some((buffer, buffer_point, _)) =
21285 self.buffer.read(cx).point_to_buffer_point(point, cx)
21286 {
21287 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21288 new_selections_by_buffer
21289 .entry(buffer)
21290 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21291 .0
21292 .push(buffer_offset..buffer_offset)
21293 }
21294 }
21295 None => {
21296 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21297 let multi_buffer = self.buffer.read(cx);
21298 for selection in selections {
21299 for (snapshot, range, _, anchor) in multi_buffer
21300 .snapshot(cx)
21301 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21302 {
21303 if let Some(anchor) = anchor {
21304 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21305 else {
21306 continue;
21307 };
21308 let offset = text::ToOffset::to_offset(
21309 &anchor.text_anchor,
21310 &buffer_handle.read(cx).snapshot(),
21311 );
21312 let range = offset..offset;
21313 new_selections_by_buffer
21314 .entry(buffer_handle)
21315 .or_insert((Vec::new(), None))
21316 .0
21317 .push(range)
21318 } else {
21319 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21320 else {
21321 continue;
21322 };
21323 new_selections_by_buffer
21324 .entry(buffer_handle)
21325 .or_insert((Vec::new(), None))
21326 .0
21327 .push(range)
21328 }
21329 }
21330 }
21331 }
21332 }
21333
21334 new_selections_by_buffer
21335 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21336
21337 if new_selections_by_buffer.is_empty() {
21338 return;
21339 }
21340
21341 // We defer the pane interaction because we ourselves are a workspace item
21342 // and activating a new item causes the pane to call a method on us reentrantly,
21343 // which panics if we're on the stack.
21344 window.defer(cx, move |window, cx| {
21345 workspace.update(cx, |workspace, cx| {
21346 let pane = if split {
21347 workspace.adjacent_pane(window, cx)
21348 } else {
21349 workspace.active_pane().clone()
21350 };
21351
21352 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21353 let editor = buffer
21354 .read(cx)
21355 .file()
21356 .is_none()
21357 .then(|| {
21358 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21359 // so `workspace.open_project_item` will never find them, always opening a new editor.
21360 // Instead, we try to activate the existing editor in the pane first.
21361 let (editor, pane_item_index) =
21362 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21363 let editor = item.downcast::<Editor>()?;
21364 let singleton_buffer =
21365 editor.read(cx).buffer().read(cx).as_singleton()?;
21366 if singleton_buffer == buffer {
21367 Some((editor, i))
21368 } else {
21369 None
21370 }
21371 })?;
21372 pane.update(cx, |pane, cx| {
21373 pane.activate_item(pane_item_index, true, true, window, cx)
21374 });
21375 Some(editor)
21376 })
21377 .flatten()
21378 .unwrap_or_else(|| {
21379 workspace.open_project_item::<Self>(
21380 pane.clone(),
21381 buffer,
21382 true,
21383 true,
21384 window,
21385 cx,
21386 )
21387 });
21388
21389 editor.update(cx, |editor, cx| {
21390 let autoscroll = match scroll_offset {
21391 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21392 None => Autoscroll::newest(),
21393 };
21394 let nav_history = editor.nav_history.take();
21395 editor.change_selections(
21396 SelectionEffects::scroll(autoscroll),
21397 window,
21398 cx,
21399 |s| {
21400 s.select_ranges(ranges);
21401 },
21402 );
21403 editor.nav_history = nav_history;
21404 });
21405 }
21406 })
21407 });
21408 }
21409
21410 // For now, don't allow opening excerpts in buffers that aren't backed by
21411 // regular project files.
21412 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21413 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21414 }
21415
21416 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21417 let snapshot = self.buffer.read(cx).read(cx);
21418 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21419 Some(
21420 ranges
21421 .iter()
21422 .map(move |range| {
21423 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21424 })
21425 .collect(),
21426 )
21427 }
21428
21429 fn selection_replacement_ranges(
21430 &self,
21431 range: Range<OffsetUtf16>,
21432 cx: &mut App,
21433 ) -> Vec<Range<OffsetUtf16>> {
21434 let selections = self
21435 .selections
21436 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21437 let newest_selection = selections
21438 .iter()
21439 .max_by_key(|selection| selection.id)
21440 .unwrap();
21441 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21442 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21443 let snapshot = self.buffer.read(cx).read(cx);
21444 selections
21445 .into_iter()
21446 .map(|mut selection| {
21447 selection.start.0 =
21448 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21449 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21450 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21451 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21452 })
21453 .collect()
21454 }
21455
21456 fn report_editor_event(
21457 &self,
21458 reported_event: ReportEditorEvent,
21459 file_extension: Option<String>,
21460 cx: &App,
21461 ) {
21462 if cfg!(any(test, feature = "test-support")) {
21463 return;
21464 }
21465
21466 let Some(project) = &self.project else { return };
21467
21468 // If None, we are in a file without an extension
21469 let file = self
21470 .buffer
21471 .read(cx)
21472 .as_singleton()
21473 .and_then(|b| b.read(cx).file());
21474 let file_extension = file_extension.or(file
21475 .as_ref()
21476 .and_then(|file| Path::new(file.file_name(cx)).extension())
21477 .and_then(|e| e.to_str())
21478 .map(|a| a.to_string()));
21479
21480 let vim_mode = vim_flavor(cx).is_some();
21481
21482 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21483 let copilot_enabled = edit_predictions_provider
21484 == language::language_settings::EditPredictionProvider::Copilot;
21485 let copilot_enabled_for_language = self
21486 .buffer
21487 .read(cx)
21488 .language_settings(cx)
21489 .show_edit_predictions;
21490
21491 let project = project.read(cx);
21492 let event_type = reported_event.event_type();
21493
21494 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21495 telemetry::event!(
21496 event_type,
21497 type = if auto_saved {"autosave"} else {"manual"},
21498 file_extension,
21499 vim_mode,
21500 copilot_enabled,
21501 copilot_enabled_for_language,
21502 edit_predictions_provider,
21503 is_via_ssh = project.is_via_remote_server(),
21504 );
21505 } else {
21506 telemetry::event!(
21507 event_type,
21508 file_extension,
21509 vim_mode,
21510 copilot_enabled,
21511 copilot_enabled_for_language,
21512 edit_predictions_provider,
21513 is_via_ssh = project.is_via_remote_server(),
21514 );
21515 };
21516 }
21517
21518 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21519 /// with each line being an array of {text, highlight} objects.
21520 fn copy_highlight_json(
21521 &mut self,
21522 _: &CopyHighlightJson,
21523 window: &mut Window,
21524 cx: &mut Context<Self>,
21525 ) {
21526 #[derive(Serialize)]
21527 struct Chunk<'a> {
21528 text: String,
21529 highlight: Option<&'a str>,
21530 }
21531
21532 let snapshot = self.buffer.read(cx).snapshot(cx);
21533 let range = self
21534 .selected_text_range(false, window, cx)
21535 .and_then(|selection| {
21536 if selection.range.is_empty() {
21537 None
21538 } else {
21539 Some(
21540 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21541 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21542 )
21543 }
21544 })
21545 .unwrap_or_else(|| 0..snapshot.len());
21546
21547 let chunks = snapshot.chunks(range, true);
21548 let mut lines = Vec::new();
21549 let mut line: VecDeque<Chunk> = VecDeque::new();
21550
21551 let Some(style) = self.style.as_ref() else {
21552 return;
21553 };
21554
21555 for chunk in chunks {
21556 let highlight = chunk
21557 .syntax_highlight_id
21558 .and_then(|id| id.name(&style.syntax));
21559 let mut chunk_lines = chunk.text.split('\n').peekable();
21560 while let Some(text) = chunk_lines.next() {
21561 let mut merged_with_last_token = false;
21562 if let Some(last_token) = line.back_mut()
21563 && last_token.highlight == highlight
21564 {
21565 last_token.text.push_str(text);
21566 merged_with_last_token = true;
21567 }
21568
21569 if !merged_with_last_token {
21570 line.push_back(Chunk {
21571 text: text.into(),
21572 highlight,
21573 });
21574 }
21575
21576 if chunk_lines.peek().is_some() {
21577 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21578 line.pop_front();
21579 }
21580 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21581 line.pop_back();
21582 }
21583
21584 lines.push(mem::take(&mut line));
21585 }
21586 }
21587 }
21588
21589 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21590 return;
21591 };
21592 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21593 }
21594
21595 pub fn open_context_menu(
21596 &mut self,
21597 _: &OpenContextMenu,
21598 window: &mut Window,
21599 cx: &mut Context<Self>,
21600 ) {
21601 self.request_autoscroll(Autoscroll::newest(), cx);
21602 let position = self
21603 .selections
21604 .newest_display(&self.display_snapshot(cx))
21605 .start;
21606 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21607 }
21608
21609 pub fn replay_insert_event(
21610 &mut self,
21611 text: &str,
21612 relative_utf16_range: Option<Range<isize>>,
21613 window: &mut Window,
21614 cx: &mut Context<Self>,
21615 ) {
21616 if !self.input_enabled {
21617 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21618 return;
21619 }
21620 if let Some(relative_utf16_range) = relative_utf16_range {
21621 let selections = self
21622 .selections
21623 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21624 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21625 let new_ranges = selections.into_iter().map(|range| {
21626 let start = OffsetUtf16(
21627 range
21628 .head()
21629 .0
21630 .saturating_add_signed(relative_utf16_range.start),
21631 );
21632 let end = OffsetUtf16(
21633 range
21634 .head()
21635 .0
21636 .saturating_add_signed(relative_utf16_range.end),
21637 );
21638 start..end
21639 });
21640 s.select_ranges(new_ranges);
21641 });
21642 }
21643
21644 self.handle_input(text, window, cx);
21645 }
21646
21647 pub fn is_focused(&self, window: &Window) -> bool {
21648 self.focus_handle.is_focused(window)
21649 }
21650
21651 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21652 cx.emit(EditorEvent::Focused);
21653
21654 if let Some(descendant) = self
21655 .last_focused_descendant
21656 .take()
21657 .and_then(|descendant| descendant.upgrade())
21658 {
21659 window.focus(&descendant);
21660 } else {
21661 if let Some(blame) = self.blame.as_ref() {
21662 blame.update(cx, GitBlame::focus)
21663 }
21664
21665 self.blink_manager.update(cx, BlinkManager::enable);
21666 self.show_cursor_names(window, cx);
21667 self.buffer.update(cx, |buffer, cx| {
21668 buffer.finalize_last_transaction(cx);
21669 if self.leader_id.is_none() {
21670 buffer.set_active_selections(
21671 &self.selections.disjoint_anchors_arc(),
21672 self.selections.line_mode(),
21673 self.cursor_shape,
21674 cx,
21675 );
21676 }
21677 });
21678 }
21679 }
21680
21681 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21682 cx.emit(EditorEvent::FocusedIn)
21683 }
21684
21685 fn handle_focus_out(
21686 &mut self,
21687 event: FocusOutEvent,
21688 _window: &mut Window,
21689 cx: &mut Context<Self>,
21690 ) {
21691 if event.blurred != self.focus_handle {
21692 self.last_focused_descendant = Some(event.blurred);
21693 }
21694 self.selection_drag_state = SelectionDragState::None;
21695 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21696 }
21697
21698 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21699 self.blink_manager.update(cx, BlinkManager::disable);
21700 self.buffer
21701 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21702
21703 if let Some(blame) = self.blame.as_ref() {
21704 blame.update(cx, GitBlame::blur)
21705 }
21706 if !self.hover_state.focused(window, cx) {
21707 hide_hover(self, cx);
21708 }
21709 if !self
21710 .context_menu
21711 .borrow()
21712 .as_ref()
21713 .is_some_and(|context_menu| context_menu.focused(window, cx))
21714 {
21715 self.hide_context_menu(window, cx);
21716 }
21717 self.take_active_edit_prediction(cx);
21718 cx.emit(EditorEvent::Blurred);
21719 cx.notify();
21720 }
21721
21722 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21723 let mut pending: String = window
21724 .pending_input_keystrokes()
21725 .into_iter()
21726 .flatten()
21727 .filter_map(|keystroke| {
21728 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21729 keystroke.key_char.clone()
21730 } else {
21731 None
21732 }
21733 })
21734 .collect();
21735
21736 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21737 pending = "".to_string();
21738 }
21739
21740 let existing_pending = self
21741 .text_highlights::<PendingInput>(cx)
21742 .map(|(_, ranges)| ranges.to_vec());
21743 if existing_pending.is_none() && pending.is_empty() {
21744 return;
21745 }
21746 let transaction =
21747 self.transact(window, cx, |this, window, cx| {
21748 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21749 let edits = selections
21750 .iter()
21751 .map(|selection| (selection.end..selection.end, pending.clone()));
21752 this.edit(edits, cx);
21753 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21754 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21755 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21756 }));
21757 });
21758 if let Some(existing_ranges) = existing_pending {
21759 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21760 this.edit(edits, cx);
21761 }
21762 });
21763
21764 let snapshot = self.snapshot(window, cx);
21765 let ranges = self
21766 .selections
21767 .all::<usize>(&snapshot.display_snapshot)
21768 .into_iter()
21769 .map(|selection| {
21770 snapshot.buffer_snapshot().anchor_after(selection.end)
21771 ..snapshot
21772 .buffer_snapshot()
21773 .anchor_before(selection.end + pending.len())
21774 })
21775 .collect();
21776
21777 if pending.is_empty() {
21778 self.clear_highlights::<PendingInput>(cx);
21779 } else {
21780 self.highlight_text::<PendingInput>(
21781 ranges,
21782 HighlightStyle {
21783 underline: Some(UnderlineStyle {
21784 thickness: px(1.),
21785 color: None,
21786 wavy: false,
21787 }),
21788 ..Default::default()
21789 },
21790 cx,
21791 );
21792 }
21793
21794 self.ime_transaction = self.ime_transaction.or(transaction);
21795 if let Some(transaction) = self.ime_transaction {
21796 self.buffer.update(cx, |buffer, cx| {
21797 buffer.group_until_transaction(transaction, cx);
21798 });
21799 }
21800
21801 if self.text_highlights::<PendingInput>(cx).is_none() {
21802 self.ime_transaction.take();
21803 }
21804 }
21805
21806 pub fn register_action_renderer(
21807 &mut self,
21808 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21809 ) -> Subscription {
21810 let id = self.next_editor_action_id.post_inc();
21811 self.editor_actions
21812 .borrow_mut()
21813 .insert(id, Box::new(listener));
21814
21815 let editor_actions = self.editor_actions.clone();
21816 Subscription::new(move || {
21817 editor_actions.borrow_mut().remove(&id);
21818 })
21819 }
21820
21821 pub fn register_action<A: Action>(
21822 &mut self,
21823 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21824 ) -> Subscription {
21825 let id = self.next_editor_action_id.post_inc();
21826 let listener = Arc::new(listener);
21827 self.editor_actions.borrow_mut().insert(
21828 id,
21829 Box::new(move |_, window, _| {
21830 let listener = listener.clone();
21831 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21832 let action = action.downcast_ref().unwrap();
21833 if phase == DispatchPhase::Bubble {
21834 listener(action, window, cx)
21835 }
21836 })
21837 }),
21838 );
21839
21840 let editor_actions = self.editor_actions.clone();
21841 Subscription::new(move || {
21842 editor_actions.borrow_mut().remove(&id);
21843 })
21844 }
21845
21846 pub fn file_header_size(&self) -> u32 {
21847 FILE_HEADER_HEIGHT
21848 }
21849
21850 pub fn restore(
21851 &mut self,
21852 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21853 window: &mut Window,
21854 cx: &mut Context<Self>,
21855 ) {
21856 let workspace = self.workspace();
21857 let project = self.project();
21858 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21859 let mut tasks = Vec::new();
21860 for (buffer_id, changes) in revert_changes {
21861 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21862 buffer.update(cx, |buffer, cx| {
21863 buffer.edit(
21864 changes
21865 .into_iter()
21866 .map(|(range, text)| (range, text.to_string())),
21867 None,
21868 cx,
21869 );
21870 });
21871
21872 if let Some(project) =
21873 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21874 {
21875 project.update(cx, |project, cx| {
21876 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21877 })
21878 }
21879 }
21880 }
21881 tasks
21882 });
21883 cx.spawn_in(window, async move |_, cx| {
21884 for (buffer, task) in save_tasks {
21885 let result = task.await;
21886 if result.is_err() {
21887 let Some(path) = buffer
21888 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21889 .ok()
21890 else {
21891 continue;
21892 };
21893 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21894 let Some(task) = cx
21895 .update_window_entity(workspace, |workspace, window, cx| {
21896 workspace
21897 .open_path_preview(path, None, false, false, false, window, cx)
21898 })
21899 .ok()
21900 else {
21901 continue;
21902 };
21903 task.await.log_err();
21904 }
21905 }
21906 }
21907 })
21908 .detach();
21909 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21910 selections.refresh()
21911 });
21912 }
21913
21914 pub fn to_pixel_point(
21915 &self,
21916 source: multi_buffer::Anchor,
21917 editor_snapshot: &EditorSnapshot,
21918 window: &mut Window,
21919 ) -> Option<gpui::Point<Pixels>> {
21920 let source_point = source.to_display_point(editor_snapshot);
21921 self.display_to_pixel_point(source_point, editor_snapshot, window)
21922 }
21923
21924 pub fn display_to_pixel_point(
21925 &self,
21926 source: DisplayPoint,
21927 editor_snapshot: &EditorSnapshot,
21928 window: &mut Window,
21929 ) -> Option<gpui::Point<Pixels>> {
21930 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21931 let text_layout_details = self.text_layout_details(window);
21932 let scroll_top = text_layout_details
21933 .scroll_anchor
21934 .scroll_position(editor_snapshot)
21935 .y;
21936
21937 if source.row().as_f64() < scroll_top.floor() {
21938 return None;
21939 }
21940 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21941 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21942 Some(gpui::Point::new(source_x, source_y))
21943 }
21944
21945 pub fn has_visible_completions_menu(&self) -> bool {
21946 !self.edit_prediction_preview_is_active()
21947 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21948 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21949 })
21950 }
21951
21952 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21953 if self.mode.is_minimap() {
21954 return;
21955 }
21956 self.addons
21957 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21958 }
21959
21960 pub fn unregister_addon<T: Addon>(&mut self) {
21961 self.addons.remove(&std::any::TypeId::of::<T>());
21962 }
21963
21964 pub fn addon<T: Addon>(&self) -> Option<&T> {
21965 let type_id = std::any::TypeId::of::<T>();
21966 self.addons
21967 .get(&type_id)
21968 .and_then(|item| item.to_any().downcast_ref::<T>())
21969 }
21970
21971 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21972 let type_id = std::any::TypeId::of::<T>();
21973 self.addons
21974 .get_mut(&type_id)
21975 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21976 }
21977
21978 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21979 let text_layout_details = self.text_layout_details(window);
21980 let style = &text_layout_details.editor_style;
21981 let font_id = window.text_system().resolve_font(&style.text.font());
21982 let font_size = style.text.font_size.to_pixels(window.rem_size());
21983 let line_height = style.text.line_height_in_pixels(window.rem_size());
21984 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21985 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21986
21987 CharacterDimensions {
21988 em_width,
21989 em_advance,
21990 line_height,
21991 }
21992 }
21993
21994 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21995 self.load_diff_task.clone()
21996 }
21997
21998 fn read_metadata_from_db(
21999 &mut self,
22000 item_id: u64,
22001 workspace_id: WorkspaceId,
22002 window: &mut Window,
22003 cx: &mut Context<Editor>,
22004 ) {
22005 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22006 && !self.mode.is_minimap()
22007 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22008 {
22009 let buffer_snapshot = OnceCell::new();
22010
22011 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22012 && !folds.is_empty()
22013 {
22014 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22015 self.fold_ranges(
22016 folds
22017 .into_iter()
22018 .map(|(start, end)| {
22019 snapshot.clip_offset(start, Bias::Left)
22020 ..snapshot.clip_offset(end, Bias::Right)
22021 })
22022 .collect(),
22023 false,
22024 window,
22025 cx,
22026 );
22027 }
22028
22029 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22030 && !selections.is_empty()
22031 {
22032 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22033 // skip adding the initial selection to selection history
22034 self.selection_history.mode = SelectionHistoryMode::Skipping;
22035 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22036 s.select_ranges(selections.into_iter().map(|(start, end)| {
22037 snapshot.clip_offset(start, Bias::Left)
22038 ..snapshot.clip_offset(end, Bias::Right)
22039 }));
22040 });
22041 self.selection_history.mode = SelectionHistoryMode::Normal;
22042 };
22043 }
22044
22045 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22046 }
22047
22048 fn update_lsp_data(
22049 &mut self,
22050 for_buffer: Option<BufferId>,
22051 window: &mut Window,
22052 cx: &mut Context<'_, Self>,
22053 ) {
22054 self.pull_diagnostics(for_buffer, window, cx);
22055 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22056 }
22057
22058 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22059 if self.ignore_lsp_data() {
22060 return;
22061 }
22062 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22063 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22064 }
22065 }
22066
22067 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22068 if !self.registered_buffers.contains_key(&buffer_id)
22069 && let Some(project) = self.project.as_ref()
22070 {
22071 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22072 project.update(cx, |project, cx| {
22073 self.registered_buffers.insert(
22074 buffer_id,
22075 project.register_buffer_with_language_servers(&buffer, cx),
22076 );
22077 });
22078 } else {
22079 self.registered_buffers.remove(&buffer_id);
22080 }
22081 }
22082 }
22083
22084 fn ignore_lsp_data(&self) -> bool {
22085 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22086 // skip any LSP updates for it.
22087 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22088 }
22089}
22090
22091fn edit_for_markdown_paste<'a>(
22092 buffer: &MultiBufferSnapshot,
22093 range: Range<usize>,
22094 to_insert: &'a str,
22095 url: Option<url::Url>,
22096) -> (Range<usize>, Cow<'a, str>) {
22097 if url.is_none() {
22098 return (range, Cow::Borrowed(to_insert));
22099 };
22100
22101 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22102
22103 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22104 Cow::Borrowed(to_insert)
22105 } else {
22106 Cow::Owned(format!("[{old_text}]({to_insert})"))
22107 };
22108 (range, new_text)
22109}
22110
22111#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
22112pub enum VimFlavor {
22113 Vim,
22114 Helix,
22115}
22116
22117pub fn vim_flavor(cx: &App) -> Option<VimFlavor> {
22118 if vim_mode_setting::HelixModeSetting::try_get(cx)
22119 .map(|helix_mode| helix_mode.0)
22120 .unwrap_or(false)
22121 {
22122 Some(VimFlavor::Helix)
22123 } else if vim_mode_setting::VimModeSetting::try_get(cx)
22124 .map(|vim_mode| vim_mode.0)
22125 .unwrap_or(false)
22126 {
22127 Some(VimFlavor::Vim)
22128 } else {
22129 None // neither vim nor helix mode
22130 }
22131}
22132
22133fn process_completion_for_edit(
22134 completion: &Completion,
22135 intent: CompletionIntent,
22136 buffer: &Entity<Buffer>,
22137 cursor_position: &text::Anchor,
22138 cx: &mut Context<Editor>,
22139) -> CompletionEdit {
22140 let buffer = buffer.read(cx);
22141 let buffer_snapshot = buffer.snapshot();
22142 let (snippet, new_text) = if completion.is_snippet() {
22143 let mut snippet_source = completion.new_text.clone();
22144 // Workaround for typescript language server issues so that methods don't expand within
22145 // strings and functions with type expressions. The previous point is used because the query
22146 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22147 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22148 let previous_point = if previous_point.column > 0 {
22149 cursor_position.to_previous_offset(&buffer_snapshot)
22150 } else {
22151 cursor_position.to_offset(&buffer_snapshot)
22152 };
22153 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22154 && scope.prefers_label_for_snippet_in_completion()
22155 && let Some(label) = completion.label()
22156 && matches!(
22157 completion.kind(),
22158 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22159 )
22160 {
22161 snippet_source = label;
22162 }
22163 match Snippet::parse(&snippet_source).log_err() {
22164 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22165 None => (None, completion.new_text.clone()),
22166 }
22167 } else {
22168 (None, completion.new_text.clone())
22169 };
22170
22171 let mut range_to_replace = {
22172 let replace_range = &completion.replace_range;
22173 if let CompletionSource::Lsp {
22174 insert_range: Some(insert_range),
22175 ..
22176 } = &completion.source
22177 {
22178 debug_assert_eq!(
22179 insert_range.start, replace_range.start,
22180 "insert_range and replace_range should start at the same position"
22181 );
22182 debug_assert!(
22183 insert_range
22184 .start
22185 .cmp(cursor_position, &buffer_snapshot)
22186 .is_le(),
22187 "insert_range should start before or at cursor position"
22188 );
22189 debug_assert!(
22190 replace_range
22191 .start
22192 .cmp(cursor_position, &buffer_snapshot)
22193 .is_le(),
22194 "replace_range should start before or at cursor position"
22195 );
22196
22197 let should_replace = match intent {
22198 CompletionIntent::CompleteWithInsert => false,
22199 CompletionIntent::CompleteWithReplace => true,
22200 CompletionIntent::Complete | CompletionIntent::Compose => {
22201 let insert_mode =
22202 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22203 .completions
22204 .lsp_insert_mode;
22205 match insert_mode {
22206 LspInsertMode::Insert => false,
22207 LspInsertMode::Replace => true,
22208 LspInsertMode::ReplaceSubsequence => {
22209 let mut text_to_replace = buffer.chars_for_range(
22210 buffer.anchor_before(replace_range.start)
22211 ..buffer.anchor_after(replace_range.end),
22212 );
22213 let mut current_needle = text_to_replace.next();
22214 for haystack_ch in completion.label.text.chars() {
22215 if let Some(needle_ch) = current_needle
22216 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22217 {
22218 current_needle = text_to_replace.next();
22219 }
22220 }
22221 current_needle.is_none()
22222 }
22223 LspInsertMode::ReplaceSuffix => {
22224 if replace_range
22225 .end
22226 .cmp(cursor_position, &buffer_snapshot)
22227 .is_gt()
22228 {
22229 let range_after_cursor = *cursor_position..replace_range.end;
22230 let text_after_cursor = buffer
22231 .text_for_range(
22232 buffer.anchor_before(range_after_cursor.start)
22233 ..buffer.anchor_after(range_after_cursor.end),
22234 )
22235 .collect::<String>()
22236 .to_ascii_lowercase();
22237 completion
22238 .label
22239 .text
22240 .to_ascii_lowercase()
22241 .ends_with(&text_after_cursor)
22242 } else {
22243 true
22244 }
22245 }
22246 }
22247 }
22248 };
22249
22250 if should_replace {
22251 replace_range.clone()
22252 } else {
22253 insert_range.clone()
22254 }
22255 } else {
22256 replace_range.clone()
22257 }
22258 };
22259
22260 if range_to_replace
22261 .end
22262 .cmp(cursor_position, &buffer_snapshot)
22263 .is_lt()
22264 {
22265 range_to_replace.end = *cursor_position;
22266 }
22267
22268 CompletionEdit {
22269 new_text,
22270 replace_range: range_to_replace.to_offset(buffer),
22271 snippet,
22272 }
22273}
22274
22275struct CompletionEdit {
22276 new_text: String,
22277 replace_range: Range<usize>,
22278 snippet: Option<Snippet>,
22279}
22280
22281fn insert_extra_newline_brackets(
22282 buffer: &MultiBufferSnapshot,
22283 range: Range<usize>,
22284 language: &language::LanguageScope,
22285) -> bool {
22286 let leading_whitespace_len = buffer
22287 .reversed_chars_at(range.start)
22288 .take_while(|c| c.is_whitespace() && *c != '\n')
22289 .map(|c| c.len_utf8())
22290 .sum::<usize>();
22291 let trailing_whitespace_len = buffer
22292 .chars_at(range.end)
22293 .take_while(|c| c.is_whitespace() && *c != '\n')
22294 .map(|c| c.len_utf8())
22295 .sum::<usize>();
22296 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22297
22298 language.brackets().any(|(pair, enabled)| {
22299 let pair_start = pair.start.trim_end();
22300 let pair_end = pair.end.trim_start();
22301
22302 enabled
22303 && pair.newline
22304 && buffer.contains_str_at(range.end, pair_end)
22305 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22306 })
22307}
22308
22309fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22310 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22311 [(buffer, range, _)] => (*buffer, range.clone()),
22312 _ => return false,
22313 };
22314 let pair = {
22315 let mut result: Option<BracketMatch> = None;
22316
22317 for pair in buffer
22318 .all_bracket_ranges(range.clone())
22319 .filter(move |pair| {
22320 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22321 })
22322 {
22323 let len = pair.close_range.end - pair.open_range.start;
22324
22325 if let Some(existing) = &result {
22326 let existing_len = existing.close_range.end - existing.open_range.start;
22327 if len > existing_len {
22328 continue;
22329 }
22330 }
22331
22332 result = Some(pair);
22333 }
22334
22335 result
22336 };
22337 let Some(pair) = pair else {
22338 return false;
22339 };
22340 pair.newline_only
22341 && buffer
22342 .chars_for_range(pair.open_range.end..range.start)
22343 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22344 .all(|c| c.is_whitespace() && c != '\n')
22345}
22346
22347fn update_uncommitted_diff_for_buffer(
22348 editor: Entity<Editor>,
22349 project: &Entity<Project>,
22350 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22351 buffer: Entity<MultiBuffer>,
22352 cx: &mut App,
22353) -> Task<()> {
22354 let mut tasks = Vec::new();
22355 project.update(cx, |project, cx| {
22356 for buffer in buffers {
22357 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22358 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22359 }
22360 }
22361 });
22362 cx.spawn(async move |cx| {
22363 let diffs = future::join_all(tasks).await;
22364 if editor
22365 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22366 .unwrap_or(false)
22367 {
22368 return;
22369 }
22370
22371 buffer
22372 .update(cx, |buffer, cx| {
22373 for diff in diffs.into_iter().flatten() {
22374 buffer.add_diff(diff, cx);
22375 }
22376 })
22377 .ok();
22378 })
22379}
22380
22381fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22382 let tab_size = tab_size.get() as usize;
22383 let mut width = offset;
22384
22385 for ch in text.chars() {
22386 width += if ch == '\t' {
22387 tab_size - (width % tab_size)
22388 } else {
22389 1
22390 };
22391 }
22392
22393 width - offset
22394}
22395
22396#[cfg(test)]
22397mod tests {
22398 use super::*;
22399
22400 #[test]
22401 fn test_string_size_with_expanded_tabs() {
22402 let nz = |val| NonZeroU32::new(val).unwrap();
22403 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22404 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22405 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22406 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22407 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22408 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22409 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22410 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22411 }
22412}
22413
22414/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22415struct WordBreakingTokenizer<'a> {
22416 input: &'a str,
22417}
22418
22419impl<'a> WordBreakingTokenizer<'a> {
22420 fn new(input: &'a str) -> Self {
22421 Self { input }
22422 }
22423}
22424
22425fn is_char_ideographic(ch: char) -> bool {
22426 use unicode_script::Script::*;
22427 use unicode_script::UnicodeScript;
22428 matches!(ch.script(), Han | Tangut | Yi)
22429}
22430
22431fn is_grapheme_ideographic(text: &str) -> bool {
22432 text.chars().any(is_char_ideographic)
22433}
22434
22435fn is_grapheme_whitespace(text: &str) -> bool {
22436 text.chars().any(|x| x.is_whitespace())
22437}
22438
22439fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22440 text.chars()
22441 .next()
22442 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22443}
22444
22445#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22446enum WordBreakToken<'a> {
22447 Word { token: &'a str, grapheme_len: usize },
22448 InlineWhitespace { token: &'a str, grapheme_len: usize },
22449 Newline,
22450}
22451
22452impl<'a> Iterator for WordBreakingTokenizer<'a> {
22453 /// Yields a span, the count of graphemes in the token, and whether it was
22454 /// whitespace. Note that it also breaks at word boundaries.
22455 type Item = WordBreakToken<'a>;
22456
22457 fn next(&mut self) -> Option<Self::Item> {
22458 use unicode_segmentation::UnicodeSegmentation;
22459 if self.input.is_empty() {
22460 return None;
22461 }
22462
22463 let mut iter = self.input.graphemes(true).peekable();
22464 let mut offset = 0;
22465 let mut grapheme_len = 0;
22466 if let Some(first_grapheme) = iter.next() {
22467 let is_newline = first_grapheme == "\n";
22468 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22469 offset += first_grapheme.len();
22470 grapheme_len += 1;
22471 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22472 if let Some(grapheme) = iter.peek().copied()
22473 && should_stay_with_preceding_ideograph(grapheme)
22474 {
22475 offset += grapheme.len();
22476 grapheme_len += 1;
22477 }
22478 } else {
22479 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22480 let mut next_word_bound = words.peek().copied();
22481 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22482 next_word_bound = words.next();
22483 }
22484 while let Some(grapheme) = iter.peek().copied() {
22485 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22486 break;
22487 };
22488 if is_grapheme_whitespace(grapheme) != is_whitespace
22489 || (grapheme == "\n") != is_newline
22490 {
22491 break;
22492 };
22493 offset += grapheme.len();
22494 grapheme_len += 1;
22495 iter.next();
22496 }
22497 }
22498 let token = &self.input[..offset];
22499 self.input = &self.input[offset..];
22500 if token == "\n" {
22501 Some(WordBreakToken::Newline)
22502 } else if is_whitespace {
22503 Some(WordBreakToken::InlineWhitespace {
22504 token,
22505 grapheme_len,
22506 })
22507 } else {
22508 Some(WordBreakToken::Word {
22509 token,
22510 grapheme_len,
22511 })
22512 }
22513 } else {
22514 None
22515 }
22516 }
22517}
22518
22519#[test]
22520fn test_word_breaking_tokenizer() {
22521 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22522 ("", &[]),
22523 (" ", &[whitespace(" ", 2)]),
22524 ("Ʒ", &[word("Ʒ", 1)]),
22525 ("Ǽ", &[word("Ǽ", 1)]),
22526 ("⋑", &[word("⋑", 1)]),
22527 ("⋑⋑", &[word("⋑⋑", 2)]),
22528 (
22529 "原理,进而",
22530 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22531 ),
22532 (
22533 "hello world",
22534 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22535 ),
22536 (
22537 "hello, world",
22538 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22539 ),
22540 (
22541 " hello world",
22542 &[
22543 whitespace(" ", 2),
22544 word("hello", 5),
22545 whitespace(" ", 1),
22546 word("world", 5),
22547 ],
22548 ),
22549 (
22550 "这是什么 \n 钢笔",
22551 &[
22552 word("这", 1),
22553 word("是", 1),
22554 word("什", 1),
22555 word("么", 1),
22556 whitespace(" ", 1),
22557 newline(),
22558 whitespace(" ", 1),
22559 word("钢", 1),
22560 word("笔", 1),
22561 ],
22562 ),
22563 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22564 ];
22565
22566 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22567 WordBreakToken::Word {
22568 token,
22569 grapheme_len,
22570 }
22571 }
22572
22573 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22574 WordBreakToken::InlineWhitespace {
22575 token,
22576 grapheme_len,
22577 }
22578 }
22579
22580 fn newline() -> WordBreakToken<'static> {
22581 WordBreakToken::Newline
22582 }
22583
22584 for (input, result) in tests {
22585 assert_eq!(
22586 WordBreakingTokenizer::new(input)
22587 .collect::<Vec<_>>()
22588 .as_slice(),
22589 *result,
22590 );
22591 }
22592}
22593
22594fn wrap_with_prefix(
22595 first_line_prefix: String,
22596 subsequent_lines_prefix: String,
22597 unwrapped_text: String,
22598 wrap_column: usize,
22599 tab_size: NonZeroU32,
22600 preserve_existing_whitespace: bool,
22601) -> String {
22602 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22603 let subsequent_lines_prefix_len =
22604 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22605 let mut wrapped_text = String::new();
22606 let mut current_line = first_line_prefix;
22607 let mut is_first_line = true;
22608
22609 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22610 let mut current_line_len = first_line_prefix_len;
22611 let mut in_whitespace = false;
22612 for token in tokenizer {
22613 let have_preceding_whitespace = in_whitespace;
22614 match token {
22615 WordBreakToken::Word {
22616 token,
22617 grapheme_len,
22618 } => {
22619 in_whitespace = false;
22620 let current_prefix_len = if is_first_line {
22621 first_line_prefix_len
22622 } else {
22623 subsequent_lines_prefix_len
22624 };
22625 if current_line_len + grapheme_len > wrap_column
22626 && current_line_len != current_prefix_len
22627 {
22628 wrapped_text.push_str(current_line.trim_end());
22629 wrapped_text.push('\n');
22630 is_first_line = false;
22631 current_line = subsequent_lines_prefix.clone();
22632 current_line_len = subsequent_lines_prefix_len;
22633 }
22634 current_line.push_str(token);
22635 current_line_len += grapheme_len;
22636 }
22637 WordBreakToken::InlineWhitespace {
22638 mut token,
22639 mut grapheme_len,
22640 } => {
22641 in_whitespace = true;
22642 if have_preceding_whitespace && !preserve_existing_whitespace {
22643 continue;
22644 }
22645 if !preserve_existing_whitespace {
22646 // Keep a single whitespace grapheme as-is
22647 if let Some(first) =
22648 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22649 {
22650 token = first;
22651 } else {
22652 token = " ";
22653 }
22654 grapheme_len = 1;
22655 }
22656 let current_prefix_len = if is_first_line {
22657 first_line_prefix_len
22658 } else {
22659 subsequent_lines_prefix_len
22660 };
22661 if current_line_len + grapheme_len > wrap_column {
22662 wrapped_text.push_str(current_line.trim_end());
22663 wrapped_text.push('\n');
22664 is_first_line = false;
22665 current_line = subsequent_lines_prefix.clone();
22666 current_line_len = subsequent_lines_prefix_len;
22667 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22668 current_line.push_str(token);
22669 current_line_len += grapheme_len;
22670 }
22671 }
22672 WordBreakToken::Newline => {
22673 in_whitespace = true;
22674 let current_prefix_len = if is_first_line {
22675 first_line_prefix_len
22676 } else {
22677 subsequent_lines_prefix_len
22678 };
22679 if preserve_existing_whitespace {
22680 wrapped_text.push_str(current_line.trim_end());
22681 wrapped_text.push('\n');
22682 is_first_line = false;
22683 current_line = subsequent_lines_prefix.clone();
22684 current_line_len = subsequent_lines_prefix_len;
22685 } else if have_preceding_whitespace {
22686 continue;
22687 } else if current_line_len + 1 > wrap_column
22688 && current_line_len != current_prefix_len
22689 {
22690 wrapped_text.push_str(current_line.trim_end());
22691 wrapped_text.push('\n');
22692 is_first_line = false;
22693 current_line = subsequent_lines_prefix.clone();
22694 current_line_len = subsequent_lines_prefix_len;
22695 } else if current_line_len != current_prefix_len {
22696 current_line.push(' ');
22697 current_line_len += 1;
22698 }
22699 }
22700 }
22701 }
22702
22703 if !current_line.is_empty() {
22704 wrapped_text.push_str(¤t_line);
22705 }
22706 wrapped_text
22707}
22708
22709#[test]
22710fn test_wrap_with_prefix() {
22711 assert_eq!(
22712 wrap_with_prefix(
22713 "# ".to_string(),
22714 "# ".to_string(),
22715 "abcdefg".to_string(),
22716 4,
22717 NonZeroU32::new(4).unwrap(),
22718 false,
22719 ),
22720 "# abcdefg"
22721 );
22722 assert_eq!(
22723 wrap_with_prefix(
22724 "".to_string(),
22725 "".to_string(),
22726 "\thello world".to_string(),
22727 8,
22728 NonZeroU32::new(4).unwrap(),
22729 false,
22730 ),
22731 "hello\nworld"
22732 );
22733 assert_eq!(
22734 wrap_with_prefix(
22735 "// ".to_string(),
22736 "// ".to_string(),
22737 "xx \nyy zz aa bb cc".to_string(),
22738 12,
22739 NonZeroU32::new(4).unwrap(),
22740 false,
22741 ),
22742 "// xx yy zz\n// aa bb cc"
22743 );
22744 assert_eq!(
22745 wrap_with_prefix(
22746 String::new(),
22747 String::new(),
22748 "这是什么 \n 钢笔".to_string(),
22749 3,
22750 NonZeroU32::new(4).unwrap(),
22751 false,
22752 ),
22753 "这是什\n么 钢\n笔"
22754 );
22755 assert_eq!(
22756 wrap_with_prefix(
22757 String::new(),
22758 String::new(),
22759 format!("foo{}bar", '\u{2009}'), // thin space
22760 80,
22761 NonZeroU32::new(4).unwrap(),
22762 false,
22763 ),
22764 format!("foo{}bar", '\u{2009}')
22765 );
22766}
22767
22768pub trait CollaborationHub {
22769 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22770 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22771 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22772}
22773
22774impl CollaborationHub for Entity<Project> {
22775 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22776 self.read(cx).collaborators()
22777 }
22778
22779 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22780 self.read(cx).user_store().read(cx).participant_indices()
22781 }
22782
22783 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22784 let this = self.read(cx);
22785 let user_ids = this.collaborators().values().map(|c| c.user_id);
22786 this.user_store().read(cx).participant_names(user_ids, cx)
22787 }
22788}
22789
22790pub trait SemanticsProvider {
22791 fn hover(
22792 &self,
22793 buffer: &Entity<Buffer>,
22794 position: text::Anchor,
22795 cx: &mut App,
22796 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22797
22798 fn inline_values(
22799 &self,
22800 buffer_handle: Entity<Buffer>,
22801 range: Range<text::Anchor>,
22802 cx: &mut App,
22803 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22804
22805 fn applicable_inlay_chunks(
22806 &self,
22807 buffer: &Entity<Buffer>,
22808 ranges: &[Range<text::Anchor>],
22809 cx: &mut App,
22810 ) -> Vec<Range<BufferRow>>;
22811
22812 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
22813
22814 fn inlay_hints(
22815 &self,
22816 invalidate: InvalidationStrategy,
22817 buffer: Entity<Buffer>,
22818 ranges: Vec<Range<text::Anchor>>,
22819 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
22820 cx: &mut App,
22821 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
22822
22823 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22824
22825 fn document_highlights(
22826 &self,
22827 buffer: &Entity<Buffer>,
22828 position: text::Anchor,
22829 cx: &mut App,
22830 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22831
22832 fn definitions(
22833 &self,
22834 buffer: &Entity<Buffer>,
22835 position: text::Anchor,
22836 kind: GotoDefinitionKind,
22837 cx: &mut App,
22838 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22839
22840 fn range_for_rename(
22841 &self,
22842 buffer: &Entity<Buffer>,
22843 position: text::Anchor,
22844 cx: &mut App,
22845 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22846
22847 fn perform_rename(
22848 &self,
22849 buffer: &Entity<Buffer>,
22850 position: text::Anchor,
22851 new_name: String,
22852 cx: &mut App,
22853 ) -> Option<Task<Result<ProjectTransaction>>>;
22854}
22855
22856pub trait CompletionProvider {
22857 fn completions(
22858 &self,
22859 excerpt_id: ExcerptId,
22860 buffer: &Entity<Buffer>,
22861 buffer_position: text::Anchor,
22862 trigger: CompletionContext,
22863 window: &mut Window,
22864 cx: &mut Context<Editor>,
22865 ) -> Task<Result<Vec<CompletionResponse>>>;
22866
22867 fn resolve_completions(
22868 &self,
22869 _buffer: Entity<Buffer>,
22870 _completion_indices: Vec<usize>,
22871 _completions: Rc<RefCell<Box<[Completion]>>>,
22872 _cx: &mut Context<Editor>,
22873 ) -> Task<Result<bool>> {
22874 Task::ready(Ok(false))
22875 }
22876
22877 fn apply_additional_edits_for_completion(
22878 &self,
22879 _buffer: Entity<Buffer>,
22880 _completions: Rc<RefCell<Box<[Completion]>>>,
22881 _completion_index: usize,
22882 _push_to_history: bool,
22883 _cx: &mut Context<Editor>,
22884 ) -> Task<Result<Option<language::Transaction>>> {
22885 Task::ready(Ok(None))
22886 }
22887
22888 fn is_completion_trigger(
22889 &self,
22890 buffer: &Entity<Buffer>,
22891 position: language::Anchor,
22892 text: &str,
22893 trigger_in_words: bool,
22894 menu_is_open: bool,
22895 cx: &mut Context<Editor>,
22896 ) -> bool;
22897
22898 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22899
22900 fn sort_completions(&self) -> bool {
22901 true
22902 }
22903
22904 fn filter_completions(&self) -> bool {
22905 true
22906 }
22907}
22908
22909pub trait CodeActionProvider {
22910 fn id(&self) -> Arc<str>;
22911
22912 fn code_actions(
22913 &self,
22914 buffer: &Entity<Buffer>,
22915 range: Range<text::Anchor>,
22916 window: &mut Window,
22917 cx: &mut App,
22918 ) -> Task<Result<Vec<CodeAction>>>;
22919
22920 fn apply_code_action(
22921 &self,
22922 buffer_handle: Entity<Buffer>,
22923 action: CodeAction,
22924 excerpt_id: ExcerptId,
22925 push_to_history: bool,
22926 window: &mut Window,
22927 cx: &mut App,
22928 ) -> Task<Result<ProjectTransaction>>;
22929}
22930
22931impl CodeActionProvider for Entity<Project> {
22932 fn id(&self) -> Arc<str> {
22933 "project".into()
22934 }
22935
22936 fn code_actions(
22937 &self,
22938 buffer: &Entity<Buffer>,
22939 range: Range<text::Anchor>,
22940 _window: &mut Window,
22941 cx: &mut App,
22942 ) -> Task<Result<Vec<CodeAction>>> {
22943 self.update(cx, |project, cx| {
22944 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22945 let code_actions = project.code_actions(buffer, range, None, cx);
22946 cx.background_spawn(async move {
22947 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22948 Ok(code_lens_actions
22949 .context("code lens fetch")?
22950 .into_iter()
22951 .flatten()
22952 .chain(
22953 code_actions
22954 .context("code action fetch")?
22955 .into_iter()
22956 .flatten(),
22957 )
22958 .collect())
22959 })
22960 })
22961 }
22962
22963 fn apply_code_action(
22964 &self,
22965 buffer_handle: Entity<Buffer>,
22966 action: CodeAction,
22967 _excerpt_id: ExcerptId,
22968 push_to_history: bool,
22969 _window: &mut Window,
22970 cx: &mut App,
22971 ) -> Task<Result<ProjectTransaction>> {
22972 self.update(cx, |project, cx| {
22973 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22974 })
22975 }
22976}
22977
22978fn snippet_completions(
22979 project: &Project,
22980 buffer: &Entity<Buffer>,
22981 buffer_position: text::Anchor,
22982 cx: &mut App,
22983) -> Task<Result<CompletionResponse>> {
22984 let languages = buffer.read(cx).languages_at(buffer_position);
22985 let snippet_store = project.snippets().read(cx);
22986
22987 let scopes: Vec<_> = languages
22988 .iter()
22989 .filter_map(|language| {
22990 let language_name = language.lsp_id();
22991 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22992
22993 if snippets.is_empty() {
22994 None
22995 } else {
22996 Some((language.default_scope(), snippets))
22997 }
22998 })
22999 .collect();
23000
23001 if scopes.is_empty() {
23002 return Task::ready(Ok(CompletionResponse {
23003 completions: vec![],
23004 display_options: CompletionDisplayOptions::default(),
23005 is_incomplete: false,
23006 }));
23007 }
23008
23009 let snapshot = buffer.read(cx).text_snapshot();
23010 let executor = cx.background_executor().clone();
23011
23012 cx.background_spawn(async move {
23013 let mut is_incomplete = false;
23014 let mut completions: Vec<Completion> = Vec::new();
23015 for (scope, snippets) in scopes.into_iter() {
23016 let classifier =
23017 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
23018
23019 const MAX_WORD_PREFIX_LEN: usize = 128;
23020 let last_word: String = snapshot
23021 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
23022 .take(MAX_WORD_PREFIX_LEN)
23023 .take_while(|c| classifier.is_word(*c))
23024 .collect::<String>()
23025 .chars()
23026 .rev()
23027 .collect();
23028
23029 if last_word.is_empty() {
23030 return Ok(CompletionResponse {
23031 completions: vec![],
23032 display_options: CompletionDisplayOptions::default(),
23033 is_incomplete: true,
23034 });
23035 }
23036
23037 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
23038 let to_lsp = |point: &text::Anchor| {
23039 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23040 point_to_lsp(end)
23041 };
23042 let lsp_end = to_lsp(&buffer_position);
23043
23044 let candidates = snippets
23045 .iter()
23046 .enumerate()
23047 .flat_map(|(ix, snippet)| {
23048 snippet
23049 .prefix
23050 .iter()
23051 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23052 })
23053 .collect::<Vec<StringMatchCandidate>>();
23054
23055 const MAX_RESULTS: usize = 100;
23056 let mut matches = fuzzy::match_strings(
23057 &candidates,
23058 &last_word,
23059 last_word.chars().any(|c| c.is_uppercase()),
23060 true,
23061 MAX_RESULTS,
23062 &Default::default(),
23063 executor.clone(),
23064 )
23065 .await;
23066
23067 if matches.len() >= MAX_RESULTS {
23068 is_incomplete = true;
23069 }
23070
23071 // Remove all candidates where the query's start does not match the start of any word in the candidate
23072 if let Some(query_start) = last_word.chars().next() {
23073 matches.retain(|string_match| {
23074 split_words(&string_match.string).any(|word| {
23075 // Check that the first codepoint of the word as lowercase matches the first
23076 // codepoint of the query as lowercase
23077 word.chars()
23078 .flat_map(|codepoint| codepoint.to_lowercase())
23079 .zip(query_start.to_lowercase())
23080 .all(|(word_cp, query_cp)| word_cp == query_cp)
23081 })
23082 });
23083 }
23084
23085 let matched_strings = matches
23086 .into_iter()
23087 .map(|m| m.string)
23088 .collect::<HashSet<_>>();
23089
23090 completions.extend(snippets.iter().filter_map(|snippet| {
23091 let matching_prefix = snippet
23092 .prefix
23093 .iter()
23094 .find(|prefix| matched_strings.contains(*prefix))?;
23095 let start = as_offset - last_word.len();
23096 let start = snapshot.anchor_before(start);
23097 let range = start..buffer_position;
23098 let lsp_start = to_lsp(&start);
23099 let lsp_range = lsp::Range {
23100 start: lsp_start,
23101 end: lsp_end,
23102 };
23103 Some(Completion {
23104 replace_range: range,
23105 new_text: snippet.body.clone(),
23106 source: CompletionSource::Lsp {
23107 insert_range: None,
23108 server_id: LanguageServerId(usize::MAX),
23109 resolved: true,
23110 lsp_completion: Box::new(lsp::CompletionItem {
23111 label: snippet.prefix.first().unwrap().clone(),
23112 kind: Some(CompletionItemKind::SNIPPET),
23113 label_details: snippet.description.as_ref().map(|description| {
23114 lsp::CompletionItemLabelDetails {
23115 detail: Some(description.clone()),
23116 description: None,
23117 }
23118 }),
23119 insert_text_format: Some(InsertTextFormat::SNIPPET),
23120 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23121 lsp::InsertReplaceEdit {
23122 new_text: snippet.body.clone(),
23123 insert: lsp_range,
23124 replace: lsp_range,
23125 },
23126 )),
23127 filter_text: Some(snippet.body.clone()),
23128 sort_text: Some(char::MAX.to_string()),
23129 ..lsp::CompletionItem::default()
23130 }),
23131 lsp_defaults: None,
23132 },
23133 label: CodeLabel::plain(matching_prefix.clone(), None),
23134 icon_path: None,
23135 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23136 single_line: snippet.name.clone().into(),
23137 plain_text: snippet
23138 .description
23139 .clone()
23140 .map(|description| description.into()),
23141 }),
23142 insert_text_mode: None,
23143 confirm: None,
23144 })
23145 }))
23146 }
23147
23148 Ok(CompletionResponse {
23149 completions,
23150 display_options: CompletionDisplayOptions::default(),
23151 is_incomplete,
23152 })
23153 })
23154}
23155
23156impl CompletionProvider for Entity<Project> {
23157 fn completions(
23158 &self,
23159 _excerpt_id: ExcerptId,
23160 buffer: &Entity<Buffer>,
23161 buffer_position: text::Anchor,
23162 options: CompletionContext,
23163 _window: &mut Window,
23164 cx: &mut Context<Editor>,
23165 ) -> Task<Result<Vec<CompletionResponse>>> {
23166 self.update(cx, |project, cx| {
23167 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23168 let project_completions = project.completions(buffer, buffer_position, options, cx);
23169 cx.background_spawn(async move {
23170 let mut responses = project_completions.await?;
23171 let snippets = snippets.await?;
23172 if !snippets.completions.is_empty() {
23173 responses.push(snippets);
23174 }
23175 Ok(responses)
23176 })
23177 })
23178 }
23179
23180 fn resolve_completions(
23181 &self,
23182 buffer: Entity<Buffer>,
23183 completion_indices: Vec<usize>,
23184 completions: Rc<RefCell<Box<[Completion]>>>,
23185 cx: &mut Context<Editor>,
23186 ) -> Task<Result<bool>> {
23187 self.update(cx, |project, cx| {
23188 project.lsp_store().update(cx, |lsp_store, cx| {
23189 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23190 })
23191 })
23192 }
23193
23194 fn apply_additional_edits_for_completion(
23195 &self,
23196 buffer: Entity<Buffer>,
23197 completions: Rc<RefCell<Box<[Completion]>>>,
23198 completion_index: usize,
23199 push_to_history: bool,
23200 cx: &mut Context<Editor>,
23201 ) -> Task<Result<Option<language::Transaction>>> {
23202 self.update(cx, |project, cx| {
23203 project.lsp_store().update(cx, |lsp_store, cx| {
23204 lsp_store.apply_additional_edits_for_completion(
23205 buffer,
23206 completions,
23207 completion_index,
23208 push_to_history,
23209 cx,
23210 )
23211 })
23212 })
23213 }
23214
23215 fn is_completion_trigger(
23216 &self,
23217 buffer: &Entity<Buffer>,
23218 position: language::Anchor,
23219 text: &str,
23220 trigger_in_words: bool,
23221 menu_is_open: bool,
23222 cx: &mut Context<Editor>,
23223 ) -> bool {
23224 let mut chars = text.chars();
23225 let char = if let Some(char) = chars.next() {
23226 char
23227 } else {
23228 return false;
23229 };
23230 if chars.next().is_some() {
23231 return false;
23232 }
23233
23234 let buffer = buffer.read(cx);
23235 let snapshot = buffer.snapshot();
23236 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23237 return false;
23238 }
23239 let classifier = snapshot
23240 .char_classifier_at(position)
23241 .scope_context(Some(CharScopeContext::Completion));
23242 if trigger_in_words && classifier.is_word(char) {
23243 return true;
23244 }
23245
23246 buffer.completion_triggers().contains(text)
23247 }
23248}
23249
23250impl SemanticsProvider for Entity<Project> {
23251 fn hover(
23252 &self,
23253 buffer: &Entity<Buffer>,
23254 position: text::Anchor,
23255 cx: &mut App,
23256 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23257 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23258 }
23259
23260 fn document_highlights(
23261 &self,
23262 buffer: &Entity<Buffer>,
23263 position: text::Anchor,
23264 cx: &mut App,
23265 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23266 Some(self.update(cx, |project, cx| {
23267 project.document_highlights(buffer, position, cx)
23268 }))
23269 }
23270
23271 fn definitions(
23272 &self,
23273 buffer: &Entity<Buffer>,
23274 position: text::Anchor,
23275 kind: GotoDefinitionKind,
23276 cx: &mut App,
23277 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23278 Some(self.update(cx, |project, cx| match kind {
23279 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23280 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23281 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23282 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23283 }))
23284 }
23285
23286 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23287 self.update(cx, |project, cx| {
23288 if project
23289 .active_debug_session(cx)
23290 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23291 {
23292 return true;
23293 }
23294
23295 buffer.update(cx, |buffer, cx| {
23296 project.any_language_server_supports_inlay_hints(buffer, cx)
23297 })
23298 })
23299 }
23300
23301 fn inline_values(
23302 &self,
23303 buffer_handle: Entity<Buffer>,
23304 range: Range<text::Anchor>,
23305 cx: &mut App,
23306 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23307 self.update(cx, |project, cx| {
23308 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23309
23310 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23311 })
23312 }
23313
23314 fn applicable_inlay_chunks(
23315 &self,
23316 buffer: &Entity<Buffer>,
23317 ranges: &[Range<text::Anchor>],
23318 cx: &mut App,
23319 ) -> Vec<Range<BufferRow>> {
23320 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23321 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23322 })
23323 }
23324
23325 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23326 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23327 lsp_store.invalidate_inlay_hints(for_buffers)
23328 });
23329 }
23330
23331 fn inlay_hints(
23332 &self,
23333 invalidate: InvalidationStrategy,
23334 buffer: Entity<Buffer>,
23335 ranges: Vec<Range<text::Anchor>>,
23336 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23337 cx: &mut App,
23338 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23339 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23340 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23341 }))
23342 }
23343
23344 fn range_for_rename(
23345 &self,
23346 buffer: &Entity<Buffer>,
23347 position: text::Anchor,
23348 cx: &mut App,
23349 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23350 Some(self.update(cx, |project, cx| {
23351 let buffer = buffer.clone();
23352 let task = project.prepare_rename(buffer.clone(), position, cx);
23353 cx.spawn(async move |_, cx| {
23354 Ok(match task.await? {
23355 PrepareRenameResponse::Success(range) => Some(range),
23356 PrepareRenameResponse::InvalidPosition => None,
23357 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23358 // Fallback on using TreeSitter info to determine identifier range
23359 buffer.read_with(cx, |buffer, _| {
23360 let snapshot = buffer.snapshot();
23361 let (range, kind) = snapshot.surrounding_word(position, None);
23362 if kind != Some(CharKind::Word) {
23363 return None;
23364 }
23365 Some(
23366 snapshot.anchor_before(range.start)
23367 ..snapshot.anchor_after(range.end),
23368 )
23369 })?
23370 }
23371 })
23372 })
23373 }))
23374 }
23375
23376 fn perform_rename(
23377 &self,
23378 buffer: &Entity<Buffer>,
23379 position: text::Anchor,
23380 new_name: String,
23381 cx: &mut App,
23382 ) -> Option<Task<Result<ProjectTransaction>>> {
23383 Some(self.update(cx, |project, cx| {
23384 project.perform_rename(buffer.clone(), position, new_name, cx)
23385 }))
23386 }
23387}
23388
23389fn consume_contiguous_rows(
23390 contiguous_row_selections: &mut Vec<Selection<Point>>,
23391 selection: &Selection<Point>,
23392 display_map: &DisplaySnapshot,
23393 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23394) -> (MultiBufferRow, MultiBufferRow) {
23395 contiguous_row_selections.push(selection.clone());
23396 let start_row = starting_row(selection, display_map);
23397 let mut end_row = ending_row(selection, display_map);
23398
23399 while let Some(next_selection) = selections.peek() {
23400 if next_selection.start.row <= end_row.0 {
23401 end_row = ending_row(next_selection, display_map);
23402 contiguous_row_selections.push(selections.next().unwrap().clone());
23403 } else {
23404 break;
23405 }
23406 }
23407 (start_row, end_row)
23408}
23409
23410fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23411 if selection.start.column > 0 {
23412 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23413 } else {
23414 MultiBufferRow(selection.start.row)
23415 }
23416}
23417
23418fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23419 if next_selection.end.column > 0 || next_selection.is_empty() {
23420 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23421 } else {
23422 MultiBufferRow(next_selection.end.row)
23423 }
23424}
23425
23426impl EditorSnapshot {
23427 pub fn remote_selections_in_range<'a>(
23428 &'a self,
23429 range: &'a Range<Anchor>,
23430 collaboration_hub: &dyn CollaborationHub,
23431 cx: &'a App,
23432 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23433 let participant_names = collaboration_hub.user_names(cx);
23434 let participant_indices = collaboration_hub.user_participant_indices(cx);
23435 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23436 let collaborators_by_replica_id = collaborators_by_peer_id
23437 .values()
23438 .map(|collaborator| (collaborator.replica_id, collaborator))
23439 .collect::<HashMap<_, _>>();
23440 self.buffer_snapshot()
23441 .selections_in_range(range, false)
23442 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23443 if replica_id == ReplicaId::AGENT {
23444 Some(RemoteSelection {
23445 replica_id,
23446 selection,
23447 cursor_shape,
23448 line_mode,
23449 collaborator_id: CollaboratorId::Agent,
23450 user_name: Some("Agent".into()),
23451 color: cx.theme().players().agent(),
23452 })
23453 } else {
23454 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23455 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23456 let user_name = participant_names.get(&collaborator.user_id).cloned();
23457 Some(RemoteSelection {
23458 replica_id,
23459 selection,
23460 cursor_shape,
23461 line_mode,
23462 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23463 user_name,
23464 color: if let Some(index) = participant_index {
23465 cx.theme().players().color_for_participant(index.0)
23466 } else {
23467 cx.theme().players().absent()
23468 },
23469 })
23470 }
23471 })
23472 }
23473
23474 pub fn hunks_for_ranges(
23475 &self,
23476 ranges: impl IntoIterator<Item = Range<Point>>,
23477 ) -> Vec<MultiBufferDiffHunk> {
23478 let mut hunks = Vec::new();
23479 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23480 HashMap::default();
23481 for query_range in ranges {
23482 let query_rows =
23483 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23484 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23485 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23486 ) {
23487 // Include deleted hunks that are adjacent to the query range, because
23488 // otherwise they would be missed.
23489 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23490 if hunk.status().is_deleted() {
23491 intersects_range |= hunk.row_range.start == query_rows.end;
23492 intersects_range |= hunk.row_range.end == query_rows.start;
23493 }
23494 if intersects_range {
23495 if !processed_buffer_rows
23496 .entry(hunk.buffer_id)
23497 .or_default()
23498 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23499 {
23500 continue;
23501 }
23502 hunks.push(hunk);
23503 }
23504 }
23505 }
23506
23507 hunks
23508 }
23509
23510 fn display_diff_hunks_for_rows<'a>(
23511 &'a self,
23512 display_rows: Range<DisplayRow>,
23513 folded_buffers: &'a HashSet<BufferId>,
23514 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23515 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23516 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23517
23518 self.buffer_snapshot()
23519 .diff_hunks_in_range(buffer_start..buffer_end)
23520 .filter_map(|hunk| {
23521 if folded_buffers.contains(&hunk.buffer_id) {
23522 return None;
23523 }
23524
23525 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23526 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23527
23528 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23529 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23530
23531 let display_hunk = if hunk_display_start.column() != 0 {
23532 DisplayDiffHunk::Folded {
23533 display_row: hunk_display_start.row(),
23534 }
23535 } else {
23536 let mut end_row = hunk_display_end.row();
23537 if hunk_display_end.column() > 0 {
23538 end_row.0 += 1;
23539 }
23540 let is_created_file = hunk.is_created_file();
23541 DisplayDiffHunk::Unfolded {
23542 status: hunk.status(),
23543 diff_base_byte_range: hunk.diff_base_byte_range,
23544 display_row_range: hunk_display_start.row()..end_row,
23545 multi_buffer_range: Anchor::range_in_buffer(
23546 hunk.excerpt_id,
23547 hunk.buffer_id,
23548 hunk.buffer_range,
23549 ),
23550 is_created_file,
23551 }
23552 };
23553
23554 Some(display_hunk)
23555 })
23556 }
23557
23558 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23559 self.display_snapshot
23560 .buffer_snapshot()
23561 .language_at(position)
23562 }
23563
23564 pub fn is_focused(&self) -> bool {
23565 self.is_focused
23566 }
23567
23568 pub fn placeholder_text(&self) -> Option<String> {
23569 self.placeholder_display_snapshot
23570 .as_ref()
23571 .map(|display_map| display_map.text())
23572 }
23573
23574 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23575 self.scroll_anchor.scroll_position(&self.display_snapshot)
23576 }
23577
23578 fn gutter_dimensions(
23579 &self,
23580 font_id: FontId,
23581 font_size: Pixels,
23582 max_line_number_width: Pixels,
23583 cx: &App,
23584 ) -> Option<GutterDimensions> {
23585 if !self.show_gutter {
23586 return None;
23587 }
23588
23589 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23590 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23591
23592 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23593 matches!(
23594 ProjectSettings::get_global(cx).git.git_gutter,
23595 GitGutterSetting::TrackedFiles
23596 )
23597 });
23598 let gutter_settings = EditorSettings::get_global(cx).gutter;
23599 let show_line_numbers = self
23600 .show_line_numbers
23601 .unwrap_or(gutter_settings.line_numbers);
23602 let line_gutter_width = if show_line_numbers {
23603 // Avoid flicker-like gutter resizes when the line number gains another digit by
23604 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23605 let min_width_for_number_on_gutter =
23606 ch_advance * gutter_settings.min_line_number_digits as f32;
23607 max_line_number_width.max(min_width_for_number_on_gutter)
23608 } else {
23609 0.0.into()
23610 };
23611
23612 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23613 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23614
23615 let git_blame_entries_width =
23616 self.git_blame_gutter_max_author_length
23617 .map(|max_author_length| {
23618 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23619 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23620
23621 /// The number of characters to dedicate to gaps and margins.
23622 const SPACING_WIDTH: usize = 4;
23623
23624 let max_char_count = max_author_length.min(renderer.max_author_length())
23625 + ::git::SHORT_SHA_LENGTH
23626 + MAX_RELATIVE_TIMESTAMP.len()
23627 + SPACING_WIDTH;
23628
23629 ch_advance * max_char_count
23630 });
23631
23632 let is_singleton = self.buffer_snapshot().is_singleton();
23633
23634 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23635 left_padding += if !is_singleton {
23636 ch_width * 4.0
23637 } else if show_runnables || show_breakpoints {
23638 ch_width * 3.0
23639 } else if show_git_gutter && show_line_numbers {
23640 ch_width * 2.0
23641 } else if show_git_gutter || show_line_numbers {
23642 ch_width
23643 } else {
23644 px(0.)
23645 };
23646
23647 let shows_folds = is_singleton && gutter_settings.folds;
23648
23649 let right_padding = if shows_folds && show_line_numbers {
23650 ch_width * 4.0
23651 } else if shows_folds || (!is_singleton && show_line_numbers) {
23652 ch_width * 3.0
23653 } else if show_line_numbers {
23654 ch_width
23655 } else {
23656 px(0.)
23657 };
23658
23659 Some(GutterDimensions {
23660 left_padding,
23661 right_padding,
23662 width: line_gutter_width + left_padding + right_padding,
23663 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23664 git_blame_entries_width,
23665 })
23666 }
23667
23668 pub fn render_crease_toggle(
23669 &self,
23670 buffer_row: MultiBufferRow,
23671 row_contains_cursor: bool,
23672 editor: Entity<Editor>,
23673 window: &mut Window,
23674 cx: &mut App,
23675 ) -> Option<AnyElement> {
23676 let folded = self.is_line_folded(buffer_row);
23677 let mut is_foldable = false;
23678
23679 if let Some(crease) = self
23680 .crease_snapshot
23681 .query_row(buffer_row, self.buffer_snapshot())
23682 {
23683 is_foldable = true;
23684 match crease {
23685 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23686 if let Some(render_toggle) = render_toggle {
23687 let toggle_callback =
23688 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23689 if folded {
23690 editor.update(cx, |editor, cx| {
23691 editor.fold_at(buffer_row, window, cx)
23692 });
23693 } else {
23694 editor.update(cx, |editor, cx| {
23695 editor.unfold_at(buffer_row, window, cx)
23696 });
23697 }
23698 });
23699 return Some((render_toggle)(
23700 buffer_row,
23701 folded,
23702 toggle_callback,
23703 window,
23704 cx,
23705 ));
23706 }
23707 }
23708 }
23709 }
23710
23711 is_foldable |= self.starts_indent(buffer_row);
23712
23713 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23714 Some(
23715 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23716 .toggle_state(folded)
23717 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23718 if folded {
23719 this.unfold_at(buffer_row, window, cx);
23720 } else {
23721 this.fold_at(buffer_row, window, cx);
23722 }
23723 }))
23724 .into_any_element(),
23725 )
23726 } else {
23727 None
23728 }
23729 }
23730
23731 pub fn render_crease_trailer(
23732 &self,
23733 buffer_row: MultiBufferRow,
23734 window: &mut Window,
23735 cx: &mut App,
23736 ) -> Option<AnyElement> {
23737 let folded = self.is_line_folded(buffer_row);
23738 if let Crease::Inline { render_trailer, .. } = self
23739 .crease_snapshot
23740 .query_row(buffer_row, self.buffer_snapshot())?
23741 {
23742 let render_trailer = render_trailer.as_ref()?;
23743 Some(render_trailer(buffer_row, folded, window, cx))
23744 } else {
23745 None
23746 }
23747 }
23748}
23749
23750impl Deref for EditorSnapshot {
23751 type Target = DisplaySnapshot;
23752
23753 fn deref(&self) -> &Self::Target {
23754 &self.display_snapshot
23755 }
23756}
23757
23758#[derive(Clone, Debug, PartialEq, Eq)]
23759pub enum EditorEvent {
23760 InputIgnored {
23761 text: Arc<str>,
23762 },
23763 InputHandled {
23764 utf16_range_to_replace: Option<Range<isize>>,
23765 text: Arc<str>,
23766 },
23767 ExcerptsAdded {
23768 buffer: Entity<Buffer>,
23769 predecessor: ExcerptId,
23770 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23771 },
23772 ExcerptsRemoved {
23773 ids: Vec<ExcerptId>,
23774 removed_buffer_ids: Vec<BufferId>,
23775 },
23776 BufferFoldToggled {
23777 ids: Vec<ExcerptId>,
23778 folded: bool,
23779 },
23780 ExcerptsEdited {
23781 ids: Vec<ExcerptId>,
23782 },
23783 ExcerptsExpanded {
23784 ids: Vec<ExcerptId>,
23785 },
23786 BufferEdited,
23787 Edited {
23788 transaction_id: clock::Lamport,
23789 },
23790 Reparsed(BufferId),
23791 Focused,
23792 FocusedIn,
23793 Blurred,
23794 DirtyChanged,
23795 Saved,
23796 TitleChanged,
23797 SelectionsChanged {
23798 local: bool,
23799 },
23800 ScrollPositionChanged {
23801 local: bool,
23802 autoscroll: bool,
23803 },
23804 TransactionUndone {
23805 transaction_id: clock::Lamport,
23806 },
23807 TransactionBegun {
23808 transaction_id: clock::Lamport,
23809 },
23810 CursorShapeChanged,
23811 BreadcrumbsChanged,
23812 PushedToNavHistory {
23813 anchor: Anchor,
23814 is_deactivate: bool,
23815 },
23816}
23817
23818impl EventEmitter<EditorEvent> for Editor {}
23819
23820impl Focusable for Editor {
23821 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23822 self.focus_handle.clone()
23823 }
23824}
23825
23826impl Render for Editor {
23827 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23828 let settings = ThemeSettings::get_global(cx);
23829
23830 let mut text_style = match self.mode {
23831 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23832 color: cx.theme().colors().editor_foreground,
23833 font_family: settings.ui_font.family.clone(),
23834 font_features: settings.ui_font.features.clone(),
23835 font_fallbacks: settings.ui_font.fallbacks.clone(),
23836 font_size: rems(0.875).into(),
23837 font_weight: settings.ui_font.weight,
23838 line_height: relative(settings.buffer_line_height.value()),
23839 ..Default::default()
23840 },
23841 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23842 color: cx.theme().colors().editor_foreground,
23843 font_family: settings.buffer_font.family.clone(),
23844 font_features: settings.buffer_font.features.clone(),
23845 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23846 font_size: settings.buffer_font_size(cx).into(),
23847 font_weight: settings.buffer_font.weight,
23848 line_height: relative(settings.buffer_line_height.value()),
23849 ..Default::default()
23850 },
23851 };
23852 if let Some(text_style_refinement) = &self.text_style_refinement {
23853 text_style.refine(text_style_refinement)
23854 }
23855
23856 let background = match self.mode {
23857 EditorMode::SingleLine => cx.theme().system().transparent,
23858 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23859 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23860 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23861 };
23862
23863 EditorElement::new(
23864 &cx.entity(),
23865 EditorStyle {
23866 background,
23867 border: cx.theme().colors().border,
23868 local_player: cx.theme().players().local(),
23869 text: text_style,
23870 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23871 syntax: cx.theme().syntax().clone(),
23872 status: cx.theme().status().clone(),
23873 inlay_hints_style: make_inlay_hints_style(cx),
23874 edit_prediction_styles: make_suggestion_styles(cx),
23875 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23876 show_underlines: self.diagnostics_enabled(),
23877 },
23878 )
23879 }
23880}
23881
23882impl EntityInputHandler for Editor {
23883 fn text_for_range(
23884 &mut self,
23885 range_utf16: Range<usize>,
23886 adjusted_range: &mut Option<Range<usize>>,
23887 _: &mut Window,
23888 cx: &mut Context<Self>,
23889 ) -> Option<String> {
23890 let snapshot = self.buffer.read(cx).read(cx);
23891 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23892 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23893 if (start.0..end.0) != range_utf16 {
23894 adjusted_range.replace(start.0..end.0);
23895 }
23896 Some(snapshot.text_for_range(start..end).collect())
23897 }
23898
23899 fn selected_text_range(
23900 &mut self,
23901 ignore_disabled_input: bool,
23902 _: &mut Window,
23903 cx: &mut Context<Self>,
23904 ) -> Option<UTF16Selection> {
23905 // Prevent the IME menu from appearing when holding down an alphabetic key
23906 // while input is disabled.
23907 if !ignore_disabled_input && !self.input_enabled {
23908 return None;
23909 }
23910
23911 let selection = self
23912 .selections
23913 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
23914 let range = selection.range();
23915
23916 Some(UTF16Selection {
23917 range: range.start.0..range.end.0,
23918 reversed: selection.reversed,
23919 })
23920 }
23921
23922 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23923 let snapshot = self.buffer.read(cx).read(cx);
23924 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23925 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23926 }
23927
23928 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23929 self.clear_highlights::<InputComposition>(cx);
23930 self.ime_transaction.take();
23931 }
23932
23933 fn replace_text_in_range(
23934 &mut self,
23935 range_utf16: Option<Range<usize>>,
23936 text: &str,
23937 window: &mut Window,
23938 cx: &mut Context<Self>,
23939 ) {
23940 if !self.input_enabled {
23941 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23942 return;
23943 }
23944
23945 self.transact(window, cx, |this, window, cx| {
23946 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23947 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23948 Some(this.selection_replacement_ranges(range_utf16, cx))
23949 } else {
23950 this.marked_text_ranges(cx)
23951 };
23952
23953 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23954 let newest_selection_id = this.selections.newest_anchor().id;
23955 this.selections
23956 .all::<OffsetUtf16>(&this.display_snapshot(cx))
23957 .iter()
23958 .zip(ranges_to_replace.iter())
23959 .find_map(|(selection, range)| {
23960 if selection.id == newest_selection_id {
23961 Some(
23962 (range.start.0 as isize - selection.head().0 as isize)
23963 ..(range.end.0 as isize - selection.head().0 as isize),
23964 )
23965 } else {
23966 None
23967 }
23968 })
23969 });
23970
23971 cx.emit(EditorEvent::InputHandled {
23972 utf16_range_to_replace: range_to_replace,
23973 text: text.into(),
23974 });
23975
23976 if let Some(new_selected_ranges) = new_selected_ranges {
23977 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23978 selections.select_ranges(new_selected_ranges)
23979 });
23980 this.backspace(&Default::default(), window, cx);
23981 }
23982
23983 this.handle_input(text, window, cx);
23984 });
23985
23986 if let Some(transaction) = self.ime_transaction {
23987 self.buffer.update(cx, |buffer, cx| {
23988 buffer.group_until_transaction(transaction, cx);
23989 });
23990 }
23991
23992 self.unmark_text(window, cx);
23993 }
23994
23995 fn replace_and_mark_text_in_range(
23996 &mut self,
23997 range_utf16: Option<Range<usize>>,
23998 text: &str,
23999 new_selected_range_utf16: Option<Range<usize>>,
24000 window: &mut Window,
24001 cx: &mut Context<Self>,
24002 ) {
24003 if !self.input_enabled {
24004 return;
24005 }
24006
24007 let transaction = self.transact(window, cx, |this, window, cx| {
24008 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24009 let snapshot = this.buffer.read(cx).read(cx);
24010 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24011 for marked_range in &mut marked_ranges {
24012 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24013 marked_range.start.0 += relative_range_utf16.start;
24014 marked_range.start =
24015 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24016 marked_range.end =
24017 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24018 }
24019 }
24020 Some(marked_ranges)
24021 } else if let Some(range_utf16) = range_utf16 {
24022 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24023 Some(this.selection_replacement_ranges(range_utf16, cx))
24024 } else {
24025 None
24026 };
24027
24028 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24029 let newest_selection_id = this.selections.newest_anchor().id;
24030 this.selections
24031 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24032 .iter()
24033 .zip(ranges_to_replace.iter())
24034 .find_map(|(selection, range)| {
24035 if selection.id == newest_selection_id {
24036 Some(
24037 (range.start.0 as isize - selection.head().0 as isize)
24038 ..(range.end.0 as isize - selection.head().0 as isize),
24039 )
24040 } else {
24041 None
24042 }
24043 })
24044 });
24045
24046 cx.emit(EditorEvent::InputHandled {
24047 utf16_range_to_replace: range_to_replace,
24048 text: text.into(),
24049 });
24050
24051 if let Some(ranges) = ranges_to_replace {
24052 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24053 s.select_ranges(ranges)
24054 });
24055 }
24056
24057 let marked_ranges = {
24058 let snapshot = this.buffer.read(cx).read(cx);
24059 this.selections
24060 .disjoint_anchors_arc()
24061 .iter()
24062 .map(|selection| {
24063 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24064 })
24065 .collect::<Vec<_>>()
24066 };
24067
24068 if text.is_empty() {
24069 this.unmark_text(window, cx);
24070 } else {
24071 this.highlight_text::<InputComposition>(
24072 marked_ranges.clone(),
24073 HighlightStyle {
24074 underline: Some(UnderlineStyle {
24075 thickness: px(1.),
24076 color: None,
24077 wavy: false,
24078 }),
24079 ..Default::default()
24080 },
24081 cx,
24082 );
24083 }
24084
24085 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24086 let use_autoclose = this.use_autoclose;
24087 let use_auto_surround = this.use_auto_surround;
24088 this.set_use_autoclose(false);
24089 this.set_use_auto_surround(false);
24090 this.handle_input(text, window, cx);
24091 this.set_use_autoclose(use_autoclose);
24092 this.set_use_auto_surround(use_auto_surround);
24093
24094 if let Some(new_selected_range) = new_selected_range_utf16 {
24095 let snapshot = this.buffer.read(cx).read(cx);
24096 let new_selected_ranges = marked_ranges
24097 .into_iter()
24098 .map(|marked_range| {
24099 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24100 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24101 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24102 snapshot.clip_offset_utf16(new_start, Bias::Left)
24103 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24104 })
24105 .collect::<Vec<_>>();
24106
24107 drop(snapshot);
24108 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24109 selections.select_ranges(new_selected_ranges)
24110 });
24111 }
24112 });
24113
24114 self.ime_transaction = self.ime_transaction.or(transaction);
24115 if let Some(transaction) = self.ime_transaction {
24116 self.buffer.update(cx, |buffer, cx| {
24117 buffer.group_until_transaction(transaction, cx);
24118 });
24119 }
24120
24121 if self.text_highlights::<InputComposition>(cx).is_none() {
24122 self.ime_transaction.take();
24123 }
24124 }
24125
24126 fn bounds_for_range(
24127 &mut self,
24128 range_utf16: Range<usize>,
24129 element_bounds: gpui::Bounds<Pixels>,
24130 window: &mut Window,
24131 cx: &mut Context<Self>,
24132 ) -> Option<gpui::Bounds<Pixels>> {
24133 let text_layout_details = self.text_layout_details(window);
24134 let CharacterDimensions {
24135 em_width,
24136 em_advance,
24137 line_height,
24138 } = self.character_dimensions(window);
24139
24140 let snapshot = self.snapshot(window, cx);
24141 let scroll_position = snapshot.scroll_position();
24142 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24143
24144 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24145 let x = Pixels::from(
24146 ScrollOffset::from(
24147 snapshot.x_for_display_point(start, &text_layout_details)
24148 + self.gutter_dimensions.full_width(),
24149 ) - scroll_left,
24150 );
24151 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24152
24153 Some(Bounds {
24154 origin: element_bounds.origin + point(x, y),
24155 size: size(em_width, line_height),
24156 })
24157 }
24158
24159 fn character_index_for_point(
24160 &mut self,
24161 point: gpui::Point<Pixels>,
24162 _window: &mut Window,
24163 _cx: &mut Context<Self>,
24164 ) -> Option<usize> {
24165 let position_map = self.last_position_map.as_ref()?;
24166 if !position_map.text_hitbox.contains(&point) {
24167 return None;
24168 }
24169 let display_point = position_map.point_for_position(point).previous_valid;
24170 let anchor = position_map
24171 .snapshot
24172 .display_point_to_anchor(display_point, Bias::Left);
24173 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24174 Some(utf16_offset.0)
24175 }
24176}
24177
24178trait SelectionExt {
24179 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24180 fn spanned_rows(
24181 &self,
24182 include_end_if_at_line_start: bool,
24183 map: &DisplaySnapshot,
24184 ) -> Range<MultiBufferRow>;
24185}
24186
24187impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24188 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24189 let start = self
24190 .start
24191 .to_point(map.buffer_snapshot())
24192 .to_display_point(map);
24193 let end = self
24194 .end
24195 .to_point(map.buffer_snapshot())
24196 .to_display_point(map);
24197 if self.reversed {
24198 end..start
24199 } else {
24200 start..end
24201 }
24202 }
24203
24204 fn spanned_rows(
24205 &self,
24206 include_end_if_at_line_start: bool,
24207 map: &DisplaySnapshot,
24208 ) -> Range<MultiBufferRow> {
24209 let start = self.start.to_point(map.buffer_snapshot());
24210 let mut end = self.end.to_point(map.buffer_snapshot());
24211 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24212 end.row -= 1;
24213 }
24214
24215 let buffer_start = map.prev_line_boundary(start).0;
24216 let buffer_end = map.next_line_boundary(end).0;
24217 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24218 }
24219}
24220
24221impl<T: InvalidationRegion> InvalidationStack<T> {
24222 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24223 where
24224 S: Clone + ToOffset,
24225 {
24226 while let Some(region) = self.last() {
24227 let all_selections_inside_invalidation_ranges =
24228 if selections.len() == region.ranges().len() {
24229 selections
24230 .iter()
24231 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24232 .all(|(selection, invalidation_range)| {
24233 let head = selection.head().to_offset(buffer);
24234 invalidation_range.start <= head && invalidation_range.end >= head
24235 })
24236 } else {
24237 false
24238 };
24239
24240 if all_selections_inside_invalidation_ranges {
24241 break;
24242 } else {
24243 self.pop();
24244 }
24245 }
24246 }
24247}
24248
24249impl<T> Default for InvalidationStack<T> {
24250 fn default() -> Self {
24251 Self(Default::default())
24252 }
24253}
24254
24255impl<T> Deref for InvalidationStack<T> {
24256 type Target = Vec<T>;
24257
24258 fn deref(&self) -> &Self::Target {
24259 &self.0
24260 }
24261}
24262
24263impl<T> DerefMut for InvalidationStack<T> {
24264 fn deref_mut(&mut self) -> &mut Self::Target {
24265 &mut self.0
24266 }
24267}
24268
24269impl InvalidationRegion for SnippetState {
24270 fn ranges(&self) -> &[Range<Anchor>] {
24271 &self.ranges[self.active_index]
24272 }
24273}
24274
24275fn edit_prediction_edit_text(
24276 current_snapshot: &BufferSnapshot,
24277 edits: &[(Range<Anchor>, String)],
24278 edit_preview: &EditPreview,
24279 include_deletions: bool,
24280 cx: &App,
24281) -> HighlightedText {
24282 let edits = edits
24283 .iter()
24284 .map(|(anchor, text)| {
24285 (
24286 anchor.start.text_anchor..anchor.end.text_anchor,
24287 text.clone(),
24288 )
24289 })
24290 .collect::<Vec<_>>();
24291
24292 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24293}
24294
24295fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24296 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24297 // Just show the raw edit text with basic styling
24298 let mut text = String::new();
24299 let mut highlights = Vec::new();
24300
24301 let insertion_highlight_style = HighlightStyle {
24302 color: Some(cx.theme().colors().text),
24303 ..Default::default()
24304 };
24305
24306 for (_, edit_text) in edits {
24307 let start_offset = text.len();
24308 text.push_str(edit_text);
24309 let end_offset = text.len();
24310
24311 if start_offset < end_offset {
24312 highlights.push((start_offset..end_offset, insertion_highlight_style));
24313 }
24314 }
24315
24316 HighlightedText {
24317 text: text.into(),
24318 highlights,
24319 }
24320}
24321
24322pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24323 match severity {
24324 lsp::DiagnosticSeverity::ERROR => colors.error,
24325 lsp::DiagnosticSeverity::WARNING => colors.warning,
24326 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24327 lsp::DiagnosticSeverity::HINT => colors.info,
24328 _ => colors.ignored,
24329 }
24330}
24331
24332pub fn styled_runs_for_code_label<'a>(
24333 label: &'a CodeLabel,
24334 syntax_theme: &'a theme::SyntaxTheme,
24335) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24336 let fade_out = HighlightStyle {
24337 fade_out: Some(0.35),
24338 ..Default::default()
24339 };
24340
24341 let mut prev_end = label.filter_range.end;
24342 label
24343 .runs
24344 .iter()
24345 .enumerate()
24346 .flat_map(move |(ix, (range, highlight_id))| {
24347 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24348 style
24349 } else {
24350 return Default::default();
24351 };
24352 let muted_style = style.highlight(fade_out);
24353
24354 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24355 if range.start >= label.filter_range.end {
24356 if range.start > prev_end {
24357 runs.push((prev_end..range.start, fade_out));
24358 }
24359 runs.push((range.clone(), muted_style));
24360 } else if range.end <= label.filter_range.end {
24361 runs.push((range.clone(), style));
24362 } else {
24363 runs.push((range.start..label.filter_range.end, style));
24364 runs.push((label.filter_range.end..range.end, muted_style));
24365 }
24366 prev_end = cmp::max(prev_end, range.end);
24367
24368 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24369 runs.push((prev_end..label.text.len(), fade_out));
24370 }
24371
24372 runs
24373 })
24374}
24375
24376pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24377 let mut prev_index = 0;
24378 let mut prev_codepoint: Option<char> = None;
24379 text.char_indices()
24380 .chain([(text.len(), '\0')])
24381 .filter_map(move |(index, codepoint)| {
24382 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24383 let is_boundary = index == text.len()
24384 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24385 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24386 if is_boundary {
24387 let chunk = &text[prev_index..index];
24388 prev_index = index;
24389 Some(chunk)
24390 } else {
24391 None
24392 }
24393 })
24394}
24395
24396pub trait RangeToAnchorExt: Sized {
24397 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24398
24399 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24400 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24401 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24402 }
24403}
24404
24405impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24406 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24407 let start_offset = self.start.to_offset(snapshot);
24408 let end_offset = self.end.to_offset(snapshot);
24409 if start_offset == end_offset {
24410 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24411 } else {
24412 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24413 }
24414 }
24415}
24416
24417pub trait RowExt {
24418 fn as_f64(&self) -> f64;
24419
24420 fn next_row(&self) -> Self;
24421
24422 fn previous_row(&self) -> Self;
24423
24424 fn minus(&self, other: Self) -> u32;
24425}
24426
24427impl RowExt for DisplayRow {
24428 fn as_f64(&self) -> f64 {
24429 self.0 as _
24430 }
24431
24432 fn next_row(&self) -> Self {
24433 Self(self.0 + 1)
24434 }
24435
24436 fn previous_row(&self) -> Self {
24437 Self(self.0.saturating_sub(1))
24438 }
24439
24440 fn minus(&self, other: Self) -> u32 {
24441 self.0 - other.0
24442 }
24443}
24444
24445impl RowExt for MultiBufferRow {
24446 fn as_f64(&self) -> f64 {
24447 self.0 as _
24448 }
24449
24450 fn next_row(&self) -> Self {
24451 Self(self.0 + 1)
24452 }
24453
24454 fn previous_row(&self) -> Self {
24455 Self(self.0.saturating_sub(1))
24456 }
24457
24458 fn minus(&self, other: Self) -> u32 {
24459 self.0 - other.0
24460 }
24461}
24462
24463trait RowRangeExt {
24464 type Row;
24465
24466 fn len(&self) -> usize;
24467
24468 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24469}
24470
24471impl RowRangeExt for Range<MultiBufferRow> {
24472 type Row = MultiBufferRow;
24473
24474 fn len(&self) -> usize {
24475 (self.end.0 - self.start.0) as usize
24476 }
24477
24478 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24479 (self.start.0..self.end.0).map(MultiBufferRow)
24480 }
24481}
24482
24483impl RowRangeExt for Range<DisplayRow> {
24484 type Row = DisplayRow;
24485
24486 fn len(&self) -> usize {
24487 (self.end.0 - self.start.0) as usize
24488 }
24489
24490 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24491 (self.start.0..self.end.0).map(DisplayRow)
24492 }
24493}
24494
24495/// If select range has more than one line, we
24496/// just point the cursor to range.start.
24497fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24498 if range.start.row == range.end.row {
24499 range
24500 } else {
24501 range.start..range.start
24502 }
24503}
24504pub struct KillRing(ClipboardItem);
24505impl Global for KillRing {}
24506
24507const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24508
24509enum BreakpointPromptEditAction {
24510 Log,
24511 Condition,
24512 HitCondition,
24513}
24514
24515struct BreakpointPromptEditor {
24516 pub(crate) prompt: Entity<Editor>,
24517 editor: WeakEntity<Editor>,
24518 breakpoint_anchor: Anchor,
24519 breakpoint: Breakpoint,
24520 edit_action: BreakpointPromptEditAction,
24521 block_ids: HashSet<CustomBlockId>,
24522 editor_margins: Arc<Mutex<EditorMargins>>,
24523 _subscriptions: Vec<Subscription>,
24524}
24525
24526impl BreakpointPromptEditor {
24527 const MAX_LINES: u8 = 4;
24528
24529 fn new(
24530 editor: WeakEntity<Editor>,
24531 breakpoint_anchor: Anchor,
24532 breakpoint: Breakpoint,
24533 edit_action: BreakpointPromptEditAction,
24534 window: &mut Window,
24535 cx: &mut Context<Self>,
24536 ) -> Self {
24537 let base_text = match edit_action {
24538 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24539 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24540 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24541 }
24542 .map(|msg| msg.to_string())
24543 .unwrap_or_default();
24544
24545 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24546 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24547
24548 let prompt = cx.new(|cx| {
24549 let mut prompt = Editor::new(
24550 EditorMode::AutoHeight {
24551 min_lines: 1,
24552 max_lines: Some(Self::MAX_LINES as usize),
24553 },
24554 buffer,
24555 None,
24556 window,
24557 cx,
24558 );
24559 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24560 prompt.set_show_cursor_when_unfocused(false, cx);
24561 prompt.set_placeholder_text(
24562 match edit_action {
24563 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24564 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24565 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24566 },
24567 window,
24568 cx,
24569 );
24570
24571 prompt
24572 });
24573
24574 Self {
24575 prompt,
24576 editor,
24577 breakpoint_anchor,
24578 breakpoint,
24579 edit_action,
24580 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24581 block_ids: Default::default(),
24582 _subscriptions: vec![],
24583 }
24584 }
24585
24586 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24587 self.block_ids.extend(block_ids)
24588 }
24589
24590 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24591 if let Some(editor) = self.editor.upgrade() {
24592 let message = self
24593 .prompt
24594 .read(cx)
24595 .buffer
24596 .read(cx)
24597 .as_singleton()
24598 .expect("A multi buffer in breakpoint prompt isn't possible")
24599 .read(cx)
24600 .as_rope()
24601 .to_string();
24602
24603 editor.update(cx, |editor, cx| {
24604 editor.edit_breakpoint_at_anchor(
24605 self.breakpoint_anchor,
24606 self.breakpoint.clone(),
24607 match self.edit_action {
24608 BreakpointPromptEditAction::Log => {
24609 BreakpointEditAction::EditLogMessage(message.into())
24610 }
24611 BreakpointPromptEditAction::Condition => {
24612 BreakpointEditAction::EditCondition(message.into())
24613 }
24614 BreakpointPromptEditAction::HitCondition => {
24615 BreakpointEditAction::EditHitCondition(message.into())
24616 }
24617 },
24618 cx,
24619 );
24620
24621 editor.remove_blocks(self.block_ids.clone(), None, cx);
24622 cx.focus_self(window);
24623 });
24624 }
24625 }
24626
24627 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24628 self.editor
24629 .update(cx, |editor, cx| {
24630 editor.remove_blocks(self.block_ids.clone(), None, cx);
24631 window.focus(&editor.focus_handle);
24632 })
24633 .log_err();
24634 }
24635
24636 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24637 let settings = ThemeSettings::get_global(cx);
24638 let text_style = TextStyle {
24639 color: if self.prompt.read(cx).read_only(cx) {
24640 cx.theme().colors().text_disabled
24641 } else {
24642 cx.theme().colors().text
24643 },
24644 font_family: settings.buffer_font.family.clone(),
24645 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24646 font_size: settings.buffer_font_size(cx).into(),
24647 font_weight: settings.buffer_font.weight,
24648 line_height: relative(settings.buffer_line_height.value()),
24649 ..Default::default()
24650 };
24651 EditorElement::new(
24652 &self.prompt,
24653 EditorStyle {
24654 background: cx.theme().colors().editor_background,
24655 local_player: cx.theme().players().local(),
24656 text: text_style,
24657 ..Default::default()
24658 },
24659 )
24660 }
24661}
24662
24663impl Render for BreakpointPromptEditor {
24664 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24665 let editor_margins = *self.editor_margins.lock();
24666 let gutter_dimensions = editor_margins.gutter;
24667 h_flex()
24668 .key_context("Editor")
24669 .bg(cx.theme().colors().editor_background)
24670 .border_y_1()
24671 .border_color(cx.theme().status().info_border)
24672 .size_full()
24673 .py(window.line_height() / 2.5)
24674 .on_action(cx.listener(Self::confirm))
24675 .on_action(cx.listener(Self::cancel))
24676 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24677 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24678 }
24679}
24680
24681impl Focusable for BreakpointPromptEditor {
24682 fn focus_handle(&self, cx: &App) -> FocusHandle {
24683 self.prompt.focus_handle(cx)
24684 }
24685}
24686
24687fn all_edits_insertions_or_deletions(
24688 edits: &Vec<(Range<Anchor>, String)>,
24689 snapshot: &MultiBufferSnapshot,
24690) -> bool {
24691 let mut all_insertions = true;
24692 let mut all_deletions = true;
24693
24694 for (range, new_text) in edits.iter() {
24695 let range_is_empty = range.to_offset(snapshot).is_empty();
24696 let text_is_empty = new_text.is_empty();
24697
24698 if range_is_empty != text_is_empty {
24699 if range_is_empty {
24700 all_deletions = false;
24701 } else {
24702 all_insertions = false;
24703 }
24704 } else {
24705 return false;
24706 }
24707
24708 if !all_insertions && !all_deletions {
24709 return false;
24710 }
24711 }
24712 all_insertions || all_deletions
24713}
24714
24715struct MissingEditPredictionKeybindingTooltip;
24716
24717impl Render for MissingEditPredictionKeybindingTooltip {
24718 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24719 ui::tooltip_container(cx, |container, cx| {
24720 container
24721 .flex_shrink_0()
24722 .max_w_80()
24723 .min_h(rems_from_px(124.))
24724 .justify_between()
24725 .child(
24726 v_flex()
24727 .flex_1()
24728 .text_ui_sm(cx)
24729 .child(Label::new("Conflict with Accept Keybinding"))
24730 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24731 )
24732 .child(
24733 h_flex()
24734 .pb_1()
24735 .gap_1()
24736 .items_end()
24737 .w_full()
24738 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24739 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24740 }))
24741 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24742 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24743 })),
24744 )
24745 })
24746 }
24747}
24748
24749#[derive(Debug, Clone, Copy, PartialEq)]
24750pub struct LineHighlight {
24751 pub background: Background,
24752 pub border: Option<gpui::Hsla>,
24753 pub include_gutter: bool,
24754 pub type_id: Option<TypeId>,
24755}
24756
24757struct LineManipulationResult {
24758 pub new_text: String,
24759 pub line_count_before: usize,
24760 pub line_count_after: usize,
24761}
24762
24763fn render_diff_hunk_controls(
24764 row: u32,
24765 status: &DiffHunkStatus,
24766 hunk_range: Range<Anchor>,
24767 is_created_file: bool,
24768 line_height: Pixels,
24769 editor: &Entity<Editor>,
24770 _window: &mut Window,
24771 cx: &mut App,
24772) -> AnyElement {
24773 h_flex()
24774 .h(line_height)
24775 .mr_1()
24776 .gap_1()
24777 .px_0p5()
24778 .pb_1()
24779 .border_x_1()
24780 .border_b_1()
24781 .border_color(cx.theme().colors().border_variant)
24782 .rounded_b_lg()
24783 .bg(cx.theme().colors().editor_background)
24784 .gap_1()
24785 .block_mouse_except_scroll()
24786 .shadow_md()
24787 .child(if status.has_secondary_hunk() {
24788 Button::new(("stage", row as u64), "Stage")
24789 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24790 .tooltip({
24791 let focus_handle = editor.focus_handle(cx);
24792 move |_window, cx| {
24793 Tooltip::for_action_in(
24794 "Stage Hunk",
24795 &::git::ToggleStaged,
24796 &focus_handle,
24797 cx,
24798 )
24799 }
24800 })
24801 .on_click({
24802 let editor = editor.clone();
24803 move |_event, _window, cx| {
24804 editor.update(cx, |editor, cx| {
24805 editor.stage_or_unstage_diff_hunks(
24806 true,
24807 vec![hunk_range.start..hunk_range.start],
24808 cx,
24809 );
24810 });
24811 }
24812 })
24813 } else {
24814 Button::new(("unstage", row as u64), "Unstage")
24815 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24816 .tooltip({
24817 let focus_handle = editor.focus_handle(cx);
24818 move |_window, cx| {
24819 Tooltip::for_action_in(
24820 "Unstage Hunk",
24821 &::git::ToggleStaged,
24822 &focus_handle,
24823 cx,
24824 )
24825 }
24826 })
24827 .on_click({
24828 let editor = editor.clone();
24829 move |_event, _window, cx| {
24830 editor.update(cx, |editor, cx| {
24831 editor.stage_or_unstage_diff_hunks(
24832 false,
24833 vec![hunk_range.start..hunk_range.start],
24834 cx,
24835 );
24836 });
24837 }
24838 })
24839 })
24840 .child(
24841 Button::new(("restore", row as u64), "Restore")
24842 .tooltip({
24843 let focus_handle = editor.focus_handle(cx);
24844 move |_window, cx| {
24845 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
24846 }
24847 })
24848 .on_click({
24849 let editor = editor.clone();
24850 move |_event, window, cx| {
24851 editor.update(cx, |editor, cx| {
24852 let snapshot = editor.snapshot(window, cx);
24853 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24854 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24855 });
24856 }
24857 })
24858 .disabled(is_created_file),
24859 )
24860 .when(
24861 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24862 |el| {
24863 el.child(
24864 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24865 .shape(IconButtonShape::Square)
24866 .icon_size(IconSize::Small)
24867 // .disabled(!has_multiple_hunks)
24868 .tooltip({
24869 let focus_handle = editor.focus_handle(cx);
24870 move |_window, cx| {
24871 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
24872 }
24873 })
24874 .on_click({
24875 let editor = editor.clone();
24876 move |_event, window, cx| {
24877 editor.update(cx, |editor, cx| {
24878 let snapshot = editor.snapshot(window, cx);
24879 let position =
24880 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24881 editor.go_to_hunk_before_or_after_position(
24882 &snapshot,
24883 position,
24884 Direction::Next,
24885 window,
24886 cx,
24887 );
24888 editor.expand_selected_diff_hunks(cx);
24889 });
24890 }
24891 }),
24892 )
24893 .child(
24894 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24895 .shape(IconButtonShape::Square)
24896 .icon_size(IconSize::Small)
24897 // .disabled(!has_multiple_hunks)
24898 .tooltip({
24899 let focus_handle = editor.focus_handle(cx);
24900 move |_window, cx| {
24901 Tooltip::for_action_in(
24902 "Previous Hunk",
24903 &GoToPreviousHunk,
24904 &focus_handle,
24905 cx,
24906 )
24907 }
24908 })
24909 .on_click({
24910 let editor = editor.clone();
24911 move |_event, window, cx| {
24912 editor.update(cx, |editor, cx| {
24913 let snapshot = editor.snapshot(window, cx);
24914 let point =
24915 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24916 editor.go_to_hunk_before_or_after_position(
24917 &snapshot,
24918 point,
24919 Direction::Prev,
24920 window,
24921 cx,
24922 );
24923 editor.expand_selected_diff_hunks(cx);
24924 });
24925 }
24926 }),
24927 )
24928 },
24929 )
24930 .into_any_element()
24931}
24932
24933pub fn multibuffer_context_lines(cx: &App) -> u32 {
24934 EditorSettings::try_get(cx)
24935 .map(|settings| settings.excerpt_context_lines)
24936 .unwrap_or(2)
24937 .min(32)
24938}