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, AhoCorasickBuilder, BuildError};
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, LanguageRegistry, OffsetRangeExt, OutlineItem, Point, Runnable,
121 RunnableRange, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
122 WordsQuery,
123 language_settings::{
124 self, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings,
125 language_settings,
126 },
127 point_from_lsp, point_to_lsp, text_diff_with_options,
128};
129use linked_editing_ranges::refresh_linked_ranges;
130use lsp::{
131 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
132 LanguageServerId,
133};
134use lsp_colors::LspColorData;
135use markdown::Markdown;
136use mouse_context_menu::MouseContextMenu;
137use movement::TextLayoutDetails;
138use multi_buffer::{
139 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
140};
141use parking_lot::Mutex;
142use persistence::DB;
143use project::{
144 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
145 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
146 InvalidationStrategy, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem,
147 ProjectPath, ProjectTransaction, TaskSourceKind,
148 debugger::{
149 breakpoint_store::{
150 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
151 BreakpointStore, BreakpointStoreEvent,
152 },
153 session::{Session, SessionEvent},
154 },
155 git_store::GitStoreEvent,
156 lsp_store::{
157 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
158 OpenLspBufferHandle,
159 },
160 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
161};
162use rand::seq::SliceRandom;
163use rpc::{ErrorCode, ErrorExt, proto::PeerId};
164use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
165use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
166use serde::{Deserialize, Serialize};
167use settings::{
168 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
169 update_settings_file,
170};
171use smallvec::{SmallVec, smallvec};
172use snippet::Snippet;
173use std::{
174 any::{Any, TypeId},
175 borrow::Cow,
176 cell::{OnceCell, RefCell},
177 cmp::{self, Ordering, Reverse},
178 iter::{self, Peekable},
179 mem,
180 num::NonZeroU32,
181 ops::{Deref, DerefMut, Not, Range, RangeInclusive},
182 path::{Path, PathBuf},
183 rc::Rc,
184 sync::Arc,
185 time::{Duration, Instant},
186};
187use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
188use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
189use theme::{
190 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
191 observe_buffer_font_size_adjustment,
192};
193use ui::{
194 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
195 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
196};
197use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
198use workspace::{
199 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
200 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
201 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
202 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
203 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
204 searchable::SearchEvent,
205};
206
207use crate::{
208 code_context_menus::CompletionsMenuSource,
209 editor_settings::MultiCursorModifier,
210 hover_links::{find_url, find_url_from_range},
211 inlays::{
212 InlineValueCache,
213 inlay_hints::{LspInlayHintData, inlay_hint_settings},
214 },
215 scroll::{ScrollOffset, ScrollPixelOffset},
216 selections_collection::resolve_selections_wrapping_blocks,
217 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
218};
219
220pub const FILE_HEADER_HEIGHT: u32 = 2;
221pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
222const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
223const MAX_LINE_LEN: usize = 1024;
224const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
225const MAX_SELECTION_HISTORY_LEN: usize = 1024;
226pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
227#[doc(hidden)]
228pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
229pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
230
231pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
232pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
233pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
234pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
235
236pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
237pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
238pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
239
240pub type RenderDiffHunkControlsFn = Arc<
241 dyn Fn(
242 u32,
243 &DiffHunkStatus,
244 Range<Anchor>,
245 bool,
246 Pixels,
247 &Entity<Editor>,
248 &mut Window,
249 &mut App,
250 ) -> AnyElement,
251>;
252
253enum ReportEditorEvent {
254 Saved { auto_saved: bool },
255 EditorOpened,
256 Closed,
257}
258
259impl ReportEditorEvent {
260 pub fn event_type(&self) -> &'static str {
261 match self {
262 Self::Saved { .. } => "Editor Saved",
263 Self::EditorOpened => "Editor Opened",
264 Self::Closed => "Editor Closed",
265 }
266 }
267}
268
269pub enum ActiveDebugLine {}
270pub enum DebugStackFrameLine {}
271enum DocumentHighlightRead {}
272enum DocumentHighlightWrite {}
273enum InputComposition {}
274pub enum PendingInput {}
275enum SelectedTextHighlight {}
276
277pub enum ConflictsOuter {}
278pub enum ConflictsOurs {}
279pub enum ConflictsTheirs {}
280pub enum ConflictsOursMarker {}
281pub enum ConflictsTheirsMarker {}
282
283#[derive(Debug, Copy, Clone, PartialEq, Eq)]
284pub enum Navigated {
285 Yes,
286 No,
287}
288
289impl Navigated {
290 pub fn from_bool(yes: bool) -> Navigated {
291 if yes { Navigated::Yes } else { Navigated::No }
292 }
293}
294
295#[derive(Debug, Clone, PartialEq, Eq)]
296enum DisplayDiffHunk {
297 Folded {
298 display_row: DisplayRow,
299 },
300 Unfolded {
301 is_created_file: bool,
302 diff_base_byte_range: Range<usize>,
303 display_row_range: Range<DisplayRow>,
304 multi_buffer_range: Range<Anchor>,
305 status: DiffHunkStatus,
306 },
307}
308
309pub enum HideMouseCursorOrigin {
310 TypingAction,
311 MovementAction,
312}
313
314pub fn init(cx: &mut App) {
315 cx.set_global(GlobalBlameRenderer(Arc::new(())));
316
317 workspace::register_project_item::<Editor>(cx);
318 workspace::FollowableViewRegistry::register::<Editor>(cx);
319 workspace::register_serializable_item::<Editor>(cx);
320
321 cx.observe_new(
322 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
323 workspace.register_action(Editor::new_file);
324 workspace.register_action(Editor::new_file_split);
325 workspace.register_action(Editor::new_file_vertical);
326 workspace.register_action(Editor::new_file_horizontal);
327 workspace.register_action(Editor::cancel_language_server_work);
328 workspace.register_action(Editor::toggle_focus);
329 },
330 )
331 .detach();
332
333 cx.on_action(move |_: &workspace::NewFile, cx| {
334 let app_state = workspace::AppState::global(cx);
335 if let Some(app_state) = app_state.upgrade() {
336 workspace::open_new(
337 Default::default(),
338 app_state,
339 cx,
340 |workspace, window, cx| {
341 Editor::new_file(workspace, &Default::default(), window, cx)
342 },
343 )
344 .detach();
345 }
346 });
347 cx.on_action(move |_: &workspace::NewWindow, cx| {
348 let app_state = workspace::AppState::global(cx);
349 if let Some(app_state) = app_state.upgrade() {
350 workspace::open_new(
351 Default::default(),
352 app_state,
353 cx,
354 |workspace, window, cx| {
355 cx.activate(true);
356 Editor::new_file(workspace, &Default::default(), window, cx)
357 },
358 )
359 .detach();
360 }
361 });
362}
363
364pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
365 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
366}
367
368pub trait DiagnosticRenderer {
369 fn render_group(
370 &self,
371 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
372 buffer_id: BufferId,
373 snapshot: EditorSnapshot,
374 editor: WeakEntity<Editor>,
375 language_registry: Option<Arc<LanguageRegistry>>,
376 cx: &mut App,
377 ) -> Vec<BlockProperties<Anchor>>;
378
379 fn render_hover(
380 &self,
381 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
382 range: Range<Point>,
383 buffer_id: BufferId,
384 language_registry: Option<Arc<LanguageRegistry>>,
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>, Arc<str>)>,
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(Debug, Clone, Copy, PartialEq, Eq)]
813pub enum BufferSerialization {
814 All,
815 NonDirtyBuffers,
816}
817
818impl BufferSerialization {
819 fn new(restore_unsaved_buffers: bool) -> Self {
820 if restore_unsaved_buffers {
821 Self::All
822 } else {
823 Self::NonDirtyBuffers
824 }
825 }
826}
827
828#[derive(Clone, Debug)]
829struct RunnableTasks {
830 templates: Vec<(TaskSourceKind, TaskTemplate)>,
831 offset: multi_buffer::Anchor,
832 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
833 column: u32,
834 // Values of all named captures, including those starting with '_'
835 extra_variables: HashMap<String, String>,
836 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
837 context_range: Range<BufferOffset>,
838}
839
840impl RunnableTasks {
841 fn resolve<'a>(
842 &'a self,
843 cx: &'a task::TaskContext,
844 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
845 self.templates.iter().filter_map(|(kind, template)| {
846 template
847 .resolve_task(&kind.to_id_base(), cx)
848 .map(|task| (kind.clone(), task))
849 })
850 }
851}
852
853#[derive(Clone)]
854pub struct ResolvedTasks {
855 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
856 position: Anchor,
857}
858
859#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
860struct BufferOffset(usize);
861
862/// Addons allow storing per-editor state in other crates (e.g. Vim)
863pub trait Addon: 'static {
864 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
865
866 fn render_buffer_header_controls(
867 &self,
868 _: &ExcerptInfo,
869 _: &Window,
870 _: &App,
871 ) -> Option<AnyElement> {
872 None
873 }
874
875 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
876 None
877 }
878
879 fn to_any(&self) -> &dyn std::any::Any;
880
881 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
882 None
883 }
884}
885
886struct ChangeLocation {
887 current: Option<Vec<Anchor>>,
888 original: Vec<Anchor>,
889}
890impl ChangeLocation {
891 fn locations(&self) -> &[Anchor] {
892 self.current.as_ref().unwrap_or(&self.original)
893 }
894}
895
896/// A set of caret positions, registered when the editor was edited.
897pub struct ChangeList {
898 changes: Vec<ChangeLocation>,
899 /// Currently "selected" change.
900 position: Option<usize>,
901}
902
903impl ChangeList {
904 pub fn new() -> Self {
905 Self {
906 changes: Vec::new(),
907 position: None,
908 }
909 }
910
911 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
912 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
913 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
914 if self.changes.is_empty() {
915 return None;
916 }
917
918 let prev = self.position.unwrap_or(self.changes.len());
919 let next = if direction == Direction::Prev {
920 prev.saturating_sub(count)
921 } else {
922 (prev + count).min(self.changes.len() - 1)
923 };
924 self.position = Some(next);
925 self.changes.get(next).map(|change| change.locations())
926 }
927
928 /// Adds a new change to the list, resetting the change list position.
929 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
930 self.position.take();
931 if let Some(last) = self.changes.last_mut()
932 && group
933 {
934 last.current = Some(new_positions)
935 } else {
936 self.changes.push(ChangeLocation {
937 original: new_positions,
938 current: None,
939 });
940 }
941 }
942
943 pub fn last(&self) -> Option<&[Anchor]> {
944 self.changes.last().map(|change| change.locations())
945 }
946
947 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
948 self.changes.last().map(|change| change.original.as_slice())
949 }
950
951 pub fn invert_last_group(&mut self) {
952 if let Some(last) = self.changes.last_mut()
953 && let Some(current) = last.current.as_mut()
954 {
955 mem::swap(&mut last.original, current);
956 }
957 }
958}
959
960#[derive(Clone)]
961struct InlineBlamePopoverState {
962 scroll_handle: ScrollHandle,
963 commit_message: Option<ParsedCommitMessage>,
964 markdown: Entity<Markdown>,
965}
966
967struct InlineBlamePopover {
968 position: gpui::Point<Pixels>,
969 hide_task: Option<Task<()>>,
970 popover_bounds: Option<Bounds<Pixels>>,
971 popover_state: InlineBlamePopoverState,
972 keyboard_grace: bool,
973}
974
975enum SelectionDragState {
976 /// State when no drag related activity is detected.
977 None,
978 /// State when the mouse is down on a selection that is about to be dragged.
979 ReadyToDrag {
980 selection: Selection<Anchor>,
981 click_position: gpui::Point<Pixels>,
982 mouse_down_time: Instant,
983 },
984 /// State when the mouse is dragging the selection in the editor.
985 Dragging {
986 selection: Selection<Anchor>,
987 drop_cursor: Selection<Anchor>,
988 hide_drop_cursor: bool,
989 },
990}
991
992enum ColumnarSelectionState {
993 FromMouse {
994 selection_tail: Anchor,
995 display_point: Option<DisplayPoint>,
996 },
997 FromSelection {
998 selection_tail: Anchor,
999 },
1000}
1001
1002/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1003/// a breakpoint on them.
1004#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1005struct PhantomBreakpointIndicator {
1006 display_row: DisplayRow,
1007 /// There's a small debounce between hovering over the line and showing the indicator.
1008 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1009 is_active: bool,
1010 collides_with_existing_breakpoint: bool,
1011}
1012
1013/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1014///
1015/// See the [module level documentation](self) for more information.
1016pub struct Editor {
1017 focus_handle: FocusHandle,
1018 last_focused_descendant: Option<WeakFocusHandle>,
1019 /// The text buffer being edited
1020 buffer: Entity<MultiBuffer>,
1021 /// Map of how text in the buffer should be displayed.
1022 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1023 pub display_map: Entity<DisplayMap>,
1024 placeholder_display_map: Option<Entity<DisplayMap>>,
1025 pub selections: SelectionsCollection,
1026 pub scroll_manager: ScrollManager,
1027 /// When inline assist editors are linked, they all render cursors because
1028 /// typing enters text into each of them, even the ones that aren't focused.
1029 pub(crate) show_cursor_when_unfocused: bool,
1030 columnar_selection_state: Option<ColumnarSelectionState>,
1031 add_selections_state: Option<AddSelectionsState>,
1032 select_next_state: Option<SelectNextState>,
1033 select_prev_state: Option<SelectNextState>,
1034 selection_history: SelectionHistory,
1035 defer_selection_effects: bool,
1036 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1037 autoclose_regions: Vec<AutocloseRegion>,
1038 snippet_stack: InvalidationStack<SnippetState>,
1039 select_syntax_node_history: SelectSyntaxNodeHistory,
1040 ime_transaction: Option<TransactionId>,
1041 pub diagnostics_max_severity: DiagnosticSeverity,
1042 active_diagnostics: ActiveDiagnostic,
1043 show_inline_diagnostics: bool,
1044 inline_diagnostics_update: Task<()>,
1045 inline_diagnostics_enabled: bool,
1046 diagnostics_enabled: bool,
1047 word_completions_enabled: bool,
1048 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1049 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1050 hard_wrap: Option<usize>,
1051 project: Option<Entity<Project>>,
1052 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1053 completion_provider: Option<Rc<dyn CompletionProvider>>,
1054 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1055 blink_manager: Entity<BlinkManager>,
1056 show_cursor_names: bool,
1057 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1058 pub show_local_selections: bool,
1059 mode: EditorMode,
1060 show_breadcrumbs: bool,
1061 show_gutter: bool,
1062 show_scrollbars: ScrollbarAxes,
1063 minimap_visibility: MinimapVisibility,
1064 offset_content: bool,
1065 disable_expand_excerpt_buttons: bool,
1066 show_line_numbers: Option<bool>,
1067 use_relative_line_numbers: Option<bool>,
1068 show_git_diff_gutter: Option<bool>,
1069 show_code_actions: Option<bool>,
1070 show_runnables: Option<bool>,
1071 show_breakpoints: Option<bool>,
1072 show_wrap_guides: Option<bool>,
1073 show_indent_guides: Option<bool>,
1074 highlight_order: usize,
1075 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1076 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1077 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1078 scrollbar_marker_state: ScrollbarMarkerState,
1079 active_indent_guides_state: ActiveIndentGuidesState,
1080 nav_history: Option<ItemNavHistory>,
1081 context_menu: RefCell<Option<CodeContextMenu>>,
1082 context_menu_options: Option<ContextMenuOptions>,
1083 mouse_context_menu: Option<MouseContextMenu>,
1084 completion_tasks: Vec<(CompletionId, Task<()>)>,
1085 inline_blame_popover: Option<InlineBlamePopover>,
1086 inline_blame_popover_show_task: Option<Task<()>>,
1087 signature_help_state: SignatureHelpState,
1088 auto_signature_help: Option<bool>,
1089 find_all_references_task_sources: Vec<Anchor>,
1090 next_completion_id: CompletionId,
1091 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1092 code_actions_task: Option<Task<Result<()>>>,
1093 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1094 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1095 document_highlights_task: Option<Task<()>>,
1096 linked_editing_range_task: Option<Task<Option<()>>>,
1097 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1098 pending_rename: Option<RenameState>,
1099 searchable: bool,
1100 cursor_shape: CursorShape,
1101 current_line_highlight: Option<CurrentLineHighlight>,
1102 autoindent_mode: Option<AutoindentMode>,
1103 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1104 input_enabled: bool,
1105 use_modal_editing: bool,
1106 read_only: bool,
1107 leader_id: Option<CollaboratorId>,
1108 remote_id: Option<ViewId>,
1109 pub hover_state: HoverState,
1110 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1111 gutter_hovered: bool,
1112 hovered_link_state: Option<HoveredLinkState>,
1113 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1114 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1115 active_edit_prediction: Option<EditPredictionState>,
1116 /// Used to prevent flickering as the user types while the menu is open
1117 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1118 edit_prediction_settings: EditPredictionSettings,
1119 edit_predictions_hidden_for_vim_mode: bool,
1120 show_edit_predictions_override: Option<bool>,
1121 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1122 edit_prediction_preview: EditPredictionPreview,
1123 edit_prediction_indent_conflict: bool,
1124 edit_prediction_requires_modifier_in_indent_conflict: bool,
1125 next_inlay_id: usize,
1126 next_color_inlay_id: usize,
1127 _subscriptions: Vec<Subscription>,
1128 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1129 gutter_dimensions: GutterDimensions,
1130 style: Option<EditorStyle>,
1131 text_style_refinement: Option<TextStyleRefinement>,
1132 next_editor_action_id: EditorActionId,
1133 editor_actions: Rc<
1134 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1135 >,
1136 use_autoclose: bool,
1137 use_auto_surround: bool,
1138 auto_replace_emoji_shortcode: bool,
1139 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1140 show_git_blame_gutter: bool,
1141 show_git_blame_inline: bool,
1142 show_git_blame_inline_delay_task: Option<Task<()>>,
1143 git_blame_inline_enabled: bool,
1144 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1145 buffer_serialization: Option<BufferSerialization>,
1146 show_selection_menu: Option<bool>,
1147 blame: Option<Entity<GitBlame>>,
1148 blame_subscription: Option<Subscription>,
1149 custom_context_menu: Option<
1150 Box<
1151 dyn 'static
1152 + Fn(
1153 &mut Self,
1154 DisplayPoint,
1155 &mut Window,
1156 &mut Context<Self>,
1157 ) -> Option<Entity<ui::ContextMenu>>,
1158 >,
1159 >,
1160 last_bounds: Option<Bounds<Pixels>>,
1161 last_position_map: Option<Rc<PositionMap>>,
1162 expect_bounds_change: Option<Bounds<Pixels>>,
1163 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1164 tasks_update_task: Option<Task<()>>,
1165 breakpoint_store: Option<Entity<BreakpointStore>>,
1166 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1167 hovered_diff_hunk_row: Option<DisplayRow>,
1168 pull_diagnostics_task: Task<()>,
1169 in_project_search: bool,
1170 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1171 breadcrumb_header: Option<String>,
1172 focused_block: Option<FocusedBlock>,
1173 next_scroll_position: NextScrollCursorCenterTopBottom,
1174 addons: HashMap<TypeId, Box<dyn Addon>>,
1175 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1176 load_diff_task: Option<Shared<Task<()>>>,
1177 /// Whether we are temporarily displaying a diff other than git's
1178 temporary_diff_override: bool,
1179 selection_mark_mode: bool,
1180 toggle_fold_multiple_buffers: Task<()>,
1181 _scroll_cursor_center_top_bottom_task: Task<()>,
1182 serialize_selections: Task<()>,
1183 serialize_folds: Task<()>,
1184 mouse_cursor_hidden: bool,
1185 minimap: Option<Entity<Self>>,
1186 hide_mouse_mode: HideMouseMode,
1187 pub change_list: ChangeList,
1188 inline_value_cache: InlineValueCache,
1189
1190 selection_drag_state: SelectionDragState,
1191 colors: Option<LspColorData>,
1192 post_scroll_update: Task<()>,
1193 refresh_colors_task: Task<()>,
1194 inlay_hints: Option<LspInlayHintData>,
1195 folding_newlines: Task<()>,
1196 select_next_is_case_sensitive: Option<bool>,
1197 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1198}
1199
1200fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1201 if debounce_ms > 0 {
1202 Some(Duration::from_millis(debounce_ms))
1203 } else {
1204 None
1205 }
1206}
1207
1208#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1209enum NextScrollCursorCenterTopBottom {
1210 #[default]
1211 Center,
1212 Top,
1213 Bottom,
1214}
1215
1216impl NextScrollCursorCenterTopBottom {
1217 fn next(&self) -> Self {
1218 match self {
1219 Self::Center => Self::Top,
1220 Self::Top => Self::Bottom,
1221 Self::Bottom => Self::Center,
1222 }
1223 }
1224}
1225
1226#[derive(Clone)]
1227pub struct EditorSnapshot {
1228 pub mode: EditorMode,
1229 show_gutter: bool,
1230 show_line_numbers: Option<bool>,
1231 show_git_diff_gutter: Option<bool>,
1232 show_code_actions: Option<bool>,
1233 show_runnables: Option<bool>,
1234 show_breakpoints: Option<bool>,
1235 git_blame_gutter_max_author_length: Option<usize>,
1236 pub display_snapshot: DisplaySnapshot,
1237 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1238 is_focused: bool,
1239 scroll_anchor: ScrollAnchor,
1240 ongoing_scroll: OngoingScroll,
1241 current_line_highlight: CurrentLineHighlight,
1242 gutter_hovered: bool,
1243}
1244
1245#[derive(Default, Debug, Clone, Copy)]
1246pub struct GutterDimensions {
1247 pub left_padding: Pixels,
1248 pub right_padding: Pixels,
1249 pub width: Pixels,
1250 pub margin: Pixels,
1251 pub git_blame_entries_width: Option<Pixels>,
1252}
1253
1254impl GutterDimensions {
1255 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1256 Self {
1257 margin: Self::default_gutter_margin(font_id, font_size, cx),
1258 ..Default::default()
1259 }
1260 }
1261
1262 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1263 -cx.text_system().descent(font_id, font_size)
1264 }
1265 /// The full width of the space taken up by the gutter.
1266 pub fn full_width(&self) -> Pixels {
1267 self.margin + self.width
1268 }
1269
1270 /// The width of the space reserved for the fold indicators,
1271 /// use alongside 'justify_end' and `gutter_width` to
1272 /// right align content with the line numbers
1273 pub fn fold_area_width(&self) -> Pixels {
1274 self.margin + self.right_padding
1275 }
1276}
1277
1278struct CharacterDimensions {
1279 em_width: Pixels,
1280 em_advance: Pixels,
1281 line_height: Pixels,
1282}
1283
1284#[derive(Debug)]
1285pub struct RemoteSelection {
1286 pub replica_id: ReplicaId,
1287 pub selection: Selection<Anchor>,
1288 pub cursor_shape: CursorShape,
1289 pub collaborator_id: CollaboratorId,
1290 pub line_mode: bool,
1291 pub user_name: Option<SharedString>,
1292 pub color: PlayerColor,
1293}
1294
1295#[derive(Clone, Debug)]
1296struct SelectionHistoryEntry {
1297 selections: Arc<[Selection<Anchor>]>,
1298 select_next_state: Option<SelectNextState>,
1299 select_prev_state: Option<SelectNextState>,
1300 add_selections_state: Option<AddSelectionsState>,
1301}
1302
1303#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1304enum SelectionHistoryMode {
1305 #[default]
1306 Normal,
1307 Undoing,
1308 Redoing,
1309 Skipping,
1310}
1311
1312#[derive(Clone, PartialEq, Eq, Hash)]
1313struct HoveredCursor {
1314 replica_id: ReplicaId,
1315 selection_id: usize,
1316}
1317
1318#[derive(Debug)]
1319/// SelectionEffects controls the side-effects of updating the selection.
1320///
1321/// The default behaviour does "what you mostly want":
1322/// - it pushes to the nav history if the cursor moved by >10 lines
1323/// - it re-triggers completion requests
1324/// - it scrolls to fit
1325///
1326/// You might want to modify these behaviours. For example when doing a "jump"
1327/// like go to definition, we always want to add to nav history; but when scrolling
1328/// in vim mode we never do.
1329///
1330/// Similarly, you might want to disable scrolling if you don't want the viewport to
1331/// move.
1332#[derive(Clone)]
1333pub struct SelectionEffects {
1334 nav_history: Option<bool>,
1335 completions: bool,
1336 scroll: Option<Autoscroll>,
1337}
1338
1339impl Default for SelectionEffects {
1340 fn default() -> Self {
1341 Self {
1342 nav_history: None,
1343 completions: true,
1344 scroll: Some(Autoscroll::fit()),
1345 }
1346 }
1347}
1348impl SelectionEffects {
1349 pub fn scroll(scroll: Autoscroll) -> Self {
1350 Self {
1351 scroll: Some(scroll),
1352 ..Default::default()
1353 }
1354 }
1355
1356 pub fn no_scroll() -> Self {
1357 Self {
1358 scroll: None,
1359 ..Default::default()
1360 }
1361 }
1362
1363 pub fn completions(self, completions: bool) -> Self {
1364 Self {
1365 completions,
1366 ..self
1367 }
1368 }
1369
1370 pub fn nav_history(self, nav_history: bool) -> Self {
1371 Self {
1372 nav_history: Some(nav_history),
1373 ..self
1374 }
1375 }
1376}
1377
1378struct DeferredSelectionEffectsState {
1379 changed: bool,
1380 effects: SelectionEffects,
1381 old_cursor_position: Anchor,
1382 history_entry: SelectionHistoryEntry,
1383}
1384
1385#[derive(Default)]
1386struct SelectionHistory {
1387 #[allow(clippy::type_complexity)]
1388 selections_by_transaction:
1389 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1390 mode: SelectionHistoryMode,
1391 undo_stack: VecDeque<SelectionHistoryEntry>,
1392 redo_stack: VecDeque<SelectionHistoryEntry>,
1393}
1394
1395impl SelectionHistory {
1396 #[track_caller]
1397 fn insert_transaction(
1398 &mut self,
1399 transaction_id: TransactionId,
1400 selections: Arc<[Selection<Anchor>]>,
1401 ) {
1402 if selections.is_empty() {
1403 log::error!(
1404 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1405 std::panic::Location::caller()
1406 );
1407 return;
1408 }
1409 self.selections_by_transaction
1410 .insert(transaction_id, (selections, None));
1411 }
1412
1413 #[allow(clippy::type_complexity)]
1414 fn transaction(
1415 &self,
1416 transaction_id: TransactionId,
1417 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1418 self.selections_by_transaction.get(&transaction_id)
1419 }
1420
1421 #[allow(clippy::type_complexity)]
1422 fn transaction_mut(
1423 &mut self,
1424 transaction_id: TransactionId,
1425 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1426 self.selections_by_transaction.get_mut(&transaction_id)
1427 }
1428
1429 fn push(&mut self, entry: SelectionHistoryEntry) {
1430 if !entry.selections.is_empty() {
1431 match self.mode {
1432 SelectionHistoryMode::Normal => {
1433 self.push_undo(entry);
1434 self.redo_stack.clear();
1435 }
1436 SelectionHistoryMode::Undoing => self.push_redo(entry),
1437 SelectionHistoryMode::Redoing => self.push_undo(entry),
1438 SelectionHistoryMode::Skipping => {}
1439 }
1440 }
1441 }
1442
1443 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1444 if self
1445 .undo_stack
1446 .back()
1447 .is_none_or(|e| e.selections != entry.selections)
1448 {
1449 self.undo_stack.push_back(entry);
1450 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1451 self.undo_stack.pop_front();
1452 }
1453 }
1454 }
1455
1456 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1457 if self
1458 .redo_stack
1459 .back()
1460 .is_none_or(|e| e.selections != entry.selections)
1461 {
1462 self.redo_stack.push_back(entry);
1463 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1464 self.redo_stack.pop_front();
1465 }
1466 }
1467 }
1468}
1469
1470#[derive(Clone, Copy)]
1471pub struct RowHighlightOptions {
1472 pub autoscroll: bool,
1473 pub include_gutter: bool,
1474}
1475
1476impl Default for RowHighlightOptions {
1477 fn default() -> Self {
1478 Self {
1479 autoscroll: Default::default(),
1480 include_gutter: true,
1481 }
1482 }
1483}
1484
1485struct RowHighlight {
1486 index: usize,
1487 range: Range<Anchor>,
1488 color: Hsla,
1489 options: RowHighlightOptions,
1490 type_id: TypeId,
1491}
1492
1493#[derive(Clone, Debug)]
1494struct AddSelectionsState {
1495 groups: Vec<AddSelectionsGroup>,
1496}
1497
1498#[derive(Clone, Debug)]
1499struct AddSelectionsGroup {
1500 above: bool,
1501 stack: Vec<usize>,
1502}
1503
1504#[derive(Clone)]
1505struct SelectNextState {
1506 query: AhoCorasick,
1507 wordwise: bool,
1508 done: bool,
1509}
1510
1511impl std::fmt::Debug for SelectNextState {
1512 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1513 f.debug_struct(std::any::type_name::<Self>())
1514 .field("wordwise", &self.wordwise)
1515 .field("done", &self.done)
1516 .finish()
1517 }
1518}
1519
1520#[derive(Debug)]
1521struct AutocloseRegion {
1522 selection_id: usize,
1523 range: Range<Anchor>,
1524 pair: BracketPair,
1525}
1526
1527#[derive(Debug)]
1528struct SnippetState {
1529 ranges: Vec<Vec<Range<Anchor>>>,
1530 active_index: usize,
1531 choices: Vec<Option<Vec<String>>>,
1532}
1533
1534#[doc(hidden)]
1535pub struct RenameState {
1536 pub range: Range<Anchor>,
1537 pub old_name: Arc<str>,
1538 pub editor: Entity<Editor>,
1539 block_id: CustomBlockId,
1540}
1541
1542struct InvalidationStack<T>(Vec<T>);
1543
1544struct RegisteredEditPredictionProvider {
1545 provider: Arc<dyn EditPredictionProviderHandle>,
1546 _subscription: Subscription,
1547}
1548
1549#[derive(Debug, PartialEq, Eq)]
1550pub struct ActiveDiagnosticGroup {
1551 pub active_range: Range<Anchor>,
1552 pub active_message: String,
1553 pub group_id: usize,
1554 pub blocks: HashSet<CustomBlockId>,
1555}
1556
1557#[derive(Debug, PartialEq, Eq)]
1558
1559pub(crate) enum ActiveDiagnostic {
1560 None,
1561 All,
1562 Group(ActiveDiagnosticGroup),
1563}
1564
1565#[derive(Serialize, Deserialize, Clone, Debug)]
1566pub struct ClipboardSelection {
1567 /// The number of bytes in this selection.
1568 pub len: usize,
1569 /// Whether this was a full-line selection.
1570 pub is_entire_line: bool,
1571 /// The indentation of the first line when this content was originally copied.
1572 pub first_line_indent: u32,
1573}
1574
1575// selections, scroll behavior, was newest selection reversed
1576type SelectSyntaxNodeHistoryState = (
1577 Box<[Selection<usize>]>,
1578 SelectSyntaxNodeScrollBehavior,
1579 bool,
1580);
1581
1582#[derive(Default)]
1583struct SelectSyntaxNodeHistory {
1584 stack: Vec<SelectSyntaxNodeHistoryState>,
1585 // disable temporarily to allow changing selections without losing the stack
1586 pub disable_clearing: bool,
1587}
1588
1589impl SelectSyntaxNodeHistory {
1590 pub fn try_clear(&mut self) {
1591 if !self.disable_clearing {
1592 self.stack.clear();
1593 }
1594 }
1595
1596 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1597 self.stack.push(selection);
1598 }
1599
1600 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1601 self.stack.pop()
1602 }
1603}
1604
1605enum SelectSyntaxNodeScrollBehavior {
1606 CursorTop,
1607 FitSelection,
1608 CursorBottom,
1609}
1610
1611#[derive(Debug)]
1612pub(crate) struct NavigationData {
1613 cursor_anchor: Anchor,
1614 cursor_position: Point,
1615 scroll_anchor: ScrollAnchor,
1616 scroll_top_row: u32,
1617}
1618
1619#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1620pub enum GotoDefinitionKind {
1621 Symbol,
1622 Declaration,
1623 Type,
1624 Implementation,
1625}
1626
1627pub enum FormatTarget {
1628 Buffers(HashSet<Entity<Buffer>>),
1629 Ranges(Vec<Range<MultiBufferPoint>>),
1630}
1631
1632pub(crate) struct FocusedBlock {
1633 id: BlockId,
1634 focus_handle: WeakFocusHandle,
1635}
1636
1637#[derive(Clone)]
1638enum JumpData {
1639 MultiBufferRow {
1640 row: MultiBufferRow,
1641 line_offset_from_top: u32,
1642 },
1643 MultiBufferPoint {
1644 excerpt_id: ExcerptId,
1645 position: Point,
1646 anchor: text::Anchor,
1647 line_offset_from_top: u32,
1648 },
1649}
1650
1651pub enum MultibufferSelectionMode {
1652 First,
1653 All,
1654}
1655
1656#[derive(Clone, Copy, Debug, Default)]
1657pub struct RewrapOptions {
1658 pub override_language_settings: bool,
1659 pub preserve_existing_whitespace: bool,
1660}
1661
1662impl Editor {
1663 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1664 let buffer = cx.new(|cx| Buffer::local("", cx));
1665 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1666 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1667 }
1668
1669 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1670 let buffer = cx.new(|cx| Buffer::local("", cx));
1671 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1672 Self::new(EditorMode::full(), buffer, None, window, cx)
1673 }
1674
1675 pub fn auto_height(
1676 min_lines: usize,
1677 max_lines: usize,
1678 window: &mut Window,
1679 cx: &mut Context<Self>,
1680 ) -> Self {
1681 let buffer = cx.new(|cx| Buffer::local("", cx));
1682 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1683 Self::new(
1684 EditorMode::AutoHeight {
1685 min_lines,
1686 max_lines: Some(max_lines),
1687 },
1688 buffer,
1689 None,
1690 window,
1691 cx,
1692 )
1693 }
1694
1695 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1696 /// The editor grows as tall as needed to fit its content.
1697 pub fn auto_height_unbounded(
1698 min_lines: usize,
1699 window: &mut Window,
1700 cx: &mut Context<Self>,
1701 ) -> Self {
1702 let buffer = cx.new(|cx| Buffer::local("", cx));
1703 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1704 Self::new(
1705 EditorMode::AutoHeight {
1706 min_lines,
1707 max_lines: None,
1708 },
1709 buffer,
1710 None,
1711 window,
1712 cx,
1713 )
1714 }
1715
1716 pub fn for_buffer(
1717 buffer: Entity<Buffer>,
1718 project: Option<Entity<Project>>,
1719 window: &mut Window,
1720 cx: &mut Context<Self>,
1721 ) -> Self {
1722 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1723 Self::new(EditorMode::full(), buffer, project, window, cx)
1724 }
1725
1726 pub fn for_multibuffer(
1727 buffer: Entity<MultiBuffer>,
1728 project: Option<Entity<Project>>,
1729 window: &mut Window,
1730 cx: &mut Context<Self>,
1731 ) -> Self {
1732 Self::new(EditorMode::full(), buffer, project, window, cx)
1733 }
1734
1735 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1736 let mut clone = Self::new(
1737 self.mode.clone(),
1738 self.buffer.clone(),
1739 self.project.clone(),
1740 window,
1741 cx,
1742 );
1743 self.display_map.update(cx, |display_map, cx| {
1744 let snapshot = display_map.snapshot(cx);
1745 clone.display_map.update(cx, |display_map, cx| {
1746 display_map.set_state(&snapshot, cx);
1747 });
1748 });
1749 clone.folds_did_change(cx);
1750 clone.selections.clone_state(&self.selections);
1751 clone.scroll_manager.clone_state(&self.scroll_manager);
1752 clone.searchable = self.searchable;
1753 clone.read_only = self.read_only;
1754 clone
1755 }
1756
1757 pub fn new(
1758 mode: EditorMode,
1759 buffer: Entity<MultiBuffer>,
1760 project: Option<Entity<Project>>,
1761 window: &mut Window,
1762 cx: &mut Context<Self>,
1763 ) -> Self {
1764 Editor::new_internal(mode, buffer, project, None, window, cx)
1765 }
1766
1767 pub fn sticky_headers(&self, cx: &App) -> Option<Vec<OutlineItem<Anchor>>> {
1768 let multi_buffer = self.buffer().read(cx);
1769 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1770 let multi_buffer_visible_start = self
1771 .scroll_manager
1772 .anchor()
1773 .anchor
1774 .to_point(&multi_buffer_snapshot);
1775 let max_row = multi_buffer_snapshot.max_point().row;
1776
1777 let start_row = (multi_buffer_visible_start.row).min(max_row);
1778 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1779
1780 if let Some((excerpt_id, buffer_id, buffer)) = multi_buffer.read(cx).as_singleton() {
1781 let outline_items = buffer
1782 .outline_items_containing(
1783 Point::new(start_row, 0)..Point::new(end_row, 0),
1784 true,
1785 self.style().map(|style| style.syntax.as_ref()),
1786 )
1787 .into_iter()
1788 .map(|outline_item| OutlineItem {
1789 depth: outline_item.depth,
1790 range: Anchor::range_in_buffer(*excerpt_id, buffer_id, outline_item.range),
1791 source_range_for_text: Anchor::range_in_buffer(
1792 *excerpt_id,
1793 buffer_id,
1794 outline_item.source_range_for_text,
1795 ),
1796 text: outline_item.text,
1797 highlight_ranges: outline_item.highlight_ranges,
1798 name_ranges: outline_item.name_ranges,
1799 body_range: outline_item
1800 .body_range
1801 .map(|range| Anchor::range_in_buffer(*excerpt_id, buffer_id, range)),
1802 annotation_range: outline_item
1803 .annotation_range
1804 .map(|range| Anchor::range_in_buffer(*excerpt_id, buffer_id, range)),
1805 });
1806 return Some(outline_items.collect());
1807 }
1808
1809 None
1810 }
1811
1812 fn new_internal(
1813 mode: EditorMode,
1814 multi_buffer: Entity<MultiBuffer>,
1815 project: Option<Entity<Project>>,
1816 display_map: Option<Entity<DisplayMap>>,
1817 window: &mut Window,
1818 cx: &mut Context<Self>,
1819 ) -> Self {
1820 debug_assert!(
1821 display_map.is_none() || mode.is_minimap(),
1822 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1823 );
1824
1825 let full_mode = mode.is_full();
1826 let is_minimap = mode.is_minimap();
1827 let diagnostics_max_severity = if full_mode {
1828 EditorSettings::get_global(cx)
1829 .diagnostics_max_severity
1830 .unwrap_or(DiagnosticSeverity::Hint)
1831 } else {
1832 DiagnosticSeverity::Off
1833 };
1834 let style = window.text_style();
1835 let font_size = style.font_size.to_pixels(window.rem_size());
1836 let editor = cx.entity().downgrade();
1837 let fold_placeholder = FoldPlaceholder {
1838 constrain_width: false,
1839 render: Arc::new(move |fold_id, fold_range, cx| {
1840 let editor = editor.clone();
1841 div()
1842 .id(fold_id)
1843 .bg(cx.theme().colors().ghost_element_background)
1844 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1845 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1846 .rounded_xs()
1847 .size_full()
1848 .cursor_pointer()
1849 .child("⋯")
1850 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1851 .on_click(move |_, _window, cx| {
1852 editor
1853 .update(cx, |editor, cx| {
1854 editor.unfold_ranges(
1855 &[fold_range.start..fold_range.end],
1856 true,
1857 false,
1858 cx,
1859 );
1860 cx.stop_propagation();
1861 })
1862 .ok();
1863 })
1864 .into_any()
1865 }),
1866 merge_adjacent: true,
1867 ..FoldPlaceholder::default()
1868 };
1869 let display_map = display_map.unwrap_or_else(|| {
1870 cx.new(|cx| {
1871 DisplayMap::new(
1872 multi_buffer.clone(),
1873 style.font(),
1874 font_size,
1875 None,
1876 FILE_HEADER_HEIGHT,
1877 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1878 fold_placeholder,
1879 diagnostics_max_severity,
1880 cx,
1881 )
1882 })
1883 });
1884
1885 let selections = SelectionsCollection::new();
1886
1887 let blink_manager = cx.new(|cx| {
1888 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1889 if is_minimap {
1890 blink_manager.disable(cx);
1891 }
1892 blink_manager
1893 });
1894
1895 let soft_wrap_mode_override =
1896 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1897
1898 let mut project_subscriptions = Vec::new();
1899 if full_mode && let Some(project) = project.as_ref() {
1900 project_subscriptions.push(cx.subscribe_in(
1901 project,
1902 window,
1903 |editor, _, event, window, cx| match event {
1904 project::Event::RefreshCodeLens => {
1905 // we always query lens with actions, without storing them, always refreshing them
1906 }
1907 project::Event::RefreshInlayHints {
1908 server_id,
1909 request_id,
1910 } => {
1911 editor.refresh_inlay_hints(
1912 InlayHintRefreshReason::RefreshRequested {
1913 server_id: *server_id,
1914 request_id: *request_id,
1915 },
1916 cx,
1917 );
1918 }
1919 project::Event::LanguageServerRemoved(..) => {
1920 if editor.tasks_update_task.is_none() {
1921 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1922 }
1923 editor.registered_buffers.clear();
1924 editor.register_visible_buffers(cx);
1925 }
1926 project::Event::LanguageServerAdded(..) => {
1927 if editor.tasks_update_task.is_none() {
1928 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1929 }
1930 }
1931 project::Event::SnippetEdit(id, snippet_edits) => {
1932 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1933 let focus_handle = editor.focus_handle(cx);
1934 if focus_handle.is_focused(window) {
1935 let snapshot = buffer.read(cx).snapshot();
1936 for (range, snippet) in snippet_edits {
1937 let editor_range =
1938 language::range_from_lsp(*range).to_offset(&snapshot);
1939 editor
1940 .insert_snippet(
1941 &[editor_range],
1942 snippet.clone(),
1943 window,
1944 cx,
1945 )
1946 .ok();
1947 }
1948 }
1949 }
1950 }
1951 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1952 let buffer_id = *buffer_id;
1953 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1954 editor.register_buffer(buffer_id, cx);
1955 editor.update_lsp_data(Some(buffer_id), window, cx);
1956 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1957 refresh_linked_ranges(editor, window, cx);
1958 editor.refresh_code_actions(window, cx);
1959 editor.refresh_document_highlights(cx);
1960 }
1961 }
1962
1963 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
1964 let Some(workspace) = editor.workspace() else {
1965 return;
1966 };
1967 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1968 else {
1969 return;
1970 };
1971
1972 if active_editor.entity_id() == cx.entity_id() {
1973 let entity_id = cx.entity_id();
1974 workspace.update(cx, |this, cx| {
1975 this.panes_mut()
1976 .iter_mut()
1977 .filter(|pane| pane.entity_id() != entity_id)
1978 .for_each(|p| {
1979 p.update(cx, |pane, _| {
1980 pane.nav_history_mut().rename_item(
1981 entity_id,
1982 project_path.clone(),
1983 abs_path.clone().into(),
1984 );
1985 })
1986 });
1987 });
1988 let edited_buffers_already_open = {
1989 let other_editors: Vec<Entity<Editor>> = workspace
1990 .read(cx)
1991 .panes()
1992 .iter()
1993 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1994 .filter(|editor| editor.entity_id() != cx.entity_id())
1995 .collect();
1996
1997 transaction.0.keys().all(|buffer| {
1998 other_editors.iter().any(|editor| {
1999 let multi_buffer = editor.read(cx).buffer();
2000 multi_buffer.read(cx).is_singleton()
2001 && multi_buffer.read(cx).as_singleton().map_or(
2002 false,
2003 |singleton| {
2004 singleton.entity_id() == buffer.entity_id()
2005 },
2006 )
2007 })
2008 })
2009 };
2010 if !edited_buffers_already_open {
2011 let workspace = workspace.downgrade();
2012 let transaction = transaction.clone();
2013 cx.defer_in(window, move |_, window, cx| {
2014 cx.spawn_in(window, async move |editor, cx| {
2015 Self::open_project_transaction(
2016 &editor,
2017 workspace,
2018 transaction,
2019 "Rename".to_string(),
2020 cx,
2021 )
2022 .await
2023 .ok()
2024 })
2025 .detach();
2026 });
2027 }
2028 }
2029 }
2030
2031 _ => {}
2032 },
2033 ));
2034 if let Some(task_inventory) = project
2035 .read(cx)
2036 .task_store()
2037 .read(cx)
2038 .task_inventory()
2039 .cloned()
2040 {
2041 project_subscriptions.push(cx.observe_in(
2042 &task_inventory,
2043 window,
2044 |editor, _, window, cx| {
2045 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2046 },
2047 ));
2048 };
2049
2050 project_subscriptions.push(cx.subscribe_in(
2051 &project.read(cx).breakpoint_store(),
2052 window,
2053 |editor, _, event, window, cx| match event {
2054 BreakpointStoreEvent::ClearDebugLines => {
2055 editor.clear_row_highlights::<ActiveDebugLine>();
2056 editor.refresh_inline_values(cx);
2057 }
2058 BreakpointStoreEvent::SetDebugLine => {
2059 if editor.go_to_active_debug_line(window, cx) {
2060 cx.stop_propagation();
2061 }
2062
2063 editor.refresh_inline_values(cx);
2064 }
2065 _ => {}
2066 },
2067 ));
2068 let git_store = project.read(cx).git_store().clone();
2069 let project = project.clone();
2070 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2071 if let GitStoreEvent::RepositoryAdded = event {
2072 this.load_diff_task = Some(
2073 update_uncommitted_diff_for_buffer(
2074 cx.entity(),
2075 &project,
2076 this.buffer.read(cx).all_buffers(),
2077 this.buffer.clone(),
2078 cx,
2079 )
2080 .shared(),
2081 );
2082 }
2083 }));
2084 }
2085
2086 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2087
2088 let inlay_hint_settings =
2089 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2090 let focus_handle = cx.focus_handle();
2091 if !is_minimap {
2092 cx.on_focus(&focus_handle, window, Self::handle_focus)
2093 .detach();
2094 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2095 .detach();
2096 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2097 .detach();
2098 cx.on_blur(&focus_handle, window, Self::handle_blur)
2099 .detach();
2100 cx.observe_pending_input(window, Self::observe_pending_input)
2101 .detach();
2102 }
2103
2104 let show_indent_guides =
2105 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2106 Some(false)
2107 } else {
2108 None
2109 };
2110
2111 let breakpoint_store = match (&mode, project.as_ref()) {
2112 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2113 _ => None,
2114 };
2115
2116 let mut code_action_providers = Vec::new();
2117 let mut load_uncommitted_diff = None;
2118 if let Some(project) = project.clone() {
2119 load_uncommitted_diff = Some(
2120 update_uncommitted_diff_for_buffer(
2121 cx.entity(),
2122 &project,
2123 multi_buffer.read(cx).all_buffers(),
2124 multi_buffer.clone(),
2125 cx,
2126 )
2127 .shared(),
2128 );
2129 code_action_providers.push(Rc::new(project) as Rc<_>);
2130 }
2131
2132 let mut editor = Self {
2133 focus_handle,
2134 show_cursor_when_unfocused: false,
2135 last_focused_descendant: None,
2136 buffer: multi_buffer.clone(),
2137 display_map: display_map.clone(),
2138 placeholder_display_map: None,
2139 selections,
2140 scroll_manager: ScrollManager::new(cx),
2141 columnar_selection_state: None,
2142 add_selections_state: None,
2143 select_next_state: None,
2144 select_prev_state: None,
2145 selection_history: SelectionHistory::default(),
2146 defer_selection_effects: false,
2147 deferred_selection_effects_state: None,
2148 autoclose_regions: Vec::new(),
2149 snippet_stack: InvalidationStack::default(),
2150 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2151 ime_transaction: None,
2152 active_diagnostics: ActiveDiagnostic::None,
2153 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2154 inline_diagnostics_update: Task::ready(()),
2155 inline_diagnostics: Vec::new(),
2156 soft_wrap_mode_override,
2157 diagnostics_max_severity,
2158 hard_wrap: None,
2159 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2160 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2161 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2162 project,
2163 blink_manager: blink_manager.clone(),
2164 show_local_selections: true,
2165 show_scrollbars: ScrollbarAxes {
2166 horizontal: full_mode,
2167 vertical: full_mode,
2168 },
2169 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2170 offset_content: !matches!(mode, EditorMode::SingleLine),
2171 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2172 show_gutter: full_mode,
2173 show_line_numbers: (!full_mode).then_some(false),
2174 use_relative_line_numbers: None,
2175 disable_expand_excerpt_buttons: !full_mode,
2176 show_git_diff_gutter: None,
2177 show_code_actions: None,
2178 show_runnables: None,
2179 show_breakpoints: None,
2180 show_wrap_guides: None,
2181 show_indent_guides,
2182 highlight_order: 0,
2183 highlighted_rows: HashMap::default(),
2184 background_highlights: HashMap::default(),
2185 gutter_highlights: HashMap::default(),
2186 scrollbar_marker_state: ScrollbarMarkerState::default(),
2187 active_indent_guides_state: ActiveIndentGuidesState::default(),
2188 nav_history: None,
2189 context_menu: RefCell::new(None),
2190 context_menu_options: None,
2191 mouse_context_menu: None,
2192 completion_tasks: Vec::new(),
2193 inline_blame_popover: None,
2194 inline_blame_popover_show_task: None,
2195 signature_help_state: SignatureHelpState::default(),
2196 auto_signature_help: None,
2197 find_all_references_task_sources: Vec::new(),
2198 next_completion_id: 0,
2199 next_inlay_id: 0,
2200 code_action_providers,
2201 available_code_actions: None,
2202 code_actions_task: None,
2203 quick_selection_highlight_task: None,
2204 debounced_selection_highlight_task: None,
2205 document_highlights_task: None,
2206 linked_editing_range_task: None,
2207 pending_rename: None,
2208 searchable: !is_minimap,
2209 cursor_shape: EditorSettings::get_global(cx)
2210 .cursor_shape
2211 .unwrap_or_default(),
2212 current_line_highlight: None,
2213 autoindent_mode: Some(AutoindentMode::EachLine),
2214
2215 workspace: None,
2216 input_enabled: !is_minimap,
2217 use_modal_editing: full_mode,
2218 read_only: is_minimap,
2219 use_autoclose: true,
2220 use_auto_surround: true,
2221 auto_replace_emoji_shortcode: false,
2222 jsx_tag_auto_close_enabled_in_any_buffer: false,
2223 leader_id: None,
2224 remote_id: None,
2225 hover_state: HoverState::default(),
2226 pending_mouse_down: None,
2227 hovered_link_state: None,
2228 edit_prediction_provider: None,
2229 active_edit_prediction: None,
2230 stale_edit_prediction_in_menu: None,
2231 edit_prediction_preview: EditPredictionPreview::Inactive {
2232 released_too_fast: false,
2233 },
2234 inline_diagnostics_enabled: full_mode,
2235 diagnostics_enabled: full_mode,
2236 word_completions_enabled: full_mode,
2237 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2238 gutter_hovered: false,
2239 pixel_position_of_newest_cursor: None,
2240 last_bounds: None,
2241 last_position_map: None,
2242 expect_bounds_change: None,
2243 gutter_dimensions: GutterDimensions::default(),
2244 style: None,
2245 show_cursor_names: false,
2246 hovered_cursors: HashMap::default(),
2247 next_editor_action_id: EditorActionId::default(),
2248 editor_actions: Rc::default(),
2249 edit_predictions_hidden_for_vim_mode: false,
2250 show_edit_predictions_override: None,
2251 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2252 edit_prediction_settings: EditPredictionSettings::Disabled,
2253 edit_prediction_indent_conflict: false,
2254 edit_prediction_requires_modifier_in_indent_conflict: true,
2255 custom_context_menu: None,
2256 show_git_blame_gutter: false,
2257 show_git_blame_inline: false,
2258 show_selection_menu: None,
2259 show_git_blame_inline_delay_task: None,
2260 git_blame_inline_enabled: full_mode
2261 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2262 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2263 buffer_serialization: is_minimap.not().then(|| {
2264 BufferSerialization::new(
2265 ProjectSettings::get_global(cx)
2266 .session
2267 .restore_unsaved_buffers,
2268 )
2269 }),
2270 blame: None,
2271 blame_subscription: None,
2272 tasks: BTreeMap::default(),
2273
2274 breakpoint_store,
2275 gutter_breakpoint_indicator: (None, None),
2276 hovered_diff_hunk_row: None,
2277 _subscriptions: (!is_minimap)
2278 .then(|| {
2279 vec![
2280 cx.observe(&multi_buffer, Self::on_buffer_changed),
2281 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2282 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2283 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2284 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2285 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2286 cx.observe_window_activation(window, |editor, window, cx| {
2287 let active = window.is_window_active();
2288 editor.blink_manager.update(cx, |blink_manager, cx| {
2289 if active {
2290 blink_manager.enable(cx);
2291 } else {
2292 blink_manager.disable(cx);
2293 }
2294 });
2295 if active {
2296 editor.show_mouse_cursor(cx);
2297 }
2298 }),
2299 ]
2300 })
2301 .unwrap_or_default(),
2302 tasks_update_task: None,
2303 pull_diagnostics_task: Task::ready(()),
2304 colors: None,
2305 refresh_colors_task: Task::ready(()),
2306 inlay_hints: None,
2307 next_color_inlay_id: 0,
2308 post_scroll_update: Task::ready(()),
2309 linked_edit_ranges: Default::default(),
2310 in_project_search: false,
2311 previous_search_ranges: None,
2312 breadcrumb_header: None,
2313 focused_block: None,
2314 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2315 addons: HashMap::default(),
2316 registered_buffers: HashMap::default(),
2317 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2318 selection_mark_mode: false,
2319 toggle_fold_multiple_buffers: Task::ready(()),
2320 serialize_selections: Task::ready(()),
2321 serialize_folds: Task::ready(()),
2322 text_style_refinement: None,
2323 load_diff_task: load_uncommitted_diff,
2324 temporary_diff_override: false,
2325 mouse_cursor_hidden: false,
2326 minimap: None,
2327 hide_mouse_mode: EditorSettings::get_global(cx)
2328 .hide_mouse
2329 .unwrap_or_default(),
2330 change_list: ChangeList::new(),
2331 mode,
2332 selection_drag_state: SelectionDragState::None,
2333 folding_newlines: Task::ready(()),
2334 lookup_key: None,
2335 select_next_is_case_sensitive: None,
2336 };
2337
2338 if is_minimap {
2339 return editor;
2340 }
2341
2342 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2343 editor
2344 ._subscriptions
2345 .push(cx.observe(breakpoints, |_, _, cx| {
2346 cx.notify();
2347 }));
2348 }
2349 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2350 editor._subscriptions.extend(project_subscriptions);
2351
2352 editor._subscriptions.push(cx.subscribe_in(
2353 &cx.entity(),
2354 window,
2355 |editor, _, e: &EditorEvent, window, cx| match e {
2356 EditorEvent::ScrollPositionChanged { local, .. } => {
2357 if *local {
2358 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2359 editor.inline_blame_popover.take();
2360 let new_anchor = editor.scroll_manager.anchor();
2361 let snapshot = editor.snapshot(window, cx);
2362 editor.update_restoration_data(cx, move |data| {
2363 data.scroll_position = (
2364 new_anchor.top_row(snapshot.buffer_snapshot()),
2365 new_anchor.offset,
2366 );
2367 });
2368
2369 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2370 cx.background_executor()
2371 .timer(Duration::from_millis(50))
2372 .await;
2373 editor
2374 .update_in(cx, |editor, window, cx| {
2375 editor.register_visible_buffers(cx);
2376 editor.refresh_colors_for_visible_range(None, window, cx);
2377 editor.refresh_inlay_hints(
2378 InlayHintRefreshReason::NewLinesShown,
2379 cx,
2380 );
2381 })
2382 .ok();
2383 });
2384 }
2385 }
2386 EditorEvent::Edited { .. } => {
2387 if vim_flavor(cx).is_none() {
2388 let display_map = editor.display_snapshot(cx);
2389 let selections = editor.selections.all_adjusted_display(&display_map);
2390 let pop_state = editor
2391 .change_list
2392 .last()
2393 .map(|previous| {
2394 previous.len() == selections.len()
2395 && previous.iter().enumerate().all(|(ix, p)| {
2396 p.to_display_point(&display_map).row()
2397 == selections[ix].head().row()
2398 })
2399 })
2400 .unwrap_or(false);
2401 let new_positions = selections
2402 .into_iter()
2403 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2404 .collect();
2405 editor
2406 .change_list
2407 .push_to_change_list(pop_state, new_positions);
2408 }
2409 }
2410 _ => (),
2411 },
2412 ));
2413
2414 if let Some(dap_store) = editor
2415 .project
2416 .as_ref()
2417 .map(|project| project.read(cx).dap_store())
2418 {
2419 let weak_editor = cx.weak_entity();
2420
2421 editor
2422 ._subscriptions
2423 .push(
2424 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2425 let session_entity = cx.entity();
2426 weak_editor
2427 .update(cx, |editor, cx| {
2428 editor._subscriptions.push(
2429 cx.subscribe(&session_entity, Self::on_debug_session_event),
2430 );
2431 })
2432 .ok();
2433 }),
2434 );
2435
2436 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2437 editor
2438 ._subscriptions
2439 .push(cx.subscribe(&session, Self::on_debug_session_event));
2440 }
2441 }
2442
2443 // skip adding the initial selection to selection history
2444 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2445 editor.end_selection(window, cx);
2446 editor.selection_history.mode = SelectionHistoryMode::Normal;
2447
2448 editor.scroll_manager.show_scrollbars(window, cx);
2449 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2450
2451 if full_mode {
2452 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2453 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2454
2455 if editor.git_blame_inline_enabled {
2456 editor.start_git_blame_inline(false, window, cx);
2457 }
2458
2459 editor.go_to_active_debug_line(window, cx);
2460
2461 editor.minimap =
2462 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2463 editor.colors = Some(LspColorData::new(cx));
2464 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2465
2466 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2467 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2468 }
2469 editor.update_lsp_data(None, window, cx);
2470 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2471 }
2472
2473 editor
2474 }
2475
2476 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2477 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2478 }
2479
2480 pub fn deploy_mouse_context_menu(
2481 &mut self,
2482 position: gpui::Point<Pixels>,
2483 context_menu: Entity<ContextMenu>,
2484 window: &mut Window,
2485 cx: &mut Context<Self>,
2486 ) {
2487 self.mouse_context_menu = Some(MouseContextMenu::new(
2488 self,
2489 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2490 context_menu,
2491 window,
2492 cx,
2493 ));
2494 }
2495
2496 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2497 self.mouse_context_menu
2498 .as_ref()
2499 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2500 }
2501
2502 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2503 if self
2504 .selections
2505 .pending_anchor()
2506 .is_some_and(|pending_selection| {
2507 let snapshot = self.buffer().read(cx).snapshot(cx);
2508 pending_selection.range().includes(range, &snapshot)
2509 })
2510 {
2511 return true;
2512 }
2513
2514 self.selections
2515 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2516 .into_iter()
2517 .any(|selection| {
2518 // This is needed to cover a corner case, if we just check for an existing
2519 // selection in the fold range, having a cursor at the start of the fold
2520 // marks it as selected. Non-empty selections don't cause this.
2521 let length = selection.end - selection.start;
2522 length > 0
2523 })
2524 }
2525
2526 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2527 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2528 }
2529
2530 fn key_context_internal(
2531 &self,
2532 has_active_edit_prediction: bool,
2533 window: &mut Window,
2534 cx: &mut App,
2535 ) -> KeyContext {
2536 let mut key_context = KeyContext::new_with_defaults();
2537 key_context.add("Editor");
2538 let mode = match self.mode {
2539 EditorMode::SingleLine => "single_line",
2540 EditorMode::AutoHeight { .. } => "auto_height",
2541 EditorMode::Minimap { .. } => "minimap",
2542 EditorMode::Full { .. } => "full",
2543 };
2544
2545 if EditorSettings::jupyter_enabled(cx) {
2546 key_context.add("jupyter");
2547 }
2548
2549 key_context.set("mode", mode);
2550 if self.pending_rename.is_some() {
2551 key_context.add("renaming");
2552 }
2553
2554 if let Some(snippet_stack) = self.snippet_stack.last() {
2555 key_context.add("in_snippet");
2556
2557 if snippet_stack.active_index > 0 {
2558 key_context.add("has_previous_tabstop");
2559 }
2560
2561 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2562 key_context.add("has_next_tabstop");
2563 }
2564 }
2565
2566 match self.context_menu.borrow().as_ref() {
2567 Some(CodeContextMenu::Completions(menu)) => {
2568 if menu.visible() {
2569 key_context.add("menu");
2570 key_context.add("showing_completions");
2571 }
2572 }
2573 Some(CodeContextMenu::CodeActions(menu)) => {
2574 if menu.visible() {
2575 key_context.add("menu");
2576 key_context.add("showing_code_actions")
2577 }
2578 }
2579 None => {}
2580 }
2581
2582 if self.signature_help_state.has_multiple_signatures() {
2583 key_context.add("showing_signature_help");
2584 }
2585
2586 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2587 if !self.focus_handle(cx).contains_focused(window, cx)
2588 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2589 {
2590 for addon in self.addons.values() {
2591 addon.extend_key_context(&mut key_context, cx)
2592 }
2593 }
2594
2595 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2596 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2597 Some(
2598 file.full_path(cx)
2599 .extension()?
2600 .to_string_lossy()
2601 .into_owned(),
2602 )
2603 }) {
2604 key_context.set("extension", extension);
2605 }
2606 } else {
2607 key_context.add("multibuffer");
2608 }
2609
2610 if has_active_edit_prediction {
2611 if self.edit_prediction_in_conflict() {
2612 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2613 } else {
2614 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2615 key_context.add("copilot_suggestion");
2616 }
2617 }
2618
2619 if self.selection_mark_mode {
2620 key_context.add("selection_mode");
2621 }
2622
2623 let disjoint = self.selections.disjoint_anchors();
2624 let snapshot = self.snapshot(window, cx);
2625 let snapshot = snapshot.buffer_snapshot();
2626 if self.mode == EditorMode::SingleLine
2627 && let [selection] = disjoint
2628 && selection.start == selection.end
2629 && selection.end.to_offset(snapshot) == snapshot.len()
2630 {
2631 key_context.add("end_of_input");
2632 }
2633
2634 key_context
2635 }
2636
2637 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2638 self.last_bounds.as_ref()
2639 }
2640
2641 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2642 if self.mouse_cursor_hidden {
2643 self.mouse_cursor_hidden = false;
2644 cx.notify();
2645 }
2646 }
2647
2648 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2649 let hide_mouse_cursor = match origin {
2650 HideMouseCursorOrigin::TypingAction => {
2651 matches!(
2652 self.hide_mouse_mode,
2653 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2654 )
2655 }
2656 HideMouseCursorOrigin::MovementAction => {
2657 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2658 }
2659 };
2660 if self.mouse_cursor_hidden != hide_mouse_cursor {
2661 self.mouse_cursor_hidden = hide_mouse_cursor;
2662 cx.notify();
2663 }
2664 }
2665
2666 pub fn edit_prediction_in_conflict(&self) -> bool {
2667 if !self.show_edit_predictions_in_menu() {
2668 return false;
2669 }
2670
2671 let showing_completions = self
2672 .context_menu
2673 .borrow()
2674 .as_ref()
2675 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2676
2677 showing_completions
2678 || self.edit_prediction_requires_modifier()
2679 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2680 // bindings to insert tab characters.
2681 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2682 }
2683
2684 pub fn accept_edit_prediction_keybind(
2685 &self,
2686 accept_partial: bool,
2687 window: &mut Window,
2688 cx: &mut App,
2689 ) -> AcceptEditPredictionBinding {
2690 let key_context = self.key_context_internal(true, window, cx);
2691 let in_conflict = self.edit_prediction_in_conflict();
2692
2693 let bindings = if accept_partial {
2694 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2695 } else {
2696 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2697 };
2698
2699 // TODO: if the binding contains multiple keystrokes, display all of them, not
2700 // just the first one.
2701 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2702 !in_conflict
2703 || binding
2704 .keystrokes()
2705 .first()
2706 .is_some_and(|keystroke| keystroke.modifiers().modified())
2707 }))
2708 }
2709
2710 pub fn new_file(
2711 workspace: &mut Workspace,
2712 _: &workspace::NewFile,
2713 window: &mut Window,
2714 cx: &mut Context<Workspace>,
2715 ) {
2716 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2717 "Failed to create buffer",
2718 window,
2719 cx,
2720 |e, _, _| match e.error_code() {
2721 ErrorCode::RemoteUpgradeRequired => Some(format!(
2722 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2723 e.error_tag("required").unwrap_or("the latest version")
2724 )),
2725 _ => None,
2726 },
2727 );
2728 }
2729
2730 pub fn new_in_workspace(
2731 workspace: &mut Workspace,
2732 window: &mut Window,
2733 cx: &mut Context<Workspace>,
2734 ) -> Task<Result<Entity<Editor>>> {
2735 let project = workspace.project().clone();
2736 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2737
2738 cx.spawn_in(window, async move |workspace, cx| {
2739 let buffer = create.await?;
2740 workspace.update_in(cx, |workspace, window, cx| {
2741 let editor =
2742 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2743 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2744 editor
2745 })
2746 })
2747 }
2748
2749 fn new_file_vertical(
2750 workspace: &mut Workspace,
2751 _: &workspace::NewFileSplitVertical,
2752 window: &mut Window,
2753 cx: &mut Context<Workspace>,
2754 ) {
2755 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2756 }
2757
2758 fn new_file_horizontal(
2759 workspace: &mut Workspace,
2760 _: &workspace::NewFileSplitHorizontal,
2761 window: &mut Window,
2762 cx: &mut Context<Workspace>,
2763 ) {
2764 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2765 }
2766
2767 fn new_file_split(
2768 workspace: &mut Workspace,
2769 action: &workspace::NewFileSplit,
2770 window: &mut Window,
2771 cx: &mut Context<Workspace>,
2772 ) {
2773 Self::new_file_in_direction(workspace, action.0, window, cx)
2774 }
2775
2776 fn new_file_in_direction(
2777 workspace: &mut Workspace,
2778 direction: SplitDirection,
2779 window: &mut Window,
2780 cx: &mut Context<Workspace>,
2781 ) {
2782 let project = workspace.project().clone();
2783 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2784
2785 cx.spawn_in(window, async move |workspace, cx| {
2786 let buffer = create.await?;
2787 workspace.update_in(cx, move |workspace, window, cx| {
2788 workspace.split_item(
2789 direction,
2790 Box::new(
2791 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2792 ),
2793 window,
2794 cx,
2795 )
2796 })?;
2797 anyhow::Ok(())
2798 })
2799 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2800 match e.error_code() {
2801 ErrorCode::RemoteUpgradeRequired => Some(format!(
2802 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2803 e.error_tag("required").unwrap_or("the latest version")
2804 )),
2805 _ => None,
2806 }
2807 });
2808 }
2809
2810 pub fn leader_id(&self) -> Option<CollaboratorId> {
2811 self.leader_id
2812 }
2813
2814 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2815 &self.buffer
2816 }
2817
2818 pub fn project(&self) -> Option<&Entity<Project>> {
2819 self.project.as_ref()
2820 }
2821
2822 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2823 self.workspace.as_ref()?.0.upgrade()
2824 }
2825
2826 /// Returns the workspace serialization ID if this editor should be serialized.
2827 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2828 self.workspace
2829 .as_ref()
2830 .filter(|_| self.should_serialize_buffer())
2831 .and_then(|workspace| workspace.1)
2832 }
2833
2834 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2835 self.buffer().read(cx).title(cx)
2836 }
2837
2838 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2839 let git_blame_gutter_max_author_length = self
2840 .render_git_blame_gutter(cx)
2841 .then(|| {
2842 if let Some(blame) = self.blame.as_ref() {
2843 let max_author_length =
2844 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2845 Some(max_author_length)
2846 } else {
2847 None
2848 }
2849 })
2850 .flatten();
2851
2852 EditorSnapshot {
2853 mode: self.mode.clone(),
2854 show_gutter: self.show_gutter,
2855 show_line_numbers: self.show_line_numbers,
2856 show_git_diff_gutter: self.show_git_diff_gutter,
2857 show_code_actions: self.show_code_actions,
2858 show_runnables: self.show_runnables,
2859 show_breakpoints: self.show_breakpoints,
2860 git_blame_gutter_max_author_length,
2861 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2862 placeholder_display_snapshot: self
2863 .placeholder_display_map
2864 .as_ref()
2865 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2866 scroll_anchor: self.scroll_manager.anchor(),
2867 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2868 is_focused: self.focus_handle.is_focused(window),
2869 current_line_highlight: self
2870 .current_line_highlight
2871 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2872 gutter_hovered: self.gutter_hovered,
2873 }
2874 }
2875
2876 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2877 self.buffer.read(cx).language_at(point, cx)
2878 }
2879
2880 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2881 self.buffer.read(cx).read(cx).file_at(point).cloned()
2882 }
2883
2884 pub fn active_excerpt(
2885 &self,
2886 cx: &App,
2887 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2888 self.buffer
2889 .read(cx)
2890 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2891 }
2892
2893 pub fn mode(&self) -> &EditorMode {
2894 &self.mode
2895 }
2896
2897 pub fn set_mode(&mut self, mode: EditorMode) {
2898 self.mode = mode;
2899 }
2900
2901 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2902 self.collaboration_hub.as_deref()
2903 }
2904
2905 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2906 self.collaboration_hub = Some(hub);
2907 }
2908
2909 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2910 self.in_project_search = in_project_search;
2911 }
2912
2913 pub fn set_custom_context_menu(
2914 &mut self,
2915 f: impl 'static
2916 + Fn(
2917 &mut Self,
2918 DisplayPoint,
2919 &mut Window,
2920 &mut Context<Self>,
2921 ) -> Option<Entity<ui::ContextMenu>>,
2922 ) {
2923 self.custom_context_menu = Some(Box::new(f))
2924 }
2925
2926 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2927 self.completion_provider = provider;
2928 }
2929
2930 #[cfg(any(test, feature = "test-support"))]
2931 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2932 self.completion_provider.clone()
2933 }
2934
2935 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2936 self.semantics_provider.clone()
2937 }
2938
2939 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2940 self.semantics_provider = provider;
2941 }
2942
2943 pub fn set_edit_prediction_provider<T>(
2944 &mut self,
2945 provider: Option<Entity<T>>,
2946 window: &mut Window,
2947 cx: &mut Context<Self>,
2948 ) where
2949 T: EditPredictionProvider,
2950 {
2951 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2952 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2953 if this.focus_handle.is_focused(window) {
2954 this.update_visible_edit_prediction(window, cx);
2955 }
2956 }),
2957 provider: Arc::new(provider),
2958 });
2959 self.update_edit_prediction_settings(cx);
2960 self.refresh_edit_prediction(false, false, window, cx);
2961 }
2962
2963 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2964 self.placeholder_display_map
2965 .as_ref()
2966 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2967 }
2968
2969 pub fn set_placeholder_text(
2970 &mut self,
2971 placeholder_text: &str,
2972 window: &mut Window,
2973 cx: &mut Context<Self>,
2974 ) {
2975 let multibuffer = cx
2976 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2977
2978 let style = window.text_style();
2979
2980 self.placeholder_display_map = Some(cx.new(|cx| {
2981 DisplayMap::new(
2982 multibuffer,
2983 style.font(),
2984 style.font_size.to_pixels(window.rem_size()),
2985 None,
2986 FILE_HEADER_HEIGHT,
2987 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2988 Default::default(),
2989 DiagnosticSeverity::Off,
2990 cx,
2991 )
2992 }));
2993 cx.notify();
2994 }
2995
2996 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2997 self.cursor_shape = cursor_shape;
2998
2999 // Disrupt blink for immediate user feedback that the cursor shape has changed
3000 self.blink_manager.update(cx, BlinkManager::show_cursor);
3001
3002 cx.notify();
3003 }
3004
3005 pub fn set_current_line_highlight(
3006 &mut self,
3007 current_line_highlight: Option<CurrentLineHighlight>,
3008 ) {
3009 self.current_line_highlight = current_line_highlight;
3010 }
3011
3012 pub fn range_for_match<T: std::marker::Copy>(
3013 &self,
3014 range: &Range<T>,
3015 collapse: bool,
3016 ) -> Range<T> {
3017 if collapse {
3018 return range.start..range.start;
3019 }
3020 range.clone()
3021 }
3022
3023 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3024 if self.display_map.read(cx).clip_at_line_ends != clip {
3025 self.display_map
3026 .update(cx, |map, _| map.clip_at_line_ends = clip);
3027 }
3028 }
3029
3030 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3031 self.input_enabled = input_enabled;
3032 }
3033
3034 pub fn set_edit_predictions_hidden_for_vim_mode(
3035 &mut self,
3036 hidden: bool,
3037 window: &mut Window,
3038 cx: &mut Context<Self>,
3039 ) {
3040 if hidden != self.edit_predictions_hidden_for_vim_mode {
3041 self.edit_predictions_hidden_for_vim_mode = hidden;
3042 if hidden {
3043 self.update_visible_edit_prediction(window, cx);
3044 } else {
3045 self.refresh_edit_prediction(true, false, window, cx);
3046 }
3047 }
3048 }
3049
3050 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3051 self.menu_edit_predictions_policy = value;
3052 }
3053
3054 pub fn set_autoindent(&mut self, autoindent: bool) {
3055 if autoindent {
3056 self.autoindent_mode = Some(AutoindentMode::EachLine);
3057 } else {
3058 self.autoindent_mode = None;
3059 }
3060 }
3061
3062 pub fn read_only(&self, cx: &App) -> bool {
3063 self.read_only || self.buffer.read(cx).read_only()
3064 }
3065
3066 pub fn set_read_only(&mut self, read_only: bool) {
3067 self.read_only = read_only;
3068 }
3069
3070 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3071 self.use_autoclose = autoclose;
3072 }
3073
3074 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3075 self.use_auto_surround = auto_surround;
3076 }
3077
3078 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3079 self.auto_replace_emoji_shortcode = auto_replace;
3080 }
3081
3082 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3083 self.buffer_serialization = should_serialize.then(|| {
3084 BufferSerialization::new(
3085 ProjectSettings::get_global(cx)
3086 .session
3087 .restore_unsaved_buffers,
3088 )
3089 })
3090 }
3091
3092 fn should_serialize_buffer(&self) -> bool {
3093 self.buffer_serialization.is_some()
3094 }
3095
3096 pub fn toggle_edit_predictions(
3097 &mut self,
3098 _: &ToggleEditPrediction,
3099 window: &mut Window,
3100 cx: &mut Context<Self>,
3101 ) {
3102 if self.show_edit_predictions_override.is_some() {
3103 self.set_show_edit_predictions(None, window, cx);
3104 } else {
3105 let show_edit_predictions = !self.edit_predictions_enabled();
3106 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3107 }
3108 }
3109
3110 pub fn set_show_edit_predictions(
3111 &mut self,
3112 show_edit_predictions: Option<bool>,
3113 window: &mut Window,
3114 cx: &mut Context<Self>,
3115 ) {
3116 self.show_edit_predictions_override = show_edit_predictions;
3117 self.update_edit_prediction_settings(cx);
3118
3119 if let Some(false) = show_edit_predictions {
3120 self.discard_edit_prediction(false, cx);
3121 } else {
3122 self.refresh_edit_prediction(false, true, window, cx);
3123 }
3124 }
3125
3126 fn edit_predictions_disabled_in_scope(
3127 &self,
3128 buffer: &Entity<Buffer>,
3129 buffer_position: language::Anchor,
3130 cx: &App,
3131 ) -> bool {
3132 let snapshot = buffer.read(cx).snapshot();
3133 let settings = snapshot.settings_at(buffer_position, cx);
3134
3135 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3136 return false;
3137 };
3138
3139 scope.override_name().is_some_and(|scope_name| {
3140 settings
3141 .edit_predictions_disabled_in
3142 .iter()
3143 .any(|s| s == scope_name)
3144 })
3145 }
3146
3147 pub fn set_use_modal_editing(&mut self, to: bool) {
3148 self.use_modal_editing = to;
3149 }
3150
3151 pub fn use_modal_editing(&self) -> bool {
3152 self.use_modal_editing
3153 }
3154
3155 fn selections_did_change(
3156 &mut self,
3157 local: bool,
3158 old_cursor_position: &Anchor,
3159 effects: SelectionEffects,
3160 window: &mut Window,
3161 cx: &mut Context<Self>,
3162 ) {
3163 window.invalidate_character_coordinates();
3164
3165 // Copy selections to primary selection buffer
3166 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3167 if local {
3168 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3169 let buffer_handle = self.buffer.read(cx).read(cx);
3170
3171 let mut text = String::new();
3172 for (index, selection) in selections.iter().enumerate() {
3173 let text_for_selection = buffer_handle
3174 .text_for_range(selection.start..selection.end)
3175 .collect::<String>();
3176
3177 text.push_str(&text_for_selection);
3178 if index != selections.len() - 1 {
3179 text.push('\n');
3180 }
3181 }
3182
3183 if !text.is_empty() {
3184 cx.write_to_primary(ClipboardItem::new_string(text));
3185 }
3186 }
3187
3188 let selection_anchors = self.selections.disjoint_anchors_arc();
3189
3190 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3191 self.buffer.update(cx, |buffer, cx| {
3192 buffer.set_active_selections(
3193 &selection_anchors,
3194 self.selections.line_mode(),
3195 self.cursor_shape,
3196 cx,
3197 )
3198 });
3199 }
3200 let display_map = self
3201 .display_map
3202 .update(cx, |display_map, cx| display_map.snapshot(cx));
3203 let buffer = display_map.buffer_snapshot();
3204 if self.selections.count() == 1 {
3205 self.add_selections_state = None;
3206 }
3207 self.select_next_state = None;
3208 self.select_prev_state = None;
3209 self.select_syntax_node_history.try_clear();
3210 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3211 self.snippet_stack.invalidate(&selection_anchors, buffer);
3212 self.take_rename(false, window, cx);
3213
3214 let newest_selection = self.selections.newest_anchor();
3215 let new_cursor_position = newest_selection.head();
3216 let selection_start = newest_selection.start;
3217
3218 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3219 self.push_to_nav_history(
3220 *old_cursor_position,
3221 Some(new_cursor_position.to_point(buffer)),
3222 false,
3223 effects.nav_history == Some(true),
3224 cx,
3225 );
3226 }
3227
3228 if local {
3229 if let Some(buffer_id) = new_cursor_position.buffer_id {
3230 self.register_buffer(buffer_id, cx);
3231 }
3232
3233 let mut context_menu = self.context_menu.borrow_mut();
3234 let completion_menu = match context_menu.as_ref() {
3235 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3236 Some(CodeContextMenu::CodeActions(_)) => {
3237 *context_menu = None;
3238 None
3239 }
3240 None => None,
3241 };
3242 let completion_position = completion_menu.map(|menu| menu.initial_position);
3243 drop(context_menu);
3244
3245 if effects.completions
3246 && let Some(completion_position) = completion_position
3247 {
3248 let start_offset = selection_start.to_offset(buffer);
3249 let position_matches = start_offset == completion_position.to_offset(buffer);
3250 let continue_showing = if position_matches {
3251 if self.snippet_stack.is_empty() {
3252 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3253 == Some(CharKind::Word)
3254 } else {
3255 // Snippet choices can be shown even when the cursor is in whitespace.
3256 // Dismissing the menu with actions like backspace is handled by
3257 // invalidation regions.
3258 true
3259 }
3260 } else {
3261 false
3262 };
3263
3264 if continue_showing {
3265 self.open_or_update_completions_menu(None, None, false, window, cx);
3266 } else {
3267 self.hide_context_menu(window, cx);
3268 }
3269 }
3270
3271 hide_hover(self, cx);
3272
3273 if old_cursor_position.to_display_point(&display_map).row()
3274 != new_cursor_position.to_display_point(&display_map).row()
3275 {
3276 self.available_code_actions.take();
3277 }
3278 self.refresh_code_actions(window, cx);
3279 self.refresh_document_highlights(cx);
3280 refresh_linked_ranges(self, window, cx);
3281
3282 self.refresh_selected_text_highlights(false, window, cx);
3283 self.refresh_matching_bracket_highlights(window, cx);
3284 self.update_visible_edit_prediction(window, cx);
3285 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3286 self.inline_blame_popover.take();
3287 if self.git_blame_inline_enabled {
3288 self.start_inline_blame_timer(window, cx);
3289 }
3290 }
3291
3292 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3293 cx.emit(EditorEvent::SelectionsChanged { local });
3294
3295 let selections = &self.selections.disjoint_anchors_arc();
3296 if selections.len() == 1 {
3297 cx.emit(SearchEvent::ActiveMatchChanged)
3298 }
3299 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3300 let inmemory_selections = selections
3301 .iter()
3302 .map(|s| {
3303 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3304 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3305 })
3306 .collect();
3307 self.update_restoration_data(cx, |data| {
3308 data.selections = inmemory_selections;
3309 });
3310
3311 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3312 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3313 {
3314 let snapshot = self.buffer().read(cx).snapshot(cx);
3315 let selections = selections.clone();
3316 let background_executor = cx.background_executor().clone();
3317 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3318 self.serialize_selections = cx.background_spawn(async move {
3319 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3320 let db_selections = selections
3321 .iter()
3322 .map(|selection| {
3323 (
3324 selection.start.to_offset(&snapshot),
3325 selection.end.to_offset(&snapshot),
3326 )
3327 })
3328 .collect();
3329
3330 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3331 .await
3332 .with_context(|| {
3333 format!(
3334 "persisting editor selections for editor {editor_id}, \
3335 workspace {workspace_id:?}"
3336 )
3337 })
3338 .log_err();
3339 });
3340 }
3341 }
3342
3343 cx.notify();
3344 }
3345
3346 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3347 use text::ToOffset as _;
3348 use text::ToPoint as _;
3349
3350 if self.mode.is_minimap()
3351 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3352 {
3353 return;
3354 }
3355
3356 if !self.buffer().read(cx).is_singleton() {
3357 return;
3358 }
3359
3360 let display_snapshot = self
3361 .display_map
3362 .update(cx, |display_map, cx| display_map.snapshot(cx));
3363 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3364 return;
3365 };
3366 let inmemory_folds = display_snapshot
3367 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3368 .map(|fold| {
3369 fold.range.start.text_anchor.to_point(&snapshot)
3370 ..fold.range.end.text_anchor.to_point(&snapshot)
3371 })
3372 .collect();
3373 self.update_restoration_data(cx, |data| {
3374 data.folds = inmemory_folds;
3375 });
3376
3377 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3378 return;
3379 };
3380 let background_executor = cx.background_executor().clone();
3381 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3382 let db_folds = display_snapshot
3383 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3384 .map(|fold| {
3385 (
3386 fold.range.start.text_anchor.to_offset(&snapshot),
3387 fold.range.end.text_anchor.to_offset(&snapshot),
3388 )
3389 })
3390 .collect();
3391 self.serialize_folds = cx.background_spawn(async move {
3392 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3393 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3394 .await
3395 .with_context(|| {
3396 format!(
3397 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3398 )
3399 })
3400 .log_err();
3401 });
3402 }
3403
3404 pub fn sync_selections(
3405 &mut self,
3406 other: Entity<Editor>,
3407 cx: &mut Context<Self>,
3408 ) -> gpui::Subscription {
3409 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3410 if !other_selections.is_empty() {
3411 self.selections
3412 .change_with(&self.display_snapshot(cx), |selections| {
3413 selections.select_anchors(other_selections);
3414 });
3415 }
3416
3417 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3418 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3419 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3420 if other_selections.is_empty() {
3421 return;
3422 }
3423 let snapshot = this.display_snapshot(cx);
3424 this.selections.change_with(&snapshot, |selections| {
3425 selections.select_anchors(other_selections);
3426 });
3427 }
3428 });
3429
3430 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3431 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3432 let these_selections = this.selections.disjoint_anchors().to_vec();
3433 if these_selections.is_empty() {
3434 return;
3435 }
3436 other.update(cx, |other_editor, cx| {
3437 let snapshot = other_editor.display_snapshot(cx);
3438 other_editor
3439 .selections
3440 .change_with(&snapshot, |selections| {
3441 selections.select_anchors(these_selections);
3442 })
3443 });
3444 }
3445 });
3446
3447 Subscription::join(other_subscription, this_subscription)
3448 }
3449
3450 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3451 if self.buffer().read(cx).is_singleton() {
3452 return;
3453 }
3454 let snapshot = self.buffer.read(cx).snapshot(cx);
3455 let buffer_ids: HashSet<BufferId> = self
3456 .selections
3457 .disjoint_anchor_ranges()
3458 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3459 .collect();
3460 for buffer_id in buffer_ids {
3461 self.unfold_buffer(buffer_id, cx);
3462 }
3463 }
3464
3465 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3466 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3467 /// effects of selection change occur at the end of the transaction.
3468 pub fn change_selections<R>(
3469 &mut self,
3470 effects: SelectionEffects,
3471 window: &mut Window,
3472 cx: &mut Context<Self>,
3473 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3474 ) -> R {
3475 let snapshot = self.display_snapshot(cx);
3476 if let Some(state) = &mut self.deferred_selection_effects_state {
3477 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3478 state.effects.completions = effects.completions;
3479 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3480 let (changed, result) = self.selections.change_with(&snapshot, change);
3481 state.changed |= changed;
3482 return result;
3483 }
3484 let mut state = DeferredSelectionEffectsState {
3485 changed: false,
3486 effects,
3487 old_cursor_position: self.selections.newest_anchor().head(),
3488 history_entry: SelectionHistoryEntry {
3489 selections: self.selections.disjoint_anchors_arc(),
3490 select_next_state: self.select_next_state.clone(),
3491 select_prev_state: self.select_prev_state.clone(),
3492 add_selections_state: self.add_selections_state.clone(),
3493 },
3494 };
3495 let (changed, result) = self.selections.change_with(&snapshot, change);
3496 state.changed = state.changed || changed;
3497 if self.defer_selection_effects {
3498 self.deferred_selection_effects_state = Some(state);
3499 } else {
3500 self.apply_selection_effects(state, window, cx);
3501 }
3502 result
3503 }
3504
3505 /// Defers the effects of selection change, so that the effects of multiple calls to
3506 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3507 /// to selection history and the state of popovers based on selection position aren't
3508 /// erroneously updated.
3509 pub fn with_selection_effects_deferred<R>(
3510 &mut self,
3511 window: &mut Window,
3512 cx: &mut Context<Self>,
3513 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3514 ) -> R {
3515 let already_deferred = self.defer_selection_effects;
3516 self.defer_selection_effects = true;
3517 let result = update(self, window, cx);
3518 if !already_deferred {
3519 self.defer_selection_effects = false;
3520 if let Some(state) = self.deferred_selection_effects_state.take() {
3521 self.apply_selection_effects(state, window, cx);
3522 }
3523 }
3524 result
3525 }
3526
3527 fn apply_selection_effects(
3528 &mut self,
3529 state: DeferredSelectionEffectsState,
3530 window: &mut Window,
3531 cx: &mut Context<Self>,
3532 ) {
3533 if state.changed {
3534 self.selection_history.push(state.history_entry);
3535
3536 if let Some(autoscroll) = state.effects.scroll {
3537 self.request_autoscroll(autoscroll, cx);
3538 }
3539
3540 let old_cursor_position = &state.old_cursor_position;
3541
3542 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3543
3544 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3545 self.show_signature_help(&ShowSignatureHelp, window, cx);
3546 }
3547 }
3548 }
3549
3550 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3551 where
3552 I: IntoIterator<Item = (Range<S>, T)>,
3553 S: ToOffset,
3554 T: Into<Arc<str>>,
3555 {
3556 if self.read_only(cx) {
3557 return;
3558 }
3559
3560 self.buffer
3561 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3562 }
3563
3564 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3565 where
3566 I: IntoIterator<Item = (Range<S>, T)>,
3567 S: ToOffset,
3568 T: Into<Arc<str>>,
3569 {
3570 if self.read_only(cx) {
3571 return;
3572 }
3573
3574 self.buffer.update(cx, |buffer, cx| {
3575 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3576 });
3577 }
3578
3579 pub fn edit_with_block_indent<I, S, T>(
3580 &mut self,
3581 edits: I,
3582 original_indent_columns: Vec<Option<u32>>,
3583 cx: &mut Context<Self>,
3584 ) where
3585 I: IntoIterator<Item = (Range<S>, T)>,
3586 S: ToOffset,
3587 T: Into<Arc<str>>,
3588 {
3589 if self.read_only(cx) {
3590 return;
3591 }
3592
3593 self.buffer.update(cx, |buffer, cx| {
3594 buffer.edit(
3595 edits,
3596 Some(AutoindentMode::Block {
3597 original_indent_columns,
3598 }),
3599 cx,
3600 )
3601 });
3602 }
3603
3604 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3605 self.hide_context_menu(window, cx);
3606
3607 match phase {
3608 SelectPhase::Begin {
3609 position,
3610 add,
3611 click_count,
3612 } => self.begin_selection(position, add, click_count, window, cx),
3613 SelectPhase::BeginColumnar {
3614 position,
3615 goal_column,
3616 reset,
3617 mode,
3618 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3619 SelectPhase::Extend {
3620 position,
3621 click_count,
3622 } => self.extend_selection(position, click_count, window, cx),
3623 SelectPhase::Update {
3624 position,
3625 goal_column,
3626 scroll_delta,
3627 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3628 SelectPhase::End => self.end_selection(window, cx),
3629 }
3630 }
3631
3632 fn extend_selection(
3633 &mut self,
3634 position: DisplayPoint,
3635 click_count: usize,
3636 window: &mut Window,
3637 cx: &mut Context<Self>,
3638 ) {
3639 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3640 let tail = self.selections.newest::<usize>(&display_map).tail();
3641 let click_count = click_count.max(match self.selections.select_mode() {
3642 SelectMode::Character => 1,
3643 SelectMode::Word(_) => 2,
3644 SelectMode::Line(_) => 3,
3645 SelectMode::All => 4,
3646 });
3647 self.begin_selection(position, false, click_count, window, cx);
3648
3649 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3650
3651 let current_selection = match self.selections.select_mode() {
3652 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3653 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3654 };
3655
3656 let mut pending_selection = self
3657 .selections
3658 .pending_anchor()
3659 .cloned()
3660 .expect("extend_selection not called with pending selection");
3661
3662 if pending_selection
3663 .start
3664 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3665 == Ordering::Greater
3666 {
3667 pending_selection.start = current_selection.start;
3668 }
3669 if pending_selection
3670 .end
3671 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3672 == Ordering::Less
3673 {
3674 pending_selection.end = current_selection.end;
3675 pending_selection.reversed = true;
3676 }
3677
3678 let mut pending_mode = self.selections.pending_mode().unwrap();
3679 match &mut pending_mode {
3680 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3681 _ => {}
3682 }
3683
3684 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3685 SelectionEffects::scroll(Autoscroll::fit())
3686 } else {
3687 SelectionEffects::no_scroll()
3688 };
3689
3690 self.change_selections(effects, window, cx, |s| {
3691 s.set_pending(pending_selection.clone(), pending_mode);
3692 s.set_is_extending(true);
3693 });
3694 }
3695
3696 fn begin_selection(
3697 &mut self,
3698 position: DisplayPoint,
3699 add: bool,
3700 click_count: usize,
3701 window: &mut Window,
3702 cx: &mut Context<Self>,
3703 ) {
3704 if !self.focus_handle.is_focused(window) {
3705 self.last_focused_descendant = None;
3706 window.focus(&self.focus_handle);
3707 }
3708
3709 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3710 let buffer = display_map.buffer_snapshot();
3711 let position = display_map.clip_point(position, Bias::Left);
3712
3713 let start;
3714 let end;
3715 let mode;
3716 let mut auto_scroll;
3717 match click_count {
3718 1 => {
3719 start = buffer.anchor_before(position.to_point(&display_map));
3720 end = start;
3721 mode = SelectMode::Character;
3722 auto_scroll = true;
3723 }
3724 2 => {
3725 let position = display_map
3726 .clip_point(position, Bias::Left)
3727 .to_offset(&display_map, Bias::Left);
3728 let (range, _) = buffer.surrounding_word(position, None);
3729 start = buffer.anchor_before(range.start);
3730 end = buffer.anchor_before(range.end);
3731 mode = SelectMode::Word(start..end);
3732 auto_scroll = true;
3733 }
3734 3 => {
3735 let position = display_map
3736 .clip_point(position, Bias::Left)
3737 .to_point(&display_map);
3738 let line_start = display_map.prev_line_boundary(position).0;
3739 let next_line_start = buffer.clip_point(
3740 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3741 Bias::Left,
3742 );
3743 start = buffer.anchor_before(line_start);
3744 end = buffer.anchor_before(next_line_start);
3745 mode = SelectMode::Line(start..end);
3746 auto_scroll = true;
3747 }
3748 _ => {
3749 start = buffer.anchor_before(0);
3750 end = buffer.anchor_before(buffer.len());
3751 mode = SelectMode::All;
3752 auto_scroll = false;
3753 }
3754 }
3755 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3756
3757 let point_to_delete: Option<usize> = {
3758 let selected_points: Vec<Selection<Point>> =
3759 self.selections.disjoint_in_range(start..end, &display_map);
3760
3761 if !add || click_count > 1 {
3762 None
3763 } else if !selected_points.is_empty() {
3764 Some(selected_points[0].id)
3765 } else {
3766 let clicked_point_already_selected =
3767 self.selections.disjoint_anchors().iter().find(|selection| {
3768 selection.start.to_point(buffer) == start.to_point(buffer)
3769 || selection.end.to_point(buffer) == end.to_point(buffer)
3770 });
3771
3772 clicked_point_already_selected.map(|selection| selection.id)
3773 }
3774 };
3775
3776 let selections_count = self.selections.count();
3777 let effects = if auto_scroll {
3778 SelectionEffects::default()
3779 } else {
3780 SelectionEffects::no_scroll()
3781 };
3782
3783 self.change_selections(effects, window, cx, |s| {
3784 if let Some(point_to_delete) = point_to_delete {
3785 s.delete(point_to_delete);
3786
3787 if selections_count == 1 {
3788 s.set_pending_anchor_range(start..end, mode);
3789 }
3790 } else {
3791 if !add {
3792 s.clear_disjoint();
3793 }
3794
3795 s.set_pending_anchor_range(start..end, mode);
3796 }
3797 });
3798 }
3799
3800 fn begin_columnar_selection(
3801 &mut self,
3802 position: DisplayPoint,
3803 goal_column: u32,
3804 reset: bool,
3805 mode: ColumnarMode,
3806 window: &mut Window,
3807 cx: &mut Context<Self>,
3808 ) {
3809 if !self.focus_handle.is_focused(window) {
3810 self.last_focused_descendant = None;
3811 window.focus(&self.focus_handle);
3812 }
3813
3814 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3815
3816 if reset {
3817 let pointer_position = display_map
3818 .buffer_snapshot()
3819 .anchor_before(position.to_point(&display_map));
3820
3821 self.change_selections(
3822 SelectionEffects::scroll(Autoscroll::newest()),
3823 window,
3824 cx,
3825 |s| {
3826 s.clear_disjoint();
3827 s.set_pending_anchor_range(
3828 pointer_position..pointer_position,
3829 SelectMode::Character,
3830 );
3831 },
3832 );
3833 };
3834
3835 let tail = self.selections.newest::<Point>(&display_map).tail();
3836 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3837 self.columnar_selection_state = match mode {
3838 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3839 selection_tail: selection_anchor,
3840 display_point: if reset {
3841 if position.column() != goal_column {
3842 Some(DisplayPoint::new(position.row(), goal_column))
3843 } else {
3844 None
3845 }
3846 } else {
3847 None
3848 },
3849 }),
3850 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3851 selection_tail: selection_anchor,
3852 }),
3853 };
3854
3855 if !reset {
3856 self.select_columns(position, goal_column, &display_map, window, cx);
3857 }
3858 }
3859
3860 fn update_selection(
3861 &mut self,
3862 position: DisplayPoint,
3863 goal_column: u32,
3864 scroll_delta: gpui::Point<f32>,
3865 window: &mut Window,
3866 cx: &mut Context<Self>,
3867 ) {
3868 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3869
3870 if self.columnar_selection_state.is_some() {
3871 self.select_columns(position, goal_column, &display_map, window, cx);
3872 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3873 let buffer = display_map.buffer_snapshot();
3874 let head;
3875 let tail;
3876 let mode = self.selections.pending_mode().unwrap();
3877 match &mode {
3878 SelectMode::Character => {
3879 head = position.to_point(&display_map);
3880 tail = pending.tail().to_point(buffer);
3881 }
3882 SelectMode::Word(original_range) => {
3883 let offset = display_map
3884 .clip_point(position, Bias::Left)
3885 .to_offset(&display_map, Bias::Left);
3886 let original_range = original_range.to_offset(buffer);
3887
3888 let head_offset = if buffer.is_inside_word(offset, None)
3889 || original_range.contains(&offset)
3890 {
3891 let (word_range, _) = buffer.surrounding_word(offset, None);
3892 if word_range.start < original_range.start {
3893 word_range.start
3894 } else {
3895 word_range.end
3896 }
3897 } else {
3898 offset
3899 };
3900
3901 head = head_offset.to_point(buffer);
3902 if head_offset <= original_range.start {
3903 tail = original_range.end.to_point(buffer);
3904 } else {
3905 tail = original_range.start.to_point(buffer);
3906 }
3907 }
3908 SelectMode::Line(original_range) => {
3909 let original_range = original_range.to_point(display_map.buffer_snapshot());
3910
3911 let position = display_map
3912 .clip_point(position, Bias::Left)
3913 .to_point(&display_map);
3914 let line_start = display_map.prev_line_boundary(position).0;
3915 let next_line_start = buffer.clip_point(
3916 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3917 Bias::Left,
3918 );
3919
3920 if line_start < original_range.start {
3921 head = line_start
3922 } else {
3923 head = next_line_start
3924 }
3925
3926 if head <= original_range.start {
3927 tail = original_range.end;
3928 } else {
3929 tail = original_range.start;
3930 }
3931 }
3932 SelectMode::All => {
3933 return;
3934 }
3935 };
3936
3937 if head < tail {
3938 pending.start = buffer.anchor_before(head);
3939 pending.end = buffer.anchor_before(tail);
3940 pending.reversed = true;
3941 } else {
3942 pending.start = buffer.anchor_before(tail);
3943 pending.end = buffer.anchor_before(head);
3944 pending.reversed = false;
3945 }
3946
3947 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3948 s.set_pending(pending.clone(), mode);
3949 });
3950 } else {
3951 log::error!("update_selection dispatched with no pending selection");
3952 return;
3953 }
3954
3955 self.apply_scroll_delta(scroll_delta, window, cx);
3956 cx.notify();
3957 }
3958
3959 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3960 self.columnar_selection_state.take();
3961 if let Some(pending_mode) = self.selections.pending_mode() {
3962 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3963 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3964 s.select(selections);
3965 s.clear_pending();
3966 if s.is_extending() {
3967 s.set_is_extending(false);
3968 } else {
3969 s.set_select_mode(pending_mode);
3970 }
3971 });
3972 }
3973 }
3974
3975 fn select_columns(
3976 &mut self,
3977 head: DisplayPoint,
3978 goal_column: u32,
3979 display_map: &DisplaySnapshot,
3980 window: &mut Window,
3981 cx: &mut Context<Self>,
3982 ) {
3983 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3984 return;
3985 };
3986
3987 let tail = match columnar_state {
3988 ColumnarSelectionState::FromMouse {
3989 selection_tail,
3990 display_point,
3991 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3992 ColumnarSelectionState::FromSelection { selection_tail } => {
3993 selection_tail.to_display_point(display_map)
3994 }
3995 };
3996
3997 let start_row = cmp::min(tail.row(), head.row());
3998 let end_row = cmp::max(tail.row(), head.row());
3999 let start_column = cmp::min(tail.column(), goal_column);
4000 let end_column = cmp::max(tail.column(), goal_column);
4001 let reversed = start_column < tail.column();
4002
4003 let selection_ranges = (start_row.0..=end_row.0)
4004 .map(DisplayRow)
4005 .filter_map(|row| {
4006 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4007 || start_column <= display_map.line_len(row))
4008 && !display_map.is_block_line(row)
4009 {
4010 let start = display_map
4011 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4012 .to_point(display_map);
4013 let end = display_map
4014 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4015 .to_point(display_map);
4016 if reversed {
4017 Some(end..start)
4018 } else {
4019 Some(start..end)
4020 }
4021 } else {
4022 None
4023 }
4024 })
4025 .collect::<Vec<_>>();
4026 if selection_ranges.is_empty() {
4027 return;
4028 }
4029
4030 let ranges = match columnar_state {
4031 ColumnarSelectionState::FromMouse { .. } => {
4032 let mut non_empty_ranges = selection_ranges
4033 .iter()
4034 .filter(|selection_range| selection_range.start != selection_range.end)
4035 .peekable();
4036 if non_empty_ranges.peek().is_some() {
4037 non_empty_ranges.cloned().collect()
4038 } else {
4039 selection_ranges
4040 }
4041 }
4042 _ => selection_ranges,
4043 };
4044
4045 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4046 s.select_ranges(ranges);
4047 });
4048 cx.notify();
4049 }
4050
4051 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4052 self.selections
4053 .all_adjusted(snapshot)
4054 .iter()
4055 .any(|selection| !selection.is_empty())
4056 }
4057
4058 pub fn has_pending_nonempty_selection(&self) -> bool {
4059 let pending_nonempty_selection = match self.selections.pending_anchor() {
4060 Some(Selection { start, end, .. }) => start != end,
4061 None => false,
4062 };
4063
4064 pending_nonempty_selection
4065 || (self.columnar_selection_state.is_some()
4066 && self.selections.disjoint_anchors().len() > 1)
4067 }
4068
4069 pub fn has_pending_selection(&self) -> bool {
4070 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4071 }
4072
4073 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4074 self.selection_mark_mode = false;
4075 self.selection_drag_state = SelectionDragState::None;
4076
4077 if self.dismiss_menus_and_popups(true, window, cx) {
4078 cx.notify();
4079 return;
4080 }
4081 if self.clear_expanded_diff_hunks(cx) {
4082 cx.notify();
4083 return;
4084 }
4085 if self.show_git_blame_gutter {
4086 self.show_git_blame_gutter = false;
4087 cx.notify();
4088 return;
4089 }
4090
4091 if self.mode.is_full()
4092 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4093 {
4094 cx.notify();
4095 return;
4096 }
4097
4098 cx.propagate();
4099 }
4100
4101 pub fn dismiss_menus_and_popups(
4102 &mut self,
4103 is_user_requested: bool,
4104 window: &mut Window,
4105 cx: &mut Context<Self>,
4106 ) -> bool {
4107 if self.take_rename(false, window, cx).is_some() {
4108 return true;
4109 }
4110
4111 if self.hide_blame_popover(true, cx) {
4112 return true;
4113 }
4114
4115 if hide_hover(self, cx) {
4116 return true;
4117 }
4118
4119 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
4120 return true;
4121 }
4122
4123 if self.hide_context_menu(window, cx).is_some() {
4124 return true;
4125 }
4126
4127 if self.mouse_context_menu.take().is_some() {
4128 return true;
4129 }
4130
4131 if is_user_requested && self.discard_edit_prediction(true, cx) {
4132 return true;
4133 }
4134
4135 if self.snippet_stack.pop().is_some() {
4136 return true;
4137 }
4138
4139 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4140 self.dismiss_diagnostics(cx);
4141 return true;
4142 }
4143
4144 false
4145 }
4146
4147 fn linked_editing_ranges_for(
4148 &self,
4149 selection: Range<text::Anchor>,
4150 cx: &App,
4151 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4152 if self.linked_edit_ranges.is_empty() {
4153 return None;
4154 }
4155 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4156 selection.end.buffer_id.and_then(|end_buffer_id| {
4157 if selection.start.buffer_id != Some(end_buffer_id) {
4158 return None;
4159 }
4160 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4161 let snapshot = buffer.read(cx).snapshot();
4162 self.linked_edit_ranges
4163 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4164 .map(|ranges| (ranges, snapshot, buffer))
4165 })?;
4166 use text::ToOffset as TO;
4167 // find offset from the start of current range to current cursor position
4168 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4169
4170 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4171 let start_difference = start_offset - start_byte_offset;
4172 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4173 let end_difference = end_offset - start_byte_offset;
4174 // Current range has associated linked ranges.
4175 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4176 for range in linked_ranges.iter() {
4177 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4178 let end_offset = start_offset + end_difference;
4179 let start_offset = start_offset + start_difference;
4180 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4181 continue;
4182 }
4183 if self.selections.disjoint_anchor_ranges().any(|s| {
4184 if s.start.buffer_id != selection.start.buffer_id
4185 || s.end.buffer_id != selection.end.buffer_id
4186 {
4187 return false;
4188 }
4189 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4190 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4191 }) {
4192 continue;
4193 }
4194 let start = buffer_snapshot.anchor_after(start_offset);
4195 let end = buffer_snapshot.anchor_after(end_offset);
4196 linked_edits
4197 .entry(buffer.clone())
4198 .or_default()
4199 .push(start..end);
4200 }
4201 Some(linked_edits)
4202 }
4203
4204 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4205 let text: Arc<str> = text.into();
4206
4207 if self.read_only(cx) {
4208 return;
4209 }
4210
4211 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4212
4213 self.unfold_buffers_with_selections(cx);
4214
4215 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4216 let mut bracket_inserted = false;
4217 let mut edits = Vec::new();
4218 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4219 let mut new_selections = Vec::with_capacity(selections.len());
4220 let mut new_autoclose_regions = Vec::new();
4221 let snapshot = self.buffer.read(cx).read(cx);
4222 let mut clear_linked_edit_ranges = false;
4223
4224 for (selection, autoclose_region) in
4225 self.selections_with_autoclose_regions(selections, &snapshot)
4226 {
4227 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4228 // Determine if the inserted text matches the opening or closing
4229 // bracket of any of this language's bracket pairs.
4230 let mut bracket_pair = None;
4231 let mut is_bracket_pair_start = false;
4232 let mut is_bracket_pair_end = false;
4233 if !text.is_empty() {
4234 let mut bracket_pair_matching_end = None;
4235 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4236 // and they are removing the character that triggered IME popup.
4237 for (pair, enabled) in scope.brackets() {
4238 if !pair.close && !pair.surround {
4239 continue;
4240 }
4241
4242 if enabled && pair.start.ends_with(text.as_ref()) {
4243 let prefix_len = pair.start.len() - text.len();
4244 let preceding_text_matches_prefix = prefix_len == 0
4245 || (selection.start.column >= (prefix_len as u32)
4246 && snapshot.contains_str_at(
4247 Point::new(
4248 selection.start.row,
4249 selection.start.column - (prefix_len as u32),
4250 ),
4251 &pair.start[..prefix_len],
4252 ));
4253 if preceding_text_matches_prefix {
4254 bracket_pair = Some(pair.clone());
4255 is_bracket_pair_start = true;
4256 break;
4257 }
4258 }
4259 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4260 {
4261 // take first bracket pair matching end, but don't break in case a later bracket
4262 // pair matches start
4263 bracket_pair_matching_end = Some(pair.clone());
4264 }
4265 }
4266 if let Some(end) = bracket_pair_matching_end
4267 && bracket_pair.is_none()
4268 {
4269 bracket_pair = Some(end);
4270 is_bracket_pair_end = true;
4271 }
4272 }
4273
4274 if let Some(bracket_pair) = bracket_pair {
4275 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4276 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4277 let auto_surround =
4278 self.use_auto_surround && snapshot_settings.use_auto_surround;
4279 if selection.is_empty() {
4280 if is_bracket_pair_start {
4281 // If the inserted text is a suffix of an opening bracket and the
4282 // selection is preceded by the rest of the opening bracket, then
4283 // insert the closing bracket.
4284 let following_text_allows_autoclose = snapshot
4285 .chars_at(selection.start)
4286 .next()
4287 .is_none_or(|c| scope.should_autoclose_before(c));
4288
4289 let preceding_text_allows_autoclose = selection.start.column == 0
4290 || snapshot
4291 .reversed_chars_at(selection.start)
4292 .next()
4293 .is_none_or(|c| {
4294 bracket_pair.start != bracket_pair.end
4295 || !snapshot
4296 .char_classifier_at(selection.start)
4297 .is_word(c)
4298 });
4299
4300 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4301 && bracket_pair.start.len() == 1
4302 {
4303 let target = bracket_pair.start.chars().next().unwrap();
4304 let current_line_count = snapshot
4305 .reversed_chars_at(selection.start)
4306 .take_while(|&c| c != '\n')
4307 .filter(|&c| c == target)
4308 .count();
4309 current_line_count % 2 == 1
4310 } else {
4311 false
4312 };
4313
4314 if autoclose
4315 && bracket_pair.close
4316 && following_text_allows_autoclose
4317 && preceding_text_allows_autoclose
4318 && !is_closing_quote
4319 {
4320 let anchor = snapshot.anchor_before(selection.end);
4321 new_selections.push((selection.map(|_| anchor), text.len()));
4322 new_autoclose_regions.push((
4323 anchor,
4324 text.len(),
4325 selection.id,
4326 bracket_pair.clone(),
4327 ));
4328 edits.push((
4329 selection.range(),
4330 format!("{}{}", text, bracket_pair.end).into(),
4331 ));
4332 bracket_inserted = true;
4333 continue;
4334 }
4335 }
4336
4337 if let Some(region) = autoclose_region {
4338 // If the selection is followed by an auto-inserted closing bracket,
4339 // then don't insert that closing bracket again; just move the selection
4340 // past the closing bracket.
4341 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4342 && text.as_ref() == region.pair.end.as_str()
4343 && snapshot.contains_str_at(region.range.end, text.as_ref());
4344 if should_skip {
4345 let anchor = snapshot.anchor_after(selection.end);
4346 new_selections
4347 .push((selection.map(|_| anchor), region.pair.end.len()));
4348 continue;
4349 }
4350 }
4351
4352 let always_treat_brackets_as_autoclosed = snapshot
4353 .language_settings_at(selection.start, cx)
4354 .always_treat_brackets_as_autoclosed;
4355 if always_treat_brackets_as_autoclosed
4356 && is_bracket_pair_end
4357 && snapshot.contains_str_at(selection.end, text.as_ref())
4358 {
4359 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4360 // and the inserted text is a closing bracket and the selection is followed
4361 // by the closing bracket then move the selection past the closing bracket.
4362 let anchor = snapshot.anchor_after(selection.end);
4363 new_selections.push((selection.map(|_| anchor), text.len()));
4364 continue;
4365 }
4366 }
4367 // If an opening bracket is 1 character long and is typed while
4368 // text is selected, then surround that text with the bracket pair.
4369 else if auto_surround
4370 && bracket_pair.surround
4371 && is_bracket_pair_start
4372 && bracket_pair.start.chars().count() == 1
4373 {
4374 edits.push((selection.start..selection.start, text.clone()));
4375 edits.push((
4376 selection.end..selection.end,
4377 bracket_pair.end.as_str().into(),
4378 ));
4379 bracket_inserted = true;
4380 new_selections.push((
4381 Selection {
4382 id: selection.id,
4383 start: snapshot.anchor_after(selection.start),
4384 end: snapshot.anchor_before(selection.end),
4385 reversed: selection.reversed,
4386 goal: selection.goal,
4387 },
4388 0,
4389 ));
4390 continue;
4391 }
4392 }
4393 }
4394
4395 if self.auto_replace_emoji_shortcode
4396 && selection.is_empty()
4397 && text.as_ref().ends_with(':')
4398 && let Some(possible_emoji_short_code) =
4399 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4400 && !possible_emoji_short_code.is_empty()
4401 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4402 {
4403 let emoji_shortcode_start = Point::new(
4404 selection.start.row,
4405 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4406 );
4407
4408 // Remove shortcode from buffer
4409 edits.push((
4410 emoji_shortcode_start..selection.start,
4411 "".to_string().into(),
4412 ));
4413 new_selections.push((
4414 Selection {
4415 id: selection.id,
4416 start: snapshot.anchor_after(emoji_shortcode_start),
4417 end: snapshot.anchor_before(selection.start),
4418 reversed: selection.reversed,
4419 goal: selection.goal,
4420 },
4421 0,
4422 ));
4423
4424 // Insert emoji
4425 let selection_start_anchor = snapshot.anchor_after(selection.start);
4426 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4427 edits.push((selection.start..selection.end, emoji.to_string().into()));
4428
4429 continue;
4430 }
4431
4432 // If not handling any auto-close operation, then just replace the selected
4433 // text with the given input and move the selection to the end of the
4434 // newly inserted text.
4435 let anchor = snapshot.anchor_after(selection.end);
4436 if !self.linked_edit_ranges.is_empty() {
4437 let start_anchor = snapshot.anchor_before(selection.start);
4438
4439 let is_word_char = text.chars().next().is_none_or(|char| {
4440 let classifier = snapshot
4441 .char_classifier_at(start_anchor.to_offset(&snapshot))
4442 .scope_context(Some(CharScopeContext::LinkedEdit));
4443 classifier.is_word(char)
4444 });
4445
4446 if is_word_char {
4447 if let Some(ranges) = self
4448 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4449 {
4450 for (buffer, edits) in ranges {
4451 linked_edits
4452 .entry(buffer.clone())
4453 .or_default()
4454 .extend(edits.into_iter().map(|range| (range, text.clone())));
4455 }
4456 }
4457 } else {
4458 clear_linked_edit_ranges = true;
4459 }
4460 }
4461
4462 new_selections.push((selection.map(|_| anchor), 0));
4463 edits.push((selection.start..selection.end, text.clone()));
4464 }
4465
4466 drop(snapshot);
4467
4468 self.transact(window, cx, |this, window, cx| {
4469 if clear_linked_edit_ranges {
4470 this.linked_edit_ranges.clear();
4471 }
4472 let initial_buffer_versions =
4473 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4474
4475 this.buffer.update(cx, |buffer, cx| {
4476 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4477 });
4478 for (buffer, edits) in linked_edits {
4479 buffer.update(cx, |buffer, cx| {
4480 let snapshot = buffer.snapshot();
4481 let edits = edits
4482 .into_iter()
4483 .map(|(range, text)| {
4484 use text::ToPoint as TP;
4485 let end_point = TP::to_point(&range.end, &snapshot);
4486 let start_point = TP::to_point(&range.start, &snapshot);
4487 (start_point..end_point, text)
4488 })
4489 .sorted_by_key(|(range, _)| range.start);
4490 buffer.edit(edits, None, cx);
4491 })
4492 }
4493 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4494 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4495 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4496 let new_selections =
4497 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4498 .zip(new_selection_deltas)
4499 .map(|(selection, delta)| Selection {
4500 id: selection.id,
4501 start: selection.start + delta,
4502 end: selection.end + delta,
4503 reversed: selection.reversed,
4504 goal: SelectionGoal::None,
4505 })
4506 .collect::<Vec<_>>();
4507
4508 let mut i = 0;
4509 for (position, delta, selection_id, pair) in new_autoclose_regions {
4510 let position = position.to_offset(map.buffer_snapshot()) + delta;
4511 let start = map.buffer_snapshot().anchor_before(position);
4512 let end = map.buffer_snapshot().anchor_after(position);
4513 while let Some(existing_state) = this.autoclose_regions.get(i) {
4514 match existing_state
4515 .range
4516 .start
4517 .cmp(&start, map.buffer_snapshot())
4518 {
4519 Ordering::Less => i += 1,
4520 Ordering::Greater => break,
4521 Ordering::Equal => {
4522 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4523 Ordering::Less => i += 1,
4524 Ordering::Equal => break,
4525 Ordering::Greater => break,
4526 }
4527 }
4528 }
4529 }
4530 this.autoclose_regions.insert(
4531 i,
4532 AutocloseRegion {
4533 selection_id,
4534 range: start..end,
4535 pair,
4536 },
4537 );
4538 }
4539
4540 let had_active_edit_prediction = this.has_active_edit_prediction();
4541 this.change_selections(
4542 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4543 window,
4544 cx,
4545 |s| s.select(new_selections),
4546 );
4547
4548 if !bracket_inserted
4549 && let Some(on_type_format_task) =
4550 this.trigger_on_type_formatting(text.to_string(), window, cx)
4551 {
4552 on_type_format_task.detach_and_log_err(cx);
4553 }
4554
4555 let editor_settings = EditorSettings::get_global(cx);
4556 if bracket_inserted
4557 && (editor_settings.auto_signature_help
4558 || editor_settings.show_signature_help_after_edits)
4559 {
4560 this.show_signature_help(&ShowSignatureHelp, window, cx);
4561 }
4562
4563 let trigger_in_words =
4564 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4565 if this.hard_wrap.is_some() {
4566 let latest: Range<Point> = this.selections.newest(&map).range();
4567 if latest.is_empty()
4568 && this
4569 .buffer()
4570 .read(cx)
4571 .snapshot(cx)
4572 .line_len(MultiBufferRow(latest.start.row))
4573 == latest.start.column
4574 {
4575 this.rewrap_impl(
4576 RewrapOptions {
4577 override_language_settings: true,
4578 preserve_existing_whitespace: true,
4579 },
4580 cx,
4581 )
4582 }
4583 }
4584 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4585 refresh_linked_ranges(this, window, cx);
4586 this.refresh_edit_prediction(true, false, window, cx);
4587 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4588 });
4589 }
4590
4591 fn find_possible_emoji_shortcode_at_position(
4592 snapshot: &MultiBufferSnapshot,
4593 position: Point,
4594 ) -> Option<String> {
4595 let mut chars = Vec::new();
4596 let mut found_colon = false;
4597 for char in snapshot.reversed_chars_at(position).take(100) {
4598 // Found a possible emoji shortcode in the middle of the buffer
4599 if found_colon {
4600 if char.is_whitespace() {
4601 chars.reverse();
4602 return Some(chars.iter().collect());
4603 }
4604 // If the previous character is not a whitespace, we are in the middle of a word
4605 // and we only want to complete the shortcode if the word is made up of other emojis
4606 let mut containing_word = String::new();
4607 for ch in snapshot
4608 .reversed_chars_at(position)
4609 .skip(chars.len() + 1)
4610 .take(100)
4611 {
4612 if ch.is_whitespace() {
4613 break;
4614 }
4615 containing_word.push(ch);
4616 }
4617 let containing_word = containing_word.chars().rev().collect::<String>();
4618 if util::word_consists_of_emojis(containing_word.as_str()) {
4619 chars.reverse();
4620 return Some(chars.iter().collect());
4621 }
4622 }
4623
4624 if char.is_whitespace() || !char.is_ascii() {
4625 return None;
4626 }
4627 if char == ':' {
4628 found_colon = true;
4629 } else {
4630 chars.push(char);
4631 }
4632 }
4633 // Found a possible emoji shortcode at the beginning of the buffer
4634 chars.reverse();
4635 Some(chars.iter().collect())
4636 }
4637
4638 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4639 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4640 self.transact(window, cx, |this, window, cx| {
4641 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4642 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4643 let multi_buffer = this.buffer.read(cx);
4644 let buffer = multi_buffer.snapshot(cx);
4645 selections
4646 .iter()
4647 .map(|selection| {
4648 let start_point = selection.start.to_point(&buffer);
4649 let mut existing_indent =
4650 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4651 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4652 let start = selection.start;
4653 let end = selection.end;
4654 let selection_is_empty = start == end;
4655 let language_scope = buffer.language_scope_at(start);
4656 let (
4657 comment_delimiter,
4658 doc_delimiter,
4659 insert_extra_newline,
4660 indent_on_newline,
4661 indent_on_extra_newline,
4662 ) = if let Some(language) = &language_scope {
4663 let mut insert_extra_newline =
4664 insert_extra_newline_brackets(&buffer, start..end, language)
4665 || insert_extra_newline_tree_sitter(&buffer, start..end);
4666
4667 // Comment extension on newline is allowed only for cursor selections
4668 let comment_delimiter = maybe!({
4669 if !selection_is_empty {
4670 return None;
4671 }
4672
4673 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4674 return None;
4675 }
4676
4677 let delimiters = language.line_comment_prefixes();
4678 let max_len_of_delimiter =
4679 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4680 let (snapshot, range) =
4681 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4682
4683 let num_of_whitespaces = snapshot
4684 .chars_for_range(range.clone())
4685 .take_while(|c| c.is_whitespace())
4686 .count();
4687 let comment_candidate = snapshot
4688 .chars_for_range(range.clone())
4689 .skip(num_of_whitespaces)
4690 .take(max_len_of_delimiter)
4691 .collect::<String>();
4692 let (delimiter, trimmed_len) = delimiters
4693 .iter()
4694 .filter_map(|delimiter| {
4695 let prefix = delimiter.trim_end();
4696 if comment_candidate.starts_with(prefix) {
4697 Some((delimiter, prefix.len()))
4698 } else {
4699 None
4700 }
4701 })
4702 .max_by_key(|(_, len)| *len)?;
4703
4704 if let Some(BlockCommentConfig {
4705 start: block_start, ..
4706 }) = language.block_comment()
4707 {
4708 let block_start_trimmed = block_start.trim_end();
4709 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4710 let line_content = snapshot
4711 .chars_for_range(range)
4712 .skip(num_of_whitespaces)
4713 .take(block_start_trimmed.len())
4714 .collect::<String>();
4715
4716 if line_content.starts_with(block_start_trimmed) {
4717 return None;
4718 }
4719 }
4720 }
4721
4722 let cursor_is_placed_after_comment_marker =
4723 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4724 if cursor_is_placed_after_comment_marker {
4725 Some(delimiter.clone())
4726 } else {
4727 None
4728 }
4729 });
4730
4731 let mut indent_on_newline = IndentSize::spaces(0);
4732 let mut indent_on_extra_newline = IndentSize::spaces(0);
4733
4734 let doc_delimiter = maybe!({
4735 if !selection_is_empty {
4736 return None;
4737 }
4738
4739 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4740 return None;
4741 }
4742
4743 let BlockCommentConfig {
4744 start: start_tag,
4745 end: end_tag,
4746 prefix: delimiter,
4747 tab_size: len,
4748 } = language.documentation_comment()?;
4749 let is_within_block_comment = buffer
4750 .language_scope_at(start_point)
4751 .is_some_and(|scope| scope.override_name() == Some("comment"));
4752 if !is_within_block_comment {
4753 return None;
4754 }
4755
4756 let (snapshot, range) =
4757 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4758
4759 let num_of_whitespaces = snapshot
4760 .chars_for_range(range.clone())
4761 .take_while(|c| c.is_whitespace())
4762 .count();
4763
4764 // 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.
4765 let column = start_point.column;
4766 let cursor_is_after_start_tag = {
4767 let start_tag_len = start_tag.len();
4768 let start_tag_line = snapshot
4769 .chars_for_range(range.clone())
4770 .skip(num_of_whitespaces)
4771 .take(start_tag_len)
4772 .collect::<String>();
4773 if start_tag_line.starts_with(start_tag.as_ref()) {
4774 num_of_whitespaces + start_tag_len <= column as usize
4775 } else {
4776 false
4777 }
4778 };
4779
4780 let cursor_is_after_delimiter = {
4781 let delimiter_trim = delimiter.trim_end();
4782 let delimiter_line = snapshot
4783 .chars_for_range(range.clone())
4784 .skip(num_of_whitespaces)
4785 .take(delimiter_trim.len())
4786 .collect::<String>();
4787 if delimiter_line.starts_with(delimiter_trim) {
4788 num_of_whitespaces + delimiter_trim.len() <= column as usize
4789 } else {
4790 false
4791 }
4792 };
4793
4794 let cursor_is_before_end_tag_if_exists = {
4795 let mut char_position = 0u32;
4796 let mut end_tag_offset = None;
4797
4798 'outer: for chunk in snapshot.text_for_range(range) {
4799 if let Some(byte_pos) = chunk.find(&**end_tag) {
4800 let chars_before_match =
4801 chunk[..byte_pos].chars().count() as u32;
4802 end_tag_offset =
4803 Some(char_position + chars_before_match);
4804 break 'outer;
4805 }
4806 char_position += chunk.chars().count() as u32;
4807 }
4808
4809 if let Some(end_tag_offset) = end_tag_offset {
4810 let cursor_is_before_end_tag = column <= end_tag_offset;
4811 if cursor_is_after_start_tag {
4812 if cursor_is_before_end_tag {
4813 insert_extra_newline = true;
4814 }
4815 let cursor_is_at_start_of_end_tag =
4816 column == end_tag_offset;
4817 if cursor_is_at_start_of_end_tag {
4818 indent_on_extra_newline.len = *len;
4819 }
4820 }
4821 cursor_is_before_end_tag
4822 } else {
4823 true
4824 }
4825 };
4826
4827 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4828 && cursor_is_before_end_tag_if_exists
4829 {
4830 if cursor_is_after_start_tag {
4831 indent_on_newline.len = *len;
4832 }
4833 Some(delimiter.clone())
4834 } else {
4835 None
4836 }
4837 });
4838
4839 (
4840 comment_delimiter,
4841 doc_delimiter,
4842 insert_extra_newline,
4843 indent_on_newline,
4844 indent_on_extra_newline,
4845 )
4846 } else {
4847 (
4848 None,
4849 None,
4850 false,
4851 IndentSize::default(),
4852 IndentSize::default(),
4853 )
4854 };
4855
4856 let prevent_auto_indent = doc_delimiter.is_some();
4857 let delimiter = comment_delimiter.or(doc_delimiter);
4858
4859 let capacity_for_delimiter =
4860 delimiter.as_deref().map(str::len).unwrap_or_default();
4861 let mut new_text = String::with_capacity(
4862 1 + capacity_for_delimiter
4863 + existing_indent.len as usize
4864 + indent_on_newline.len as usize
4865 + indent_on_extra_newline.len as usize,
4866 );
4867 new_text.push('\n');
4868 new_text.extend(existing_indent.chars());
4869 new_text.extend(indent_on_newline.chars());
4870
4871 if let Some(delimiter) = &delimiter {
4872 new_text.push_str(delimiter);
4873 }
4874
4875 if insert_extra_newline {
4876 new_text.push('\n');
4877 new_text.extend(existing_indent.chars());
4878 new_text.extend(indent_on_extra_newline.chars());
4879 }
4880
4881 let anchor = buffer.anchor_after(end);
4882 let new_selection = selection.map(|_| anchor);
4883 (
4884 ((start..end, new_text), prevent_auto_indent),
4885 (insert_extra_newline, new_selection),
4886 )
4887 })
4888 .unzip()
4889 };
4890
4891 let mut auto_indent_edits = Vec::new();
4892 let mut edits = Vec::new();
4893 for (edit, prevent_auto_indent) in edits_with_flags {
4894 if prevent_auto_indent {
4895 edits.push(edit);
4896 } else {
4897 auto_indent_edits.push(edit);
4898 }
4899 }
4900 if !edits.is_empty() {
4901 this.edit(edits, cx);
4902 }
4903 if !auto_indent_edits.is_empty() {
4904 this.edit_with_autoindent(auto_indent_edits, cx);
4905 }
4906
4907 let buffer = this.buffer.read(cx).snapshot(cx);
4908 let new_selections = selection_info
4909 .into_iter()
4910 .map(|(extra_newline_inserted, new_selection)| {
4911 let mut cursor = new_selection.end.to_point(&buffer);
4912 if extra_newline_inserted {
4913 cursor.row -= 1;
4914 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4915 }
4916 new_selection.map(|_| cursor)
4917 })
4918 .collect();
4919
4920 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4921 this.refresh_edit_prediction(true, false, window, cx);
4922 });
4923 }
4924
4925 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4926 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4927
4928 let buffer = self.buffer.read(cx);
4929 let snapshot = buffer.snapshot(cx);
4930
4931 let mut edits = Vec::new();
4932 let mut rows = Vec::new();
4933
4934 for (rows_inserted, selection) in self
4935 .selections
4936 .all_adjusted(&self.display_snapshot(cx))
4937 .into_iter()
4938 .enumerate()
4939 {
4940 let cursor = selection.head();
4941 let row = cursor.row;
4942
4943 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4944
4945 let newline = "\n".to_string();
4946 edits.push((start_of_line..start_of_line, newline));
4947
4948 rows.push(row + rows_inserted as u32);
4949 }
4950
4951 self.transact(window, cx, |editor, window, cx| {
4952 editor.edit(edits, cx);
4953
4954 editor.change_selections(Default::default(), window, cx, |s| {
4955 let mut index = 0;
4956 s.move_cursors_with(|map, _, _| {
4957 let row = rows[index];
4958 index += 1;
4959
4960 let point = Point::new(row, 0);
4961 let boundary = map.next_line_boundary(point).1;
4962 let clipped = map.clip_point(boundary, Bias::Left);
4963
4964 (clipped, SelectionGoal::None)
4965 });
4966 });
4967
4968 let mut indent_edits = Vec::new();
4969 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4970 for row in rows {
4971 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4972 for (row, indent) in indents {
4973 if indent.len == 0 {
4974 continue;
4975 }
4976
4977 let text = match indent.kind {
4978 IndentKind::Space => " ".repeat(indent.len as usize),
4979 IndentKind::Tab => "\t".repeat(indent.len as usize),
4980 };
4981 let point = Point::new(row.0, 0);
4982 indent_edits.push((point..point, text));
4983 }
4984 }
4985 editor.edit(indent_edits, cx);
4986 });
4987 }
4988
4989 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4990 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4991
4992 let buffer = self.buffer.read(cx);
4993 let snapshot = buffer.snapshot(cx);
4994
4995 let mut edits = Vec::new();
4996 let mut rows = Vec::new();
4997 let mut rows_inserted = 0;
4998
4999 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5000 let cursor = selection.head();
5001 let row = cursor.row;
5002
5003 let point = Point::new(row + 1, 0);
5004 let start_of_line = snapshot.clip_point(point, Bias::Left);
5005
5006 let newline = "\n".to_string();
5007 edits.push((start_of_line..start_of_line, newline));
5008
5009 rows_inserted += 1;
5010 rows.push(row + rows_inserted);
5011 }
5012
5013 self.transact(window, cx, |editor, window, cx| {
5014 editor.edit(edits, cx);
5015
5016 editor.change_selections(Default::default(), window, cx, |s| {
5017 let mut index = 0;
5018 s.move_cursors_with(|map, _, _| {
5019 let row = rows[index];
5020 index += 1;
5021
5022 let point = Point::new(row, 0);
5023 let boundary = map.next_line_boundary(point).1;
5024 let clipped = map.clip_point(boundary, Bias::Left);
5025
5026 (clipped, SelectionGoal::None)
5027 });
5028 });
5029
5030 let mut indent_edits = Vec::new();
5031 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5032 for row in rows {
5033 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5034 for (row, indent) in indents {
5035 if indent.len == 0 {
5036 continue;
5037 }
5038
5039 let text = match indent.kind {
5040 IndentKind::Space => " ".repeat(indent.len as usize),
5041 IndentKind::Tab => "\t".repeat(indent.len as usize),
5042 };
5043 let point = Point::new(row.0, 0);
5044 indent_edits.push((point..point, text));
5045 }
5046 }
5047 editor.edit(indent_edits, cx);
5048 });
5049 }
5050
5051 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5052 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5053 original_indent_columns: Vec::new(),
5054 });
5055 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5056 }
5057
5058 fn insert_with_autoindent_mode(
5059 &mut self,
5060 text: &str,
5061 autoindent_mode: Option<AutoindentMode>,
5062 window: &mut Window,
5063 cx: &mut Context<Self>,
5064 ) {
5065 if self.read_only(cx) {
5066 return;
5067 }
5068
5069 let text: Arc<str> = text.into();
5070 self.transact(window, cx, |this, window, cx| {
5071 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5072 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5073 let anchors = {
5074 let snapshot = buffer.read(cx);
5075 old_selections
5076 .iter()
5077 .map(|s| {
5078 let anchor = snapshot.anchor_after(s.head());
5079 s.map(|_| anchor)
5080 })
5081 .collect::<Vec<_>>()
5082 };
5083 buffer.edit(
5084 old_selections
5085 .iter()
5086 .map(|s| (s.start..s.end, text.clone())),
5087 autoindent_mode,
5088 cx,
5089 );
5090 anchors
5091 });
5092
5093 this.change_selections(Default::default(), window, cx, |s| {
5094 s.select_anchors(selection_anchors);
5095 });
5096
5097 cx.notify();
5098 });
5099 }
5100
5101 fn trigger_completion_on_input(
5102 &mut self,
5103 text: &str,
5104 trigger_in_words: bool,
5105 window: &mut Window,
5106 cx: &mut Context<Self>,
5107 ) {
5108 let completions_source = self
5109 .context_menu
5110 .borrow()
5111 .as_ref()
5112 .and_then(|menu| match menu {
5113 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5114 CodeContextMenu::CodeActions(_) => None,
5115 });
5116
5117 match completions_source {
5118 Some(CompletionsMenuSource::Words { .. }) => {
5119 self.open_or_update_completions_menu(
5120 Some(CompletionsMenuSource::Words {
5121 ignore_threshold: false,
5122 }),
5123 None,
5124 trigger_in_words,
5125 window,
5126 cx,
5127 );
5128 }
5129 _ => self.open_or_update_completions_menu(
5130 None,
5131 Some(text.to_owned()).filter(|x| !x.is_empty()),
5132 true,
5133 window,
5134 cx,
5135 ),
5136 }
5137 }
5138
5139 /// If any empty selections is touching the start of its innermost containing autoclose
5140 /// region, expand it to select the brackets.
5141 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5142 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5143 let buffer = self.buffer.read(cx).read(cx);
5144 let new_selections = self
5145 .selections_with_autoclose_regions(selections, &buffer)
5146 .map(|(mut selection, region)| {
5147 if !selection.is_empty() {
5148 return selection;
5149 }
5150
5151 if let Some(region) = region {
5152 let mut range = region.range.to_offset(&buffer);
5153 if selection.start == range.start && range.start >= region.pair.start.len() {
5154 range.start -= region.pair.start.len();
5155 if buffer.contains_str_at(range.start, ®ion.pair.start)
5156 && buffer.contains_str_at(range.end, ®ion.pair.end)
5157 {
5158 range.end += region.pair.end.len();
5159 selection.start = range.start;
5160 selection.end = range.end;
5161
5162 return selection;
5163 }
5164 }
5165 }
5166
5167 let always_treat_brackets_as_autoclosed = buffer
5168 .language_settings_at(selection.start, cx)
5169 .always_treat_brackets_as_autoclosed;
5170
5171 if !always_treat_brackets_as_autoclosed {
5172 return selection;
5173 }
5174
5175 if let Some(scope) = buffer.language_scope_at(selection.start) {
5176 for (pair, enabled) in scope.brackets() {
5177 if !enabled || !pair.close {
5178 continue;
5179 }
5180
5181 if buffer.contains_str_at(selection.start, &pair.end) {
5182 let pair_start_len = pair.start.len();
5183 if buffer.contains_str_at(
5184 selection.start.saturating_sub(pair_start_len),
5185 &pair.start,
5186 ) {
5187 selection.start -= pair_start_len;
5188 selection.end += pair.end.len();
5189
5190 return selection;
5191 }
5192 }
5193 }
5194 }
5195
5196 selection
5197 })
5198 .collect();
5199
5200 drop(buffer);
5201 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5202 selections.select(new_selections)
5203 });
5204 }
5205
5206 /// Iterate the given selections, and for each one, find the smallest surrounding
5207 /// autoclose region. This uses the ordering of the selections and the autoclose
5208 /// regions to avoid repeated comparisons.
5209 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5210 &'a self,
5211 selections: impl IntoIterator<Item = Selection<D>>,
5212 buffer: &'a MultiBufferSnapshot,
5213 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5214 let mut i = 0;
5215 let mut regions = self.autoclose_regions.as_slice();
5216 selections.into_iter().map(move |selection| {
5217 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5218
5219 let mut enclosing = None;
5220 while let Some(pair_state) = regions.get(i) {
5221 if pair_state.range.end.to_offset(buffer) < range.start {
5222 regions = ®ions[i + 1..];
5223 i = 0;
5224 } else if pair_state.range.start.to_offset(buffer) > range.end {
5225 break;
5226 } else {
5227 if pair_state.selection_id == selection.id {
5228 enclosing = Some(pair_state);
5229 }
5230 i += 1;
5231 }
5232 }
5233
5234 (selection, enclosing)
5235 })
5236 }
5237
5238 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5239 fn invalidate_autoclose_regions(
5240 &mut self,
5241 mut selections: &[Selection<Anchor>],
5242 buffer: &MultiBufferSnapshot,
5243 ) {
5244 self.autoclose_regions.retain(|state| {
5245 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5246 return false;
5247 }
5248
5249 let mut i = 0;
5250 while let Some(selection) = selections.get(i) {
5251 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5252 selections = &selections[1..];
5253 continue;
5254 }
5255 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5256 break;
5257 }
5258 if selection.id == state.selection_id {
5259 return true;
5260 } else {
5261 i += 1;
5262 }
5263 }
5264 false
5265 });
5266 }
5267
5268 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5269 let offset = position.to_offset(buffer);
5270 let (word_range, kind) =
5271 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5272 if offset > word_range.start && kind == Some(CharKind::Word) {
5273 Some(
5274 buffer
5275 .text_for_range(word_range.start..offset)
5276 .collect::<String>(),
5277 )
5278 } else {
5279 None
5280 }
5281 }
5282
5283 pub fn visible_excerpts(
5284 &self,
5285 cx: &mut Context<Editor>,
5286 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5287 let Some(project) = self.project() else {
5288 return HashMap::default();
5289 };
5290 let project = project.read(cx);
5291 let multi_buffer = self.buffer().read(cx);
5292 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5293 let multi_buffer_visible_start = self
5294 .scroll_manager
5295 .anchor()
5296 .anchor
5297 .to_point(&multi_buffer_snapshot);
5298 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5299 multi_buffer_visible_start
5300 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5301 Bias::Left,
5302 );
5303 multi_buffer_snapshot
5304 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5305 .into_iter()
5306 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5307 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5308 let buffer_file = project::File::from_dyn(buffer.file())?;
5309 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5310 let worktree_entry = buffer_worktree
5311 .read(cx)
5312 .entry_for_id(buffer_file.project_entry_id()?)?;
5313 if worktree_entry.is_ignored {
5314 None
5315 } else {
5316 Some((
5317 excerpt_id,
5318 (
5319 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5320 buffer.version().clone(),
5321 excerpt_visible_range,
5322 ),
5323 ))
5324 }
5325 })
5326 .collect()
5327 }
5328
5329 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5330 TextLayoutDetails {
5331 text_system: window.text_system().clone(),
5332 editor_style: self.style.clone().unwrap(),
5333 rem_size: window.rem_size(),
5334 scroll_anchor: self.scroll_manager.anchor(),
5335 visible_rows: self.visible_line_count(),
5336 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5337 }
5338 }
5339
5340 fn trigger_on_type_formatting(
5341 &self,
5342 input: String,
5343 window: &mut Window,
5344 cx: &mut Context<Self>,
5345 ) -> Option<Task<Result<()>>> {
5346 if input.len() != 1 {
5347 return None;
5348 }
5349
5350 let project = self.project()?;
5351 let position = self.selections.newest_anchor().head();
5352 let (buffer, buffer_position) = self
5353 .buffer
5354 .read(cx)
5355 .text_anchor_for_position(position, cx)?;
5356
5357 let settings = language_settings::language_settings(
5358 buffer
5359 .read(cx)
5360 .language_at(buffer_position)
5361 .map(|l| l.name()),
5362 buffer.read(cx).file(),
5363 cx,
5364 );
5365 if !settings.use_on_type_format {
5366 return None;
5367 }
5368
5369 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5370 // hence we do LSP request & edit on host side only — add formats to host's history.
5371 let push_to_lsp_host_history = true;
5372 // If this is not the host, append its history with new edits.
5373 let push_to_client_history = project.read(cx).is_via_collab();
5374
5375 let on_type_formatting = project.update(cx, |project, cx| {
5376 project.on_type_format(
5377 buffer.clone(),
5378 buffer_position,
5379 input,
5380 push_to_lsp_host_history,
5381 cx,
5382 )
5383 });
5384 Some(cx.spawn_in(window, async move |editor, cx| {
5385 if let Some(transaction) = on_type_formatting.await? {
5386 if push_to_client_history {
5387 buffer
5388 .update(cx, |buffer, _| {
5389 buffer.push_transaction(transaction, Instant::now());
5390 buffer.finalize_last_transaction();
5391 })
5392 .ok();
5393 }
5394 editor.update(cx, |editor, cx| {
5395 editor.refresh_document_highlights(cx);
5396 })?;
5397 }
5398 Ok(())
5399 }))
5400 }
5401
5402 pub fn show_word_completions(
5403 &mut self,
5404 _: &ShowWordCompletions,
5405 window: &mut Window,
5406 cx: &mut Context<Self>,
5407 ) {
5408 self.open_or_update_completions_menu(
5409 Some(CompletionsMenuSource::Words {
5410 ignore_threshold: true,
5411 }),
5412 None,
5413 false,
5414 window,
5415 cx,
5416 );
5417 }
5418
5419 pub fn show_completions(
5420 &mut self,
5421 _: &ShowCompletions,
5422 window: &mut Window,
5423 cx: &mut Context<Self>,
5424 ) {
5425 self.open_or_update_completions_menu(None, None, false, window, cx);
5426 }
5427
5428 fn open_or_update_completions_menu(
5429 &mut self,
5430 requested_source: Option<CompletionsMenuSource>,
5431 trigger: Option<String>,
5432 trigger_in_words: bool,
5433 window: &mut Window,
5434 cx: &mut Context<Self>,
5435 ) {
5436 if self.pending_rename.is_some() {
5437 return;
5438 }
5439
5440 let completions_source = self
5441 .context_menu
5442 .borrow()
5443 .as_ref()
5444 .and_then(|menu| match menu {
5445 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5446 CodeContextMenu::CodeActions(_) => None,
5447 });
5448
5449 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5450
5451 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5452 // inserted and selected. To handle that case, the start of the selection is used so that
5453 // the menu starts with all choices.
5454 let position = self
5455 .selections
5456 .newest_anchor()
5457 .start
5458 .bias_right(&multibuffer_snapshot);
5459 if position.diff_base_anchor.is_some() {
5460 return;
5461 }
5462 let buffer_position = multibuffer_snapshot.anchor_before(position);
5463 let Some(buffer) = buffer_position
5464 .buffer_id
5465 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5466 else {
5467 return;
5468 };
5469 let buffer_snapshot = buffer.read(cx).snapshot();
5470
5471 let query: Option<Arc<String>> =
5472 Self::completion_query(&multibuffer_snapshot, buffer_position)
5473 .map(|query| query.into());
5474
5475 drop(multibuffer_snapshot);
5476
5477 // Hide the current completions menu when query is empty. Without this, cached
5478 // completions from before the trigger char may be reused (#32774).
5479 if query.is_none() {
5480 let menu_is_open = matches!(
5481 self.context_menu.borrow().as_ref(),
5482 Some(CodeContextMenu::Completions(_))
5483 );
5484 if menu_is_open {
5485 self.hide_context_menu(window, cx);
5486 }
5487 }
5488
5489 let mut ignore_word_threshold = false;
5490 let provider = match requested_source {
5491 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5492 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5493 ignore_word_threshold = ignore_threshold;
5494 None
5495 }
5496 Some(CompletionsMenuSource::SnippetChoices)
5497 | Some(CompletionsMenuSource::SnippetsOnly) => {
5498 log::error!("bug: SnippetChoices requested_source is not handled");
5499 None
5500 }
5501 };
5502
5503 let sort_completions = provider
5504 .as_ref()
5505 .is_some_and(|provider| provider.sort_completions());
5506
5507 let filter_completions = provider
5508 .as_ref()
5509 .is_none_or(|provider| provider.filter_completions());
5510
5511 let was_snippets_only = matches!(
5512 completions_source,
5513 Some(CompletionsMenuSource::SnippetsOnly)
5514 );
5515
5516 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5517 if filter_completions {
5518 menu.filter(
5519 query.clone().unwrap_or_default(),
5520 buffer_position.text_anchor,
5521 &buffer,
5522 provider.clone(),
5523 window,
5524 cx,
5525 );
5526 }
5527 // When `is_incomplete` is false, no need to re-query completions when the current query
5528 // is a suffix of the initial query.
5529 let was_complete = !menu.is_incomplete;
5530 if was_complete && !was_snippets_only {
5531 // If the new query is a suffix of the old query (typing more characters) and
5532 // the previous result was complete, the existing completions can be filtered.
5533 //
5534 // Note that snippet completions are always complete.
5535 let query_matches = match (&menu.initial_query, &query) {
5536 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5537 (None, _) => true,
5538 _ => false,
5539 };
5540 if query_matches {
5541 let position_matches = if menu.initial_position == position {
5542 true
5543 } else {
5544 let snapshot = self.buffer.read(cx).read(cx);
5545 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5546 };
5547 if position_matches {
5548 return;
5549 }
5550 }
5551 }
5552 };
5553
5554 let Anchor {
5555 excerpt_id: buffer_excerpt_id,
5556 text_anchor: buffer_position,
5557 ..
5558 } = buffer_position;
5559
5560 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5561 buffer_snapshot.surrounding_word(buffer_position, None)
5562 {
5563 let word_to_exclude = buffer_snapshot
5564 .text_for_range(word_range.clone())
5565 .collect::<String>();
5566 (
5567 buffer_snapshot.anchor_before(word_range.start)
5568 ..buffer_snapshot.anchor_after(buffer_position),
5569 Some(word_to_exclude),
5570 )
5571 } else {
5572 (buffer_position..buffer_position, None)
5573 };
5574
5575 let language = buffer_snapshot
5576 .language_at(buffer_position)
5577 .map(|language| language.name());
5578
5579 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5580 .completions
5581 .clone();
5582
5583 let show_completion_documentation = buffer_snapshot
5584 .settings_at(buffer_position, cx)
5585 .show_completion_documentation;
5586
5587 // The document can be large, so stay in reasonable bounds when searching for words,
5588 // otherwise completion pop-up might be slow to appear.
5589 const WORD_LOOKUP_ROWS: u32 = 5_000;
5590 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5591 let min_word_search = buffer_snapshot.clip_point(
5592 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5593 Bias::Left,
5594 );
5595 let max_word_search = buffer_snapshot.clip_point(
5596 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5597 Bias::Right,
5598 );
5599 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5600 ..buffer_snapshot.point_to_offset(max_word_search);
5601
5602 let skip_digits = query
5603 .as_ref()
5604 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5605
5606 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5607 trigger.as_ref().is_none_or(|trigger| {
5608 provider.is_completion_trigger(
5609 &buffer,
5610 position.text_anchor,
5611 trigger,
5612 trigger_in_words,
5613 completions_source.is_some(),
5614 cx,
5615 )
5616 })
5617 });
5618
5619 let provider_responses = if let Some(provider) = &provider
5620 && load_provider_completions
5621 {
5622 let trigger_character =
5623 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5624 let completion_context = CompletionContext {
5625 trigger_kind: match &trigger_character {
5626 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5627 None => CompletionTriggerKind::INVOKED,
5628 },
5629 trigger_character,
5630 };
5631
5632 provider.completions(
5633 buffer_excerpt_id,
5634 &buffer,
5635 buffer_position,
5636 completion_context,
5637 window,
5638 cx,
5639 )
5640 } else {
5641 Task::ready(Ok(Vec::new()))
5642 };
5643
5644 let load_word_completions = if !self.word_completions_enabled {
5645 false
5646 } else if requested_source
5647 == Some(CompletionsMenuSource::Words {
5648 ignore_threshold: true,
5649 })
5650 {
5651 true
5652 } else {
5653 load_provider_completions
5654 && completion_settings.words != WordsCompletionMode::Disabled
5655 && (ignore_word_threshold || {
5656 let words_min_length = completion_settings.words_min_length;
5657 // check whether word has at least `words_min_length` characters
5658 let query_chars = query.iter().flat_map(|q| q.chars());
5659 query_chars.take(words_min_length).count() == words_min_length
5660 })
5661 };
5662
5663 let mut words = if load_word_completions {
5664 cx.background_spawn({
5665 let buffer_snapshot = buffer_snapshot.clone();
5666 async move {
5667 buffer_snapshot.words_in_range(WordsQuery {
5668 fuzzy_contents: None,
5669 range: word_search_range,
5670 skip_digits,
5671 })
5672 }
5673 })
5674 } else {
5675 Task::ready(BTreeMap::default())
5676 };
5677
5678 let snippets = if let Some(provider) = &provider
5679 && provider.show_snippets()
5680 && let Some(project) = self.project()
5681 {
5682 let char_classifier = buffer_snapshot
5683 .char_classifier_at(buffer_position)
5684 .scope_context(Some(CharScopeContext::Completion));
5685 project.update(cx, |project, cx| {
5686 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5687 })
5688 } else {
5689 Task::ready(Ok(CompletionResponse {
5690 completions: Vec::new(),
5691 display_options: Default::default(),
5692 is_incomplete: false,
5693 }))
5694 };
5695
5696 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5697
5698 let id = post_inc(&mut self.next_completion_id);
5699 let task = cx.spawn_in(window, async move |editor, cx| {
5700 let Ok(()) = editor.update(cx, |this, _| {
5701 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5702 }) else {
5703 return;
5704 };
5705
5706 // TODO: Ideally completions from different sources would be selectively re-queried, so
5707 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5708 let mut completions = Vec::new();
5709 let mut is_incomplete = false;
5710 let mut display_options: Option<CompletionDisplayOptions> = None;
5711 if let Some(provider_responses) = provider_responses.await.log_err()
5712 && !provider_responses.is_empty()
5713 {
5714 for response in provider_responses {
5715 completions.extend(response.completions);
5716 is_incomplete = is_incomplete || response.is_incomplete;
5717 match display_options.as_mut() {
5718 None => {
5719 display_options = Some(response.display_options);
5720 }
5721 Some(options) => options.merge(&response.display_options),
5722 }
5723 }
5724 if completion_settings.words == WordsCompletionMode::Fallback {
5725 words = Task::ready(BTreeMap::default());
5726 }
5727 }
5728 let display_options = display_options.unwrap_or_default();
5729
5730 let mut words = words.await;
5731 if let Some(word_to_exclude) = &word_to_exclude {
5732 words.remove(word_to_exclude);
5733 }
5734 for lsp_completion in &completions {
5735 words.remove(&lsp_completion.new_text);
5736 }
5737 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5738 replace_range: word_replace_range.clone(),
5739 new_text: word.clone(),
5740 label: CodeLabel::plain(word, None),
5741 match_start: None,
5742 snippet_deduplication_key: None,
5743 icon_path: None,
5744 documentation: None,
5745 source: CompletionSource::BufferWord {
5746 word_range,
5747 resolved: false,
5748 },
5749 insert_text_mode: Some(InsertTextMode::AS_IS),
5750 confirm: None,
5751 }));
5752
5753 completions.extend(
5754 snippets
5755 .await
5756 .into_iter()
5757 .flat_map(|response| response.completions),
5758 );
5759
5760 let menu = if completions.is_empty() {
5761 None
5762 } else {
5763 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5764 let languages = editor
5765 .workspace
5766 .as_ref()
5767 .and_then(|(workspace, _)| workspace.upgrade())
5768 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5769 let menu = CompletionsMenu::new(
5770 id,
5771 requested_source.unwrap_or(if load_provider_completions {
5772 CompletionsMenuSource::Normal
5773 } else {
5774 CompletionsMenuSource::SnippetsOnly
5775 }),
5776 sort_completions,
5777 show_completion_documentation,
5778 position,
5779 query.clone(),
5780 is_incomplete,
5781 buffer.clone(),
5782 completions.into(),
5783 display_options,
5784 snippet_sort_order,
5785 languages,
5786 language,
5787 cx,
5788 );
5789
5790 let query = if filter_completions { query } else { None };
5791 let matches_task = menu.do_async_filtering(
5792 query.unwrap_or_default(),
5793 buffer_position,
5794 &buffer,
5795 cx,
5796 );
5797 (menu, matches_task)
5798 }) else {
5799 return;
5800 };
5801
5802 let matches = matches_task.await;
5803
5804 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5805 // Newer menu already set, so exit.
5806 if let Some(CodeContextMenu::Completions(prev_menu)) =
5807 editor.context_menu.borrow().as_ref()
5808 && prev_menu.id > id
5809 {
5810 return;
5811 };
5812
5813 // Only valid to take prev_menu because either the new menu is immediately set
5814 // below, or the menu is hidden.
5815 if let Some(CodeContextMenu::Completions(prev_menu)) =
5816 editor.context_menu.borrow_mut().take()
5817 {
5818 let position_matches =
5819 if prev_menu.initial_position == menu.initial_position {
5820 true
5821 } else {
5822 let snapshot = editor.buffer.read(cx).read(cx);
5823 prev_menu.initial_position.to_offset(&snapshot)
5824 == menu.initial_position.to_offset(&snapshot)
5825 };
5826 if position_matches {
5827 // Preserve markdown cache before `set_filter_results` because it will
5828 // try to populate the documentation cache.
5829 menu.preserve_markdown_cache(prev_menu);
5830 }
5831 };
5832
5833 menu.set_filter_results(matches, provider, window, cx);
5834 }) else {
5835 return;
5836 };
5837
5838 menu.visible().then_some(menu)
5839 };
5840
5841 editor
5842 .update_in(cx, |editor, window, cx| {
5843 if editor.focus_handle.is_focused(window)
5844 && let Some(menu) = menu
5845 {
5846 *editor.context_menu.borrow_mut() =
5847 Some(CodeContextMenu::Completions(menu));
5848
5849 crate::hover_popover::hide_hover(editor, cx);
5850 if editor.show_edit_predictions_in_menu() {
5851 editor.update_visible_edit_prediction(window, cx);
5852 } else {
5853 editor.discard_edit_prediction(false, cx);
5854 }
5855
5856 cx.notify();
5857 return;
5858 }
5859
5860 if editor.completion_tasks.len() <= 1 {
5861 // If there are no more completion tasks and the last menu was empty, we should hide it.
5862 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5863 // If it was already hidden and we don't show edit predictions in the menu,
5864 // we should also show the edit prediction when available.
5865 if was_hidden && editor.show_edit_predictions_in_menu() {
5866 editor.update_visible_edit_prediction(window, cx);
5867 }
5868 }
5869 })
5870 .ok();
5871 });
5872
5873 self.completion_tasks.push((id, task));
5874 }
5875
5876 #[cfg(feature = "test-support")]
5877 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5878 let menu = self.context_menu.borrow();
5879 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5880 let completions = menu.completions.borrow();
5881 Some(completions.to_vec())
5882 } else {
5883 None
5884 }
5885 }
5886
5887 pub fn with_completions_menu_matching_id<R>(
5888 &self,
5889 id: CompletionId,
5890 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5891 ) -> R {
5892 let mut context_menu = self.context_menu.borrow_mut();
5893 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5894 return f(None);
5895 };
5896 if completions_menu.id != id {
5897 return f(None);
5898 }
5899 f(Some(completions_menu))
5900 }
5901
5902 pub fn confirm_completion(
5903 &mut self,
5904 action: &ConfirmCompletion,
5905 window: &mut Window,
5906 cx: &mut Context<Self>,
5907 ) -> Option<Task<Result<()>>> {
5908 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5909 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5910 }
5911
5912 pub fn confirm_completion_insert(
5913 &mut self,
5914 _: &ConfirmCompletionInsert,
5915 window: &mut Window,
5916 cx: &mut Context<Self>,
5917 ) -> Option<Task<Result<()>>> {
5918 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5919 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5920 }
5921
5922 pub fn confirm_completion_replace(
5923 &mut self,
5924 _: &ConfirmCompletionReplace,
5925 window: &mut Window,
5926 cx: &mut Context<Self>,
5927 ) -> Option<Task<Result<()>>> {
5928 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5929 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5930 }
5931
5932 pub fn compose_completion(
5933 &mut self,
5934 action: &ComposeCompletion,
5935 window: &mut Window,
5936 cx: &mut Context<Self>,
5937 ) -> Option<Task<Result<()>>> {
5938 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5939 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5940 }
5941
5942 fn do_completion(
5943 &mut self,
5944 item_ix: Option<usize>,
5945 intent: CompletionIntent,
5946 window: &mut Window,
5947 cx: &mut Context<Editor>,
5948 ) -> Option<Task<Result<()>>> {
5949 use language::ToOffset as _;
5950
5951 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5952 else {
5953 return None;
5954 };
5955
5956 let candidate_id = {
5957 let entries = completions_menu.entries.borrow();
5958 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5959 if self.show_edit_predictions_in_menu() {
5960 self.discard_edit_prediction(true, cx);
5961 }
5962 mat.candidate_id
5963 };
5964
5965 let completion = completions_menu
5966 .completions
5967 .borrow()
5968 .get(candidate_id)?
5969 .clone();
5970 cx.stop_propagation();
5971
5972 let buffer_handle = completions_menu.buffer.clone();
5973
5974 let CompletionEdit {
5975 new_text,
5976 snippet,
5977 replace_range,
5978 } = process_completion_for_edit(
5979 &completion,
5980 intent,
5981 &buffer_handle,
5982 &completions_menu.initial_position.text_anchor,
5983 cx,
5984 );
5985
5986 let buffer = buffer_handle.read(cx);
5987 let snapshot = self.buffer.read(cx).snapshot(cx);
5988 let newest_anchor = self.selections.newest_anchor();
5989 let replace_range_multibuffer = {
5990 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5991 excerpt.map_range_from_buffer(replace_range.clone())
5992 };
5993 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5994 return None;
5995 }
5996
5997 let old_text = buffer
5998 .text_for_range(replace_range.clone())
5999 .collect::<String>();
6000 let lookbehind = newest_anchor
6001 .start
6002 .text_anchor
6003 .to_offset(buffer)
6004 .saturating_sub(replace_range.start);
6005 let lookahead = replace_range
6006 .end
6007 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6008 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6009 let suffix = &old_text[lookbehind.min(old_text.len())..];
6010
6011 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
6012 let mut ranges = Vec::new();
6013 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6014
6015 for selection in &selections {
6016 let range = if selection.id == newest_anchor.id {
6017 replace_range_multibuffer.clone()
6018 } else {
6019 let mut range = selection.range();
6020
6021 // if prefix is present, don't duplicate it
6022 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6023 range.start = range.start.saturating_sub(lookbehind);
6024
6025 // if suffix is also present, mimic the newest cursor and replace it
6026 if selection.id != newest_anchor.id
6027 && snapshot.contains_str_at(range.end, suffix)
6028 {
6029 range.end += lookahead;
6030 }
6031 }
6032 range
6033 };
6034
6035 ranges.push(range.clone());
6036
6037 if !self.linked_edit_ranges.is_empty() {
6038 let start_anchor = snapshot.anchor_before(range.start);
6039 let end_anchor = snapshot.anchor_after(range.end);
6040 if let Some(ranges) = self
6041 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6042 {
6043 for (buffer, edits) in ranges {
6044 linked_edits
6045 .entry(buffer.clone())
6046 .or_default()
6047 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6048 }
6049 }
6050 }
6051 }
6052
6053 let common_prefix_len = old_text
6054 .chars()
6055 .zip(new_text.chars())
6056 .take_while(|(a, b)| a == b)
6057 .map(|(a, _)| a.len_utf8())
6058 .sum::<usize>();
6059
6060 cx.emit(EditorEvent::InputHandled {
6061 utf16_range_to_replace: None,
6062 text: new_text[common_prefix_len..].into(),
6063 });
6064
6065 self.transact(window, cx, |editor, window, cx| {
6066 if let Some(mut snippet) = snippet {
6067 snippet.text = new_text.to_string();
6068 editor
6069 .insert_snippet(&ranges, snippet, window, cx)
6070 .log_err();
6071 } else {
6072 editor.buffer.update(cx, |multi_buffer, cx| {
6073 let auto_indent = match completion.insert_text_mode {
6074 Some(InsertTextMode::AS_IS) => None,
6075 _ => editor.autoindent_mode.clone(),
6076 };
6077 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6078 multi_buffer.edit(edits, auto_indent, cx);
6079 });
6080 }
6081 for (buffer, edits) in linked_edits {
6082 buffer.update(cx, |buffer, cx| {
6083 let snapshot = buffer.snapshot();
6084 let edits = edits
6085 .into_iter()
6086 .map(|(range, text)| {
6087 use text::ToPoint as TP;
6088 let end_point = TP::to_point(&range.end, &snapshot);
6089 let start_point = TP::to_point(&range.start, &snapshot);
6090 (start_point..end_point, text)
6091 })
6092 .sorted_by_key(|(range, _)| range.start);
6093 buffer.edit(edits, None, cx);
6094 })
6095 }
6096
6097 editor.refresh_edit_prediction(true, false, window, cx);
6098 });
6099 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6100
6101 let show_new_completions_on_confirm = completion
6102 .confirm
6103 .as_ref()
6104 .is_some_and(|confirm| confirm(intent, window, cx));
6105 if show_new_completions_on_confirm {
6106 self.open_or_update_completions_menu(None, None, false, window, cx);
6107 }
6108
6109 let provider = self.completion_provider.as_ref()?;
6110 drop(completion);
6111 let apply_edits = provider.apply_additional_edits_for_completion(
6112 buffer_handle,
6113 completions_menu.completions.clone(),
6114 candidate_id,
6115 true,
6116 cx,
6117 );
6118
6119 let editor_settings = EditorSettings::get_global(cx);
6120 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6121 // After the code completion is finished, users often want to know what signatures are needed.
6122 // so we should automatically call signature_help
6123 self.show_signature_help(&ShowSignatureHelp, window, cx);
6124 }
6125
6126 Some(cx.foreground_executor().spawn(async move {
6127 apply_edits.await?;
6128 Ok(())
6129 }))
6130 }
6131
6132 pub fn toggle_code_actions(
6133 &mut self,
6134 action: &ToggleCodeActions,
6135 window: &mut Window,
6136 cx: &mut Context<Self>,
6137 ) {
6138 let quick_launch = action.quick_launch;
6139 let mut context_menu = self.context_menu.borrow_mut();
6140 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6141 if code_actions.deployed_from == action.deployed_from {
6142 // Toggle if we're selecting the same one
6143 *context_menu = None;
6144 cx.notify();
6145 return;
6146 } else {
6147 // Otherwise, clear it and start a new one
6148 *context_menu = None;
6149 cx.notify();
6150 }
6151 }
6152 drop(context_menu);
6153 let snapshot = self.snapshot(window, cx);
6154 let deployed_from = action.deployed_from.clone();
6155 let action = action.clone();
6156 self.completion_tasks.clear();
6157 self.discard_edit_prediction(false, cx);
6158
6159 let multibuffer_point = match &action.deployed_from {
6160 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6161 DisplayPoint::new(*row, 0).to_point(&snapshot)
6162 }
6163 _ => self
6164 .selections
6165 .newest::<Point>(&snapshot.display_snapshot)
6166 .head(),
6167 };
6168 let Some((buffer, buffer_row)) = snapshot
6169 .buffer_snapshot()
6170 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6171 .and_then(|(buffer_snapshot, range)| {
6172 self.buffer()
6173 .read(cx)
6174 .buffer(buffer_snapshot.remote_id())
6175 .map(|buffer| (buffer, range.start.row))
6176 })
6177 else {
6178 return;
6179 };
6180 let buffer_id = buffer.read(cx).remote_id();
6181 let tasks = self
6182 .tasks
6183 .get(&(buffer_id, buffer_row))
6184 .map(|t| Arc::new(t.to_owned()));
6185
6186 if !self.focus_handle.is_focused(window) {
6187 return;
6188 }
6189 let project = self.project.clone();
6190
6191 let code_actions_task = match deployed_from {
6192 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6193 _ => self.code_actions(buffer_row, window, cx),
6194 };
6195
6196 let runnable_task = match deployed_from {
6197 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6198 _ => {
6199 let mut task_context_task = Task::ready(None);
6200 if let Some(tasks) = &tasks
6201 && let Some(project) = project
6202 {
6203 task_context_task =
6204 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6205 }
6206
6207 cx.spawn_in(window, {
6208 let buffer = buffer.clone();
6209 async move |editor, cx| {
6210 let task_context = task_context_task.await;
6211
6212 let resolved_tasks =
6213 tasks
6214 .zip(task_context.clone())
6215 .map(|(tasks, task_context)| ResolvedTasks {
6216 templates: tasks.resolve(&task_context).collect(),
6217 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6218 multibuffer_point.row,
6219 tasks.column,
6220 )),
6221 });
6222 let debug_scenarios = editor
6223 .update(cx, |editor, cx| {
6224 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6225 })?
6226 .await;
6227 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6228 }
6229 })
6230 }
6231 };
6232
6233 cx.spawn_in(window, async move |editor, cx| {
6234 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6235 let code_actions = code_actions_task.await;
6236 let spawn_straight_away = quick_launch
6237 && resolved_tasks
6238 .as_ref()
6239 .is_some_and(|tasks| tasks.templates.len() == 1)
6240 && code_actions
6241 .as_ref()
6242 .is_none_or(|actions| actions.is_empty())
6243 && debug_scenarios.is_empty();
6244
6245 editor.update_in(cx, |editor, window, cx| {
6246 crate::hover_popover::hide_hover(editor, cx);
6247 let actions = CodeActionContents::new(
6248 resolved_tasks,
6249 code_actions,
6250 debug_scenarios,
6251 task_context.unwrap_or_default(),
6252 );
6253
6254 // Don't show the menu if there are no actions available
6255 if actions.is_empty() {
6256 cx.notify();
6257 return Task::ready(Ok(()));
6258 }
6259
6260 *editor.context_menu.borrow_mut() =
6261 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6262 buffer,
6263 actions,
6264 selected_item: Default::default(),
6265 scroll_handle: UniformListScrollHandle::default(),
6266 deployed_from,
6267 }));
6268 cx.notify();
6269 if spawn_straight_away
6270 && let Some(task) = editor.confirm_code_action(
6271 &ConfirmCodeAction { item_ix: Some(0) },
6272 window,
6273 cx,
6274 )
6275 {
6276 return task;
6277 }
6278
6279 Task::ready(Ok(()))
6280 })
6281 })
6282 .detach_and_log_err(cx);
6283 }
6284
6285 fn debug_scenarios(
6286 &mut self,
6287 resolved_tasks: &Option<ResolvedTasks>,
6288 buffer: &Entity<Buffer>,
6289 cx: &mut App,
6290 ) -> Task<Vec<task::DebugScenario>> {
6291 maybe!({
6292 let project = self.project()?;
6293 let dap_store = project.read(cx).dap_store();
6294 let mut scenarios = vec![];
6295 let resolved_tasks = resolved_tasks.as_ref()?;
6296 let buffer = buffer.read(cx);
6297 let language = buffer.language()?;
6298 let file = buffer.file();
6299 let debug_adapter = language_settings(language.name().into(), file, cx)
6300 .debuggers
6301 .first()
6302 .map(SharedString::from)
6303 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6304
6305 dap_store.update(cx, |dap_store, cx| {
6306 for (_, task) in &resolved_tasks.templates {
6307 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6308 task.original_task().clone(),
6309 debug_adapter.clone().into(),
6310 task.display_label().to_owned().into(),
6311 cx,
6312 );
6313 scenarios.push(maybe_scenario);
6314 }
6315 });
6316 Some(cx.background_spawn(async move {
6317 futures::future::join_all(scenarios)
6318 .await
6319 .into_iter()
6320 .flatten()
6321 .collect::<Vec<_>>()
6322 }))
6323 })
6324 .unwrap_or_else(|| Task::ready(vec![]))
6325 }
6326
6327 fn code_actions(
6328 &mut self,
6329 buffer_row: u32,
6330 window: &mut Window,
6331 cx: &mut Context<Self>,
6332 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6333 let mut task = self.code_actions_task.take();
6334 cx.spawn_in(window, async move |editor, cx| {
6335 while let Some(prev_task) = task {
6336 prev_task.await.log_err();
6337 task = editor
6338 .update(cx, |this, _| this.code_actions_task.take())
6339 .ok()?;
6340 }
6341
6342 editor
6343 .update(cx, |editor, cx| {
6344 editor
6345 .available_code_actions
6346 .clone()
6347 .and_then(|(location, code_actions)| {
6348 let snapshot = location.buffer.read(cx).snapshot();
6349 let point_range = location.range.to_point(&snapshot);
6350 let point_range = point_range.start.row..=point_range.end.row;
6351 if point_range.contains(&buffer_row) {
6352 Some(code_actions)
6353 } else {
6354 None
6355 }
6356 })
6357 })
6358 .ok()
6359 .flatten()
6360 })
6361 }
6362
6363 pub fn confirm_code_action(
6364 &mut self,
6365 action: &ConfirmCodeAction,
6366 window: &mut Window,
6367 cx: &mut Context<Self>,
6368 ) -> Option<Task<Result<()>>> {
6369 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6370
6371 let actions_menu =
6372 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6373 menu
6374 } else {
6375 return None;
6376 };
6377
6378 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6379 let action = actions_menu.actions.get(action_ix)?;
6380 let title = action.label();
6381 let buffer = actions_menu.buffer;
6382 let workspace = self.workspace()?;
6383
6384 match action {
6385 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6386 workspace.update(cx, |workspace, cx| {
6387 workspace.schedule_resolved_task(
6388 task_source_kind,
6389 resolved_task,
6390 false,
6391 window,
6392 cx,
6393 );
6394
6395 Some(Task::ready(Ok(())))
6396 })
6397 }
6398 CodeActionsItem::CodeAction {
6399 excerpt_id,
6400 action,
6401 provider,
6402 } => {
6403 let apply_code_action =
6404 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6405 let workspace = workspace.downgrade();
6406 Some(cx.spawn_in(window, async move |editor, cx| {
6407 let project_transaction = apply_code_action.await?;
6408 Self::open_project_transaction(
6409 &editor,
6410 workspace,
6411 project_transaction,
6412 title,
6413 cx,
6414 )
6415 .await
6416 }))
6417 }
6418 CodeActionsItem::DebugScenario(scenario) => {
6419 let context = actions_menu.actions.context;
6420
6421 workspace.update(cx, |workspace, cx| {
6422 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6423 workspace.start_debug_session(
6424 scenario,
6425 context,
6426 Some(buffer),
6427 None,
6428 window,
6429 cx,
6430 );
6431 });
6432 Some(Task::ready(Ok(())))
6433 }
6434 }
6435 }
6436
6437 pub async fn open_project_transaction(
6438 editor: &WeakEntity<Editor>,
6439 workspace: WeakEntity<Workspace>,
6440 transaction: ProjectTransaction,
6441 title: String,
6442 cx: &mut AsyncWindowContext,
6443 ) -> Result<()> {
6444 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6445 cx.update(|_, cx| {
6446 entries.sort_unstable_by_key(|(buffer, _)| {
6447 buffer.read(cx).file().map(|f| f.path().clone())
6448 });
6449 })?;
6450 if entries.is_empty() {
6451 return Ok(());
6452 }
6453
6454 // If the project transaction's edits are all contained within this editor, then
6455 // avoid opening a new editor to display them.
6456
6457 if let [(buffer, transaction)] = &*entries {
6458 let excerpt = editor.update(cx, |editor, cx| {
6459 editor
6460 .buffer()
6461 .read(cx)
6462 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6463 })?;
6464 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6465 && excerpted_buffer == *buffer
6466 {
6467 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6468 let excerpt_range = excerpt_range.to_offset(buffer);
6469 buffer
6470 .edited_ranges_for_transaction::<usize>(transaction)
6471 .all(|range| {
6472 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6473 })
6474 })?;
6475
6476 if all_edits_within_excerpt {
6477 return Ok(());
6478 }
6479 }
6480 }
6481
6482 let mut ranges_to_highlight = Vec::new();
6483 let excerpt_buffer = cx.new(|cx| {
6484 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6485 for (buffer_handle, transaction) in &entries {
6486 let edited_ranges = buffer_handle
6487 .read(cx)
6488 .edited_ranges_for_transaction::<Point>(transaction)
6489 .collect::<Vec<_>>();
6490 let (ranges, _) = multibuffer.set_excerpts_for_path(
6491 PathKey::for_buffer(buffer_handle, cx),
6492 buffer_handle.clone(),
6493 edited_ranges,
6494 multibuffer_context_lines(cx),
6495 cx,
6496 );
6497
6498 ranges_to_highlight.extend(ranges);
6499 }
6500 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6501 multibuffer
6502 })?;
6503
6504 workspace.update_in(cx, |workspace, window, cx| {
6505 let project = workspace.project().clone();
6506 let editor =
6507 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6508 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6509 editor.update(cx, |editor, cx| {
6510 editor.highlight_background::<Self>(
6511 &ranges_to_highlight,
6512 |theme| theme.colors().editor_highlighted_line_background,
6513 cx,
6514 );
6515 });
6516 })?;
6517
6518 Ok(())
6519 }
6520
6521 pub fn clear_code_action_providers(&mut self) {
6522 self.code_action_providers.clear();
6523 self.available_code_actions.take();
6524 }
6525
6526 pub fn add_code_action_provider(
6527 &mut self,
6528 provider: Rc<dyn CodeActionProvider>,
6529 window: &mut Window,
6530 cx: &mut Context<Self>,
6531 ) {
6532 if self
6533 .code_action_providers
6534 .iter()
6535 .any(|existing_provider| existing_provider.id() == provider.id())
6536 {
6537 return;
6538 }
6539
6540 self.code_action_providers.push(provider);
6541 self.refresh_code_actions(window, cx);
6542 }
6543
6544 pub fn remove_code_action_provider(
6545 &mut self,
6546 id: Arc<str>,
6547 window: &mut Window,
6548 cx: &mut Context<Self>,
6549 ) {
6550 self.code_action_providers
6551 .retain(|provider| provider.id() != id);
6552 self.refresh_code_actions(window, cx);
6553 }
6554
6555 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6556 !self.code_action_providers.is_empty()
6557 && EditorSettings::get_global(cx).toolbar.code_actions
6558 }
6559
6560 pub fn has_available_code_actions(&self) -> bool {
6561 self.available_code_actions
6562 .as_ref()
6563 .is_some_and(|(_, actions)| !actions.is_empty())
6564 }
6565
6566 fn render_inline_code_actions(
6567 &self,
6568 icon_size: ui::IconSize,
6569 display_row: DisplayRow,
6570 is_active: bool,
6571 cx: &mut Context<Self>,
6572 ) -> AnyElement {
6573 let show_tooltip = !self.context_menu_visible();
6574 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6575 .icon_size(icon_size)
6576 .shape(ui::IconButtonShape::Square)
6577 .icon_color(ui::Color::Hidden)
6578 .toggle_state(is_active)
6579 .when(show_tooltip, |this| {
6580 this.tooltip({
6581 let focus_handle = self.focus_handle.clone();
6582 move |_window, cx| {
6583 Tooltip::for_action_in(
6584 "Toggle Code Actions",
6585 &ToggleCodeActions {
6586 deployed_from: None,
6587 quick_launch: false,
6588 },
6589 &focus_handle,
6590 cx,
6591 )
6592 }
6593 })
6594 })
6595 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6596 window.focus(&editor.focus_handle(cx));
6597 editor.toggle_code_actions(
6598 &crate::actions::ToggleCodeActions {
6599 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6600 display_row,
6601 )),
6602 quick_launch: false,
6603 },
6604 window,
6605 cx,
6606 );
6607 }))
6608 .into_any_element()
6609 }
6610
6611 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6612 &self.context_menu
6613 }
6614
6615 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6616 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6617 cx.background_executor()
6618 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6619 .await;
6620
6621 let (start_buffer, start, _, end, newest_selection) = this
6622 .update(cx, |this, cx| {
6623 let newest_selection = this.selections.newest_anchor().clone();
6624 if newest_selection.head().diff_base_anchor.is_some() {
6625 return None;
6626 }
6627 let display_snapshot = this.display_snapshot(cx);
6628 let newest_selection_adjusted =
6629 this.selections.newest_adjusted(&display_snapshot);
6630 let buffer = this.buffer.read(cx);
6631
6632 let (start_buffer, start) =
6633 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6634 let (end_buffer, end) =
6635 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6636
6637 Some((start_buffer, start, end_buffer, end, newest_selection))
6638 })?
6639 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6640 .context(
6641 "Expected selection to lie in a single buffer when refreshing code actions",
6642 )?;
6643 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6644 let providers = this.code_action_providers.clone();
6645 let tasks = this
6646 .code_action_providers
6647 .iter()
6648 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6649 .collect::<Vec<_>>();
6650 (providers, tasks)
6651 })?;
6652
6653 let mut actions = Vec::new();
6654 for (provider, provider_actions) in
6655 providers.into_iter().zip(future::join_all(tasks).await)
6656 {
6657 if let Some(provider_actions) = provider_actions.log_err() {
6658 actions.extend(provider_actions.into_iter().map(|action| {
6659 AvailableCodeAction {
6660 excerpt_id: newest_selection.start.excerpt_id,
6661 action,
6662 provider: provider.clone(),
6663 }
6664 }));
6665 }
6666 }
6667
6668 this.update(cx, |this, cx| {
6669 this.available_code_actions = if actions.is_empty() {
6670 None
6671 } else {
6672 Some((
6673 Location {
6674 buffer: start_buffer,
6675 range: start..end,
6676 },
6677 actions.into(),
6678 ))
6679 };
6680 cx.notify();
6681 })
6682 }));
6683 }
6684
6685 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6686 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6687 self.show_git_blame_inline = false;
6688
6689 self.show_git_blame_inline_delay_task =
6690 Some(cx.spawn_in(window, async move |this, cx| {
6691 cx.background_executor().timer(delay).await;
6692
6693 this.update(cx, |this, cx| {
6694 this.show_git_blame_inline = true;
6695 cx.notify();
6696 })
6697 .log_err();
6698 }));
6699 }
6700 }
6701
6702 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6703 let snapshot = self.snapshot(window, cx);
6704 let cursor = self
6705 .selections
6706 .newest::<Point>(&snapshot.display_snapshot)
6707 .head();
6708 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6709 else {
6710 return;
6711 };
6712
6713 let Some(blame) = self.blame.as_ref() else {
6714 return;
6715 };
6716
6717 let row_info = RowInfo {
6718 buffer_id: Some(buffer.remote_id()),
6719 buffer_row: Some(point.row),
6720 ..Default::default()
6721 };
6722 let Some((buffer, blame_entry)) = blame
6723 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6724 .flatten()
6725 else {
6726 return;
6727 };
6728
6729 let anchor = self.selections.newest_anchor().head();
6730 let position = self.to_pixel_point(anchor, &snapshot, window);
6731 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6732 self.show_blame_popover(
6733 buffer,
6734 &blame_entry,
6735 position + last_bounds.origin,
6736 true,
6737 cx,
6738 );
6739 };
6740 }
6741
6742 fn show_blame_popover(
6743 &mut self,
6744 buffer: BufferId,
6745 blame_entry: &BlameEntry,
6746 position: gpui::Point<Pixels>,
6747 ignore_timeout: bool,
6748 cx: &mut Context<Self>,
6749 ) {
6750 if let Some(state) = &mut self.inline_blame_popover {
6751 state.hide_task.take();
6752 } else {
6753 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6754 let blame_entry = blame_entry.clone();
6755 let show_task = cx.spawn(async move |editor, cx| {
6756 if !ignore_timeout {
6757 cx.background_executor()
6758 .timer(std::time::Duration::from_millis(blame_popover_delay))
6759 .await;
6760 }
6761 editor
6762 .update(cx, |editor, cx| {
6763 editor.inline_blame_popover_show_task.take();
6764 let Some(blame) = editor.blame.as_ref() else {
6765 return;
6766 };
6767 let blame = blame.read(cx);
6768 let details = blame.details_for_entry(buffer, &blame_entry);
6769 let markdown = cx.new(|cx| {
6770 Markdown::new(
6771 details
6772 .as_ref()
6773 .map(|message| message.message.clone())
6774 .unwrap_or_default(),
6775 None,
6776 None,
6777 cx,
6778 )
6779 });
6780 editor.inline_blame_popover = Some(InlineBlamePopover {
6781 position,
6782 hide_task: None,
6783 popover_bounds: None,
6784 popover_state: InlineBlamePopoverState {
6785 scroll_handle: ScrollHandle::new(),
6786 commit_message: details,
6787 markdown,
6788 },
6789 keyboard_grace: ignore_timeout,
6790 });
6791 cx.notify();
6792 })
6793 .ok();
6794 });
6795 self.inline_blame_popover_show_task = Some(show_task);
6796 }
6797 }
6798
6799 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6800 self.inline_blame_popover_show_task.take();
6801 if let Some(state) = &mut self.inline_blame_popover {
6802 let hide_task = cx.spawn(async move |editor, cx| {
6803 if !ignore_timeout {
6804 cx.background_executor()
6805 .timer(std::time::Duration::from_millis(100))
6806 .await;
6807 }
6808 editor
6809 .update(cx, |editor, cx| {
6810 editor.inline_blame_popover.take();
6811 cx.notify();
6812 })
6813 .ok();
6814 });
6815 state.hide_task = Some(hide_task);
6816 true
6817 } else {
6818 false
6819 }
6820 }
6821
6822 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6823 if self.pending_rename.is_some() {
6824 return None;
6825 }
6826
6827 let provider = self.semantics_provider.clone()?;
6828 let buffer = self.buffer.read(cx);
6829 let newest_selection = self.selections.newest_anchor().clone();
6830 let cursor_position = newest_selection.head();
6831 let (cursor_buffer, cursor_buffer_position) =
6832 buffer.text_anchor_for_position(cursor_position, cx)?;
6833 let (tail_buffer, tail_buffer_position) =
6834 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6835 if cursor_buffer != tail_buffer {
6836 return None;
6837 }
6838
6839 let snapshot = cursor_buffer.read(cx).snapshot();
6840 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6841 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6842 if start_word_range != end_word_range {
6843 self.document_highlights_task.take();
6844 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6845 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6846 return None;
6847 }
6848
6849 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6850 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6851 cx.background_executor()
6852 .timer(Duration::from_millis(debounce))
6853 .await;
6854
6855 let highlights = if let Some(highlights) = cx
6856 .update(|cx| {
6857 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6858 })
6859 .ok()
6860 .flatten()
6861 {
6862 highlights.await.log_err()
6863 } else {
6864 None
6865 };
6866
6867 if let Some(highlights) = highlights {
6868 this.update(cx, |this, cx| {
6869 if this.pending_rename.is_some() {
6870 return;
6871 }
6872
6873 let buffer = this.buffer.read(cx);
6874 if buffer
6875 .text_anchor_for_position(cursor_position, cx)
6876 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6877 {
6878 return;
6879 }
6880
6881 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6882 let mut write_ranges = Vec::new();
6883 let mut read_ranges = Vec::new();
6884 for highlight in highlights {
6885 let buffer_id = cursor_buffer.read(cx).remote_id();
6886 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6887 {
6888 let start = highlight
6889 .range
6890 .start
6891 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6892 let end = highlight
6893 .range
6894 .end
6895 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6896 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6897 continue;
6898 }
6899
6900 let range =
6901 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6902 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6903 write_ranges.push(range);
6904 } else {
6905 read_ranges.push(range);
6906 }
6907 }
6908 }
6909
6910 this.highlight_background::<DocumentHighlightRead>(
6911 &read_ranges,
6912 |theme| theme.colors().editor_document_highlight_read_background,
6913 cx,
6914 );
6915 this.highlight_background::<DocumentHighlightWrite>(
6916 &write_ranges,
6917 |theme| theme.colors().editor_document_highlight_write_background,
6918 cx,
6919 );
6920 cx.notify();
6921 })
6922 .log_err();
6923 }
6924 }));
6925 None
6926 }
6927
6928 fn prepare_highlight_query_from_selection(
6929 &mut self,
6930 window: &Window,
6931 cx: &mut Context<Editor>,
6932 ) -> Option<(String, Range<Anchor>)> {
6933 if matches!(self.mode, EditorMode::SingleLine) {
6934 return None;
6935 }
6936 if !EditorSettings::get_global(cx).selection_highlight {
6937 return None;
6938 }
6939 if self.selections.count() != 1 || self.selections.line_mode() {
6940 return None;
6941 }
6942 let snapshot = self.snapshot(window, cx);
6943 let selection = self.selections.newest::<Point>(&snapshot);
6944 // If the selection spans multiple rows OR it is empty
6945 if selection.start.row != selection.end.row
6946 || selection.start.column == selection.end.column
6947 {
6948 return None;
6949 }
6950 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6951 let query = snapshot
6952 .buffer_snapshot()
6953 .text_for_range(selection_anchor_range.clone())
6954 .collect::<String>();
6955 if query.trim().is_empty() {
6956 return None;
6957 }
6958 Some((query, selection_anchor_range))
6959 }
6960
6961 fn update_selection_occurrence_highlights(
6962 &mut self,
6963 query_text: String,
6964 query_range: Range<Anchor>,
6965 multi_buffer_range_to_query: Range<Point>,
6966 use_debounce: bool,
6967 window: &mut Window,
6968 cx: &mut Context<Editor>,
6969 ) -> Task<()> {
6970 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6971 cx.spawn_in(window, async move |editor, cx| {
6972 if use_debounce {
6973 cx.background_executor()
6974 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6975 .await;
6976 }
6977 let match_task = cx.background_spawn(async move {
6978 let buffer_ranges = multi_buffer_snapshot
6979 .range_to_buffer_ranges(multi_buffer_range_to_query)
6980 .into_iter()
6981 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6982 let mut match_ranges = Vec::new();
6983 let Ok(regex) = project::search::SearchQuery::text(
6984 query_text.clone(),
6985 false,
6986 false,
6987 false,
6988 Default::default(),
6989 Default::default(),
6990 false,
6991 None,
6992 ) else {
6993 return Vec::default();
6994 };
6995 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6996 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6997 match_ranges.extend(
6998 regex
6999 .search(buffer_snapshot, Some(search_range.clone()))
7000 .await
7001 .into_iter()
7002 .filter_map(|match_range| {
7003 let match_start = buffer_snapshot
7004 .anchor_after(search_range.start + match_range.start);
7005 let match_end = buffer_snapshot
7006 .anchor_before(search_range.start + match_range.end);
7007 let match_anchor_range = Anchor::range_in_buffer(
7008 excerpt_id,
7009 buffer_snapshot.remote_id(),
7010 match_start..match_end,
7011 );
7012 (match_anchor_range != query_range).then_some(match_anchor_range)
7013 }),
7014 );
7015 }
7016 match_ranges
7017 });
7018 let match_ranges = match_task.await;
7019 editor
7020 .update_in(cx, |editor, _, cx| {
7021 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7022 if !match_ranges.is_empty() {
7023 editor.highlight_background::<SelectedTextHighlight>(
7024 &match_ranges,
7025 |theme| theme.colors().editor_document_highlight_bracket_background,
7026 cx,
7027 )
7028 }
7029 })
7030 .log_err();
7031 })
7032 }
7033
7034 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7035 struct NewlineFold;
7036 let type_id = std::any::TypeId::of::<NewlineFold>();
7037 if !self.mode.is_single_line() {
7038 return;
7039 }
7040 let snapshot = self.snapshot(window, cx);
7041 if snapshot.buffer_snapshot().max_point().row == 0 {
7042 return;
7043 }
7044 let task = cx.background_spawn(async move {
7045 let new_newlines = snapshot
7046 .buffer_chars_at(0)
7047 .filter_map(|(c, i)| {
7048 if c == '\n' {
7049 Some(
7050 snapshot.buffer_snapshot().anchor_after(i)
7051 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7052 )
7053 } else {
7054 None
7055 }
7056 })
7057 .collect::<Vec<_>>();
7058 let existing_newlines = snapshot
7059 .folds_in_range(0..snapshot.buffer_snapshot().len())
7060 .filter_map(|fold| {
7061 if fold.placeholder.type_tag == Some(type_id) {
7062 Some(fold.range.start..fold.range.end)
7063 } else {
7064 None
7065 }
7066 })
7067 .collect::<Vec<_>>();
7068
7069 (new_newlines, existing_newlines)
7070 });
7071 self.folding_newlines = cx.spawn(async move |this, cx| {
7072 let (new_newlines, existing_newlines) = task.await;
7073 if new_newlines == existing_newlines {
7074 return;
7075 }
7076 let placeholder = FoldPlaceholder {
7077 render: Arc::new(move |_, _, cx| {
7078 div()
7079 .bg(cx.theme().status().hint_background)
7080 .border_b_1()
7081 .size_full()
7082 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7083 .border_color(cx.theme().status().hint)
7084 .child("\\n")
7085 .into_any()
7086 }),
7087 constrain_width: false,
7088 merge_adjacent: false,
7089 type_tag: Some(type_id),
7090 };
7091 let creases = new_newlines
7092 .into_iter()
7093 .map(|range| Crease::simple(range, placeholder.clone()))
7094 .collect();
7095 this.update(cx, |this, cx| {
7096 this.display_map.update(cx, |display_map, cx| {
7097 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7098 display_map.fold(creases, cx);
7099 });
7100 })
7101 .ok();
7102 });
7103 }
7104
7105 fn refresh_selected_text_highlights(
7106 &mut self,
7107 on_buffer_edit: bool,
7108 window: &mut Window,
7109 cx: &mut Context<Editor>,
7110 ) {
7111 let Some((query_text, query_range)) =
7112 self.prepare_highlight_query_from_selection(window, cx)
7113 else {
7114 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7115 self.quick_selection_highlight_task.take();
7116 self.debounced_selection_highlight_task.take();
7117 return;
7118 };
7119 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7120 if on_buffer_edit
7121 || self
7122 .quick_selection_highlight_task
7123 .as_ref()
7124 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7125 {
7126 let multi_buffer_visible_start = self
7127 .scroll_manager
7128 .anchor()
7129 .anchor
7130 .to_point(&multi_buffer_snapshot);
7131 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7132 multi_buffer_visible_start
7133 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7134 Bias::Left,
7135 );
7136 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7137 self.quick_selection_highlight_task = Some((
7138 query_range.clone(),
7139 self.update_selection_occurrence_highlights(
7140 query_text.clone(),
7141 query_range.clone(),
7142 multi_buffer_visible_range,
7143 false,
7144 window,
7145 cx,
7146 ),
7147 ));
7148 }
7149 if on_buffer_edit
7150 || self
7151 .debounced_selection_highlight_task
7152 .as_ref()
7153 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7154 {
7155 let multi_buffer_start = multi_buffer_snapshot
7156 .anchor_before(0)
7157 .to_point(&multi_buffer_snapshot);
7158 let multi_buffer_end = multi_buffer_snapshot
7159 .anchor_after(multi_buffer_snapshot.len())
7160 .to_point(&multi_buffer_snapshot);
7161 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7162 self.debounced_selection_highlight_task = Some((
7163 query_range.clone(),
7164 self.update_selection_occurrence_highlights(
7165 query_text,
7166 query_range,
7167 multi_buffer_full_range,
7168 true,
7169 window,
7170 cx,
7171 ),
7172 ));
7173 }
7174 }
7175
7176 pub fn refresh_edit_prediction(
7177 &mut self,
7178 debounce: bool,
7179 user_requested: bool,
7180 window: &mut Window,
7181 cx: &mut Context<Self>,
7182 ) -> Option<()> {
7183 if DisableAiSettings::get_global(cx).disable_ai {
7184 return None;
7185 }
7186
7187 let provider = self.edit_prediction_provider()?;
7188 let cursor = self.selections.newest_anchor().head();
7189 let (buffer, cursor_buffer_position) =
7190 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7191
7192 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7193 self.discard_edit_prediction(false, cx);
7194 return None;
7195 }
7196
7197 self.update_visible_edit_prediction(window, cx);
7198
7199 if !user_requested
7200 && (!self.should_show_edit_predictions()
7201 || !self.is_focused(window)
7202 || buffer.read(cx).is_empty())
7203 {
7204 self.discard_edit_prediction(false, cx);
7205 return None;
7206 }
7207
7208 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7209 Some(())
7210 }
7211
7212 fn show_edit_predictions_in_menu(&self) -> bool {
7213 match self.edit_prediction_settings {
7214 EditPredictionSettings::Disabled => false,
7215 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7216 }
7217 }
7218
7219 pub fn edit_predictions_enabled(&self) -> bool {
7220 match self.edit_prediction_settings {
7221 EditPredictionSettings::Disabled => false,
7222 EditPredictionSettings::Enabled { .. } => true,
7223 }
7224 }
7225
7226 fn edit_prediction_requires_modifier(&self) -> bool {
7227 match self.edit_prediction_settings {
7228 EditPredictionSettings::Disabled => false,
7229 EditPredictionSettings::Enabled {
7230 preview_requires_modifier,
7231 ..
7232 } => preview_requires_modifier,
7233 }
7234 }
7235
7236 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7237 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7238 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7239 self.discard_edit_prediction(false, cx);
7240 } else {
7241 let selection = self.selections.newest_anchor();
7242 let cursor = selection.head();
7243
7244 if let Some((buffer, cursor_buffer_position)) =
7245 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7246 {
7247 self.edit_prediction_settings =
7248 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7249 }
7250 }
7251 }
7252
7253 fn edit_prediction_settings_at_position(
7254 &self,
7255 buffer: &Entity<Buffer>,
7256 buffer_position: language::Anchor,
7257 cx: &App,
7258 ) -> EditPredictionSettings {
7259 if !self.mode.is_full()
7260 || !self.show_edit_predictions_override.unwrap_or(true)
7261 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7262 {
7263 return EditPredictionSettings::Disabled;
7264 }
7265
7266 let buffer = buffer.read(cx);
7267
7268 let file = buffer.file();
7269
7270 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7271 return EditPredictionSettings::Disabled;
7272 };
7273
7274 let by_provider = matches!(
7275 self.menu_edit_predictions_policy,
7276 MenuEditPredictionsPolicy::ByProvider
7277 );
7278
7279 let show_in_menu = by_provider
7280 && self
7281 .edit_prediction_provider
7282 .as_ref()
7283 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7284
7285 let preview_requires_modifier =
7286 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7287
7288 EditPredictionSettings::Enabled {
7289 show_in_menu,
7290 preview_requires_modifier,
7291 }
7292 }
7293
7294 fn should_show_edit_predictions(&self) -> bool {
7295 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7296 }
7297
7298 pub fn edit_prediction_preview_is_active(&self) -> bool {
7299 matches!(
7300 self.edit_prediction_preview,
7301 EditPredictionPreview::Active { .. }
7302 )
7303 }
7304
7305 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7306 let cursor = self.selections.newest_anchor().head();
7307 if let Some((buffer, cursor_position)) =
7308 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7309 {
7310 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7311 } else {
7312 false
7313 }
7314 }
7315
7316 pub fn supports_minimap(&self, cx: &App) -> bool {
7317 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7318 }
7319
7320 fn edit_predictions_enabled_in_buffer(
7321 &self,
7322 buffer: &Entity<Buffer>,
7323 buffer_position: language::Anchor,
7324 cx: &App,
7325 ) -> bool {
7326 maybe!({
7327 if self.read_only(cx) {
7328 return Some(false);
7329 }
7330 let provider = self.edit_prediction_provider()?;
7331 if !provider.is_enabled(buffer, buffer_position, cx) {
7332 return Some(false);
7333 }
7334 let buffer = buffer.read(cx);
7335 let Some(file) = buffer.file() else {
7336 return Some(true);
7337 };
7338 let settings = all_language_settings(Some(file), cx);
7339 Some(settings.edit_predictions_enabled_for_file(file, cx))
7340 })
7341 .unwrap_or(false)
7342 }
7343
7344 fn cycle_edit_prediction(
7345 &mut self,
7346 direction: Direction,
7347 window: &mut Window,
7348 cx: &mut Context<Self>,
7349 ) -> Option<()> {
7350 let provider = self.edit_prediction_provider()?;
7351 let cursor = self.selections.newest_anchor().head();
7352 let (buffer, cursor_buffer_position) =
7353 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7354 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7355 return None;
7356 }
7357
7358 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7359 self.update_visible_edit_prediction(window, cx);
7360
7361 Some(())
7362 }
7363
7364 pub fn show_edit_prediction(
7365 &mut self,
7366 _: &ShowEditPrediction,
7367 window: &mut Window,
7368 cx: &mut Context<Self>,
7369 ) {
7370 if !self.has_active_edit_prediction() {
7371 self.refresh_edit_prediction(false, true, window, cx);
7372 return;
7373 }
7374
7375 self.update_visible_edit_prediction(window, cx);
7376 }
7377
7378 pub fn display_cursor_names(
7379 &mut self,
7380 _: &DisplayCursorNames,
7381 window: &mut Window,
7382 cx: &mut Context<Self>,
7383 ) {
7384 self.show_cursor_names(window, cx);
7385 }
7386
7387 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7388 self.show_cursor_names = true;
7389 cx.notify();
7390 cx.spawn_in(window, async move |this, cx| {
7391 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7392 this.update(cx, |this, cx| {
7393 this.show_cursor_names = false;
7394 cx.notify()
7395 })
7396 .ok()
7397 })
7398 .detach();
7399 }
7400
7401 pub fn next_edit_prediction(
7402 &mut self,
7403 _: &NextEditPrediction,
7404 window: &mut Window,
7405 cx: &mut Context<Self>,
7406 ) {
7407 if self.has_active_edit_prediction() {
7408 self.cycle_edit_prediction(Direction::Next, window, cx);
7409 } else {
7410 let is_copilot_disabled = self
7411 .refresh_edit_prediction(false, true, window, cx)
7412 .is_none();
7413 if is_copilot_disabled {
7414 cx.propagate();
7415 }
7416 }
7417 }
7418
7419 pub fn previous_edit_prediction(
7420 &mut self,
7421 _: &PreviousEditPrediction,
7422 window: &mut Window,
7423 cx: &mut Context<Self>,
7424 ) {
7425 if self.has_active_edit_prediction() {
7426 self.cycle_edit_prediction(Direction::Prev, window, cx);
7427 } else {
7428 let is_copilot_disabled = self
7429 .refresh_edit_prediction(false, true, window, cx)
7430 .is_none();
7431 if is_copilot_disabled {
7432 cx.propagate();
7433 }
7434 }
7435 }
7436
7437 pub fn accept_edit_prediction(
7438 &mut self,
7439 _: &AcceptEditPrediction,
7440 window: &mut Window,
7441 cx: &mut Context<Self>,
7442 ) {
7443 if self.show_edit_predictions_in_menu() {
7444 self.hide_context_menu(window, cx);
7445 }
7446
7447 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7448 return;
7449 };
7450
7451 match &active_edit_prediction.completion {
7452 EditPrediction::MoveWithin { target, .. } => {
7453 let target = *target;
7454
7455 if let Some(position_map) = &self.last_position_map {
7456 if position_map
7457 .visible_row_range
7458 .contains(&target.to_display_point(&position_map.snapshot).row())
7459 || !self.edit_prediction_requires_modifier()
7460 {
7461 self.unfold_ranges(&[target..target], true, false, cx);
7462 // Note that this is also done in vim's handler of the Tab action.
7463 self.change_selections(
7464 SelectionEffects::scroll(Autoscroll::newest()),
7465 window,
7466 cx,
7467 |selections| {
7468 selections.select_anchor_ranges([target..target]);
7469 },
7470 );
7471 self.clear_row_highlights::<EditPredictionPreview>();
7472
7473 self.edit_prediction_preview
7474 .set_previous_scroll_position(None);
7475 } else {
7476 self.edit_prediction_preview
7477 .set_previous_scroll_position(Some(
7478 position_map.snapshot.scroll_anchor,
7479 ));
7480
7481 self.highlight_rows::<EditPredictionPreview>(
7482 target..target,
7483 cx.theme().colors().editor_highlighted_line_background,
7484 RowHighlightOptions {
7485 autoscroll: true,
7486 ..Default::default()
7487 },
7488 cx,
7489 );
7490 self.request_autoscroll(Autoscroll::fit(), cx);
7491 }
7492 }
7493 }
7494 EditPrediction::MoveOutside { snapshot, target } => {
7495 if let Some(workspace) = self.workspace() {
7496 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7497 .detach_and_log_err(cx);
7498 }
7499 }
7500 EditPrediction::Edit { edits, .. } => {
7501 self.report_edit_prediction_event(
7502 active_edit_prediction.completion_id.clone(),
7503 true,
7504 cx,
7505 );
7506
7507 if let Some(provider) = self.edit_prediction_provider() {
7508 provider.accept(cx);
7509 }
7510
7511 // Store the transaction ID and selections before applying the edit
7512 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7513
7514 let snapshot = self.buffer.read(cx).snapshot(cx);
7515 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7516
7517 self.buffer.update(cx, |buffer, cx| {
7518 buffer.edit(edits.iter().cloned(), None, cx)
7519 });
7520
7521 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7522 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7523 });
7524
7525 let selections = self.selections.disjoint_anchors_arc();
7526 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7527 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7528 if has_new_transaction {
7529 self.selection_history
7530 .insert_transaction(transaction_id_now, selections);
7531 }
7532 }
7533
7534 self.update_visible_edit_prediction(window, cx);
7535 if self.active_edit_prediction.is_none() {
7536 self.refresh_edit_prediction(true, true, window, cx);
7537 }
7538
7539 cx.notify();
7540 }
7541 }
7542
7543 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7544 }
7545
7546 pub fn accept_partial_edit_prediction(
7547 &mut self,
7548 _: &AcceptPartialEditPrediction,
7549 window: &mut Window,
7550 cx: &mut Context<Self>,
7551 ) {
7552 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7553 return;
7554 };
7555 if self.selections.count() != 1 {
7556 return;
7557 }
7558
7559 match &active_edit_prediction.completion {
7560 EditPrediction::MoveWithin { target, .. } => {
7561 let target = *target;
7562 self.change_selections(
7563 SelectionEffects::scroll(Autoscroll::newest()),
7564 window,
7565 cx,
7566 |selections| {
7567 selections.select_anchor_ranges([target..target]);
7568 },
7569 );
7570 }
7571 EditPrediction::MoveOutside { snapshot, target } => {
7572 if let Some(workspace) = self.workspace() {
7573 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7574 .detach_and_log_err(cx);
7575 }
7576 }
7577 EditPrediction::Edit { edits, .. } => {
7578 self.report_edit_prediction_event(
7579 active_edit_prediction.completion_id.clone(),
7580 true,
7581 cx,
7582 );
7583
7584 // Find an insertion that starts at the cursor position.
7585 let snapshot = self.buffer.read(cx).snapshot(cx);
7586 let cursor_offset = self
7587 .selections
7588 .newest::<usize>(&self.display_snapshot(cx))
7589 .head();
7590 let insertion = edits.iter().find_map(|(range, text)| {
7591 let range = range.to_offset(&snapshot);
7592 if range.is_empty() && range.start == cursor_offset {
7593 Some(text)
7594 } else {
7595 None
7596 }
7597 });
7598
7599 if let Some(text) = insertion {
7600 let mut partial_completion = text
7601 .chars()
7602 .by_ref()
7603 .take_while(|c| c.is_alphabetic())
7604 .collect::<String>();
7605 if partial_completion.is_empty() {
7606 partial_completion = text
7607 .chars()
7608 .by_ref()
7609 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7610 .collect::<String>();
7611 }
7612
7613 cx.emit(EditorEvent::InputHandled {
7614 utf16_range_to_replace: None,
7615 text: partial_completion.clone().into(),
7616 });
7617
7618 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7619
7620 self.refresh_edit_prediction(true, true, window, cx);
7621 cx.notify();
7622 } else {
7623 self.accept_edit_prediction(&Default::default(), window, cx);
7624 }
7625 }
7626 }
7627 }
7628
7629 fn discard_edit_prediction(
7630 &mut self,
7631 should_report_edit_prediction_event: bool,
7632 cx: &mut Context<Self>,
7633 ) -> bool {
7634 if should_report_edit_prediction_event {
7635 let completion_id = self
7636 .active_edit_prediction
7637 .as_ref()
7638 .and_then(|active_completion| active_completion.completion_id.clone());
7639
7640 self.report_edit_prediction_event(completion_id, false, cx);
7641 }
7642
7643 if let Some(provider) = self.edit_prediction_provider() {
7644 provider.discard(cx);
7645 }
7646
7647 self.take_active_edit_prediction(cx)
7648 }
7649
7650 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7651 let Some(provider) = self.edit_prediction_provider() else {
7652 return;
7653 };
7654
7655 let Some((_, buffer, _)) = self
7656 .buffer
7657 .read(cx)
7658 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7659 else {
7660 return;
7661 };
7662
7663 let extension = buffer
7664 .read(cx)
7665 .file()
7666 .and_then(|file| Some(file.path().extension()?.to_string()));
7667
7668 let event_type = match accepted {
7669 true => "Edit Prediction Accepted",
7670 false => "Edit Prediction Discarded",
7671 };
7672 telemetry::event!(
7673 event_type,
7674 provider = provider.name(),
7675 prediction_id = id,
7676 suggestion_accepted = accepted,
7677 file_extension = extension,
7678 );
7679 }
7680
7681 fn open_editor_at_anchor(
7682 snapshot: &language::BufferSnapshot,
7683 target: language::Anchor,
7684 workspace: &Entity<Workspace>,
7685 window: &mut Window,
7686 cx: &mut App,
7687 ) -> Task<Result<()>> {
7688 workspace.update(cx, |workspace, cx| {
7689 let path = snapshot.file().map(|file| file.full_path(cx));
7690 let Some(path) =
7691 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7692 else {
7693 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7694 };
7695 let target = text::ToPoint::to_point(&target, snapshot);
7696 let item = workspace.open_path(path, None, true, window, cx);
7697 window.spawn(cx, async move |cx| {
7698 let Some(editor) = item.await?.downcast::<Editor>() else {
7699 return Ok(());
7700 };
7701 editor
7702 .update_in(cx, |editor, window, cx| {
7703 editor.go_to_singleton_buffer_point(target, window, cx);
7704 })
7705 .ok();
7706 anyhow::Ok(())
7707 })
7708 })
7709 }
7710
7711 pub fn has_active_edit_prediction(&self) -> bool {
7712 self.active_edit_prediction.is_some()
7713 }
7714
7715 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7716 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7717 return false;
7718 };
7719
7720 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7721 self.clear_highlights::<EditPredictionHighlight>(cx);
7722 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7723 true
7724 }
7725
7726 /// Returns true when we're displaying the edit prediction popover below the cursor
7727 /// like we are not previewing and the LSP autocomplete menu is visible
7728 /// or we are in `when_holding_modifier` mode.
7729 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7730 if self.edit_prediction_preview_is_active()
7731 || !self.show_edit_predictions_in_menu()
7732 || !self.edit_predictions_enabled()
7733 {
7734 return false;
7735 }
7736
7737 if self.has_visible_completions_menu() {
7738 return true;
7739 }
7740
7741 has_completion && self.edit_prediction_requires_modifier()
7742 }
7743
7744 fn handle_modifiers_changed(
7745 &mut self,
7746 modifiers: Modifiers,
7747 position_map: &PositionMap,
7748 window: &mut Window,
7749 cx: &mut Context<Self>,
7750 ) {
7751 // Ensure that the edit prediction preview is updated, even when not
7752 // enabled, if there's an active edit prediction preview.
7753 if self.show_edit_predictions_in_menu()
7754 || matches!(
7755 self.edit_prediction_preview,
7756 EditPredictionPreview::Active { .. }
7757 )
7758 {
7759 self.update_edit_prediction_preview(&modifiers, window, cx);
7760 }
7761
7762 self.update_selection_mode(&modifiers, position_map, window, cx);
7763
7764 let mouse_position = window.mouse_position();
7765 if !position_map.text_hitbox.is_hovered(window) {
7766 return;
7767 }
7768
7769 self.update_hovered_link(
7770 position_map.point_for_position(mouse_position),
7771 &position_map.snapshot,
7772 modifiers,
7773 window,
7774 cx,
7775 )
7776 }
7777
7778 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7779 match EditorSettings::get_global(cx).multi_cursor_modifier {
7780 MultiCursorModifier::Alt => modifiers.secondary(),
7781 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7782 }
7783 }
7784
7785 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7786 match EditorSettings::get_global(cx).multi_cursor_modifier {
7787 MultiCursorModifier::Alt => modifiers.alt,
7788 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7789 }
7790 }
7791
7792 fn columnar_selection_mode(
7793 modifiers: &Modifiers,
7794 cx: &mut Context<Self>,
7795 ) -> Option<ColumnarMode> {
7796 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7797 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7798 Some(ColumnarMode::FromMouse)
7799 } else if Self::is_alt_pressed(modifiers, cx) {
7800 Some(ColumnarMode::FromSelection)
7801 } else {
7802 None
7803 }
7804 } else {
7805 None
7806 }
7807 }
7808
7809 fn update_selection_mode(
7810 &mut self,
7811 modifiers: &Modifiers,
7812 position_map: &PositionMap,
7813 window: &mut Window,
7814 cx: &mut Context<Self>,
7815 ) {
7816 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7817 return;
7818 };
7819 if self.selections.pending_anchor().is_none() {
7820 return;
7821 }
7822
7823 let mouse_position = window.mouse_position();
7824 let point_for_position = position_map.point_for_position(mouse_position);
7825 let position = point_for_position.previous_valid;
7826
7827 self.select(
7828 SelectPhase::BeginColumnar {
7829 position,
7830 reset: false,
7831 mode,
7832 goal_column: point_for_position.exact_unclipped.column(),
7833 },
7834 window,
7835 cx,
7836 );
7837 }
7838
7839 fn update_edit_prediction_preview(
7840 &mut self,
7841 modifiers: &Modifiers,
7842 window: &mut Window,
7843 cx: &mut Context<Self>,
7844 ) {
7845 let mut modifiers_held = false;
7846 if let Some(accept_keystroke) = self
7847 .accept_edit_prediction_keybind(false, window, cx)
7848 .keystroke()
7849 {
7850 modifiers_held = modifiers_held
7851 || (accept_keystroke.modifiers() == modifiers
7852 && accept_keystroke.modifiers().modified());
7853 };
7854 if let Some(accept_partial_keystroke) = self
7855 .accept_edit_prediction_keybind(true, window, cx)
7856 .keystroke()
7857 {
7858 modifiers_held = modifiers_held
7859 || (accept_partial_keystroke.modifiers() == modifiers
7860 && accept_partial_keystroke.modifiers().modified());
7861 }
7862
7863 if modifiers_held {
7864 if matches!(
7865 self.edit_prediction_preview,
7866 EditPredictionPreview::Inactive { .. }
7867 ) {
7868 if let Some(provider) = self.edit_prediction_provider.as_ref() {
7869 provider.provider.did_show(cx)
7870 }
7871
7872 self.edit_prediction_preview = EditPredictionPreview::Active {
7873 previous_scroll_position: None,
7874 since: Instant::now(),
7875 };
7876
7877 self.update_visible_edit_prediction(window, cx);
7878 cx.notify();
7879 }
7880 } else if let EditPredictionPreview::Active {
7881 previous_scroll_position,
7882 since,
7883 } = self.edit_prediction_preview
7884 {
7885 if let (Some(previous_scroll_position), Some(position_map)) =
7886 (previous_scroll_position, self.last_position_map.as_ref())
7887 {
7888 self.set_scroll_position(
7889 previous_scroll_position
7890 .scroll_position(&position_map.snapshot.display_snapshot),
7891 window,
7892 cx,
7893 );
7894 }
7895
7896 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7897 released_too_fast: since.elapsed() < Duration::from_millis(200),
7898 };
7899 self.clear_row_highlights::<EditPredictionPreview>();
7900 self.update_visible_edit_prediction(window, cx);
7901 cx.notify();
7902 }
7903 }
7904
7905 fn update_visible_edit_prediction(
7906 &mut self,
7907 _window: &mut Window,
7908 cx: &mut Context<Self>,
7909 ) -> Option<()> {
7910 if DisableAiSettings::get_global(cx).disable_ai {
7911 return None;
7912 }
7913
7914 if self.ime_transaction.is_some() {
7915 self.discard_edit_prediction(false, cx);
7916 return None;
7917 }
7918
7919 let selection = self.selections.newest_anchor();
7920 let cursor = selection.head();
7921 let multibuffer = self.buffer.read(cx).snapshot(cx);
7922 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7923 let excerpt_id = cursor.excerpt_id;
7924
7925 let show_in_menu = self.show_edit_predictions_in_menu();
7926 let completions_menu_has_precedence = !show_in_menu
7927 && (self.context_menu.borrow().is_some()
7928 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7929
7930 if completions_menu_has_precedence
7931 || !offset_selection.is_empty()
7932 || self
7933 .active_edit_prediction
7934 .as_ref()
7935 .is_some_and(|completion| {
7936 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7937 return false;
7938 };
7939 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7940 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7941 !invalidation_range.contains(&offset_selection.head())
7942 })
7943 {
7944 self.discard_edit_prediction(false, cx);
7945 return None;
7946 }
7947
7948 self.take_active_edit_prediction(cx);
7949 let Some(provider) = self.edit_prediction_provider() else {
7950 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7951 return None;
7952 };
7953
7954 let (buffer, cursor_buffer_position) =
7955 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7956
7957 self.edit_prediction_settings =
7958 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7959
7960 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7961
7962 if self.edit_prediction_indent_conflict {
7963 let cursor_point = cursor.to_point(&multibuffer);
7964
7965 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7966
7967 if let Some((_, indent)) = indents.iter().next()
7968 && indent.len == cursor_point.column
7969 {
7970 self.edit_prediction_indent_conflict = false;
7971 }
7972 }
7973
7974 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7975
7976 let (completion_id, edits, edit_preview) = match edit_prediction {
7977 edit_prediction::EditPrediction::Local {
7978 id,
7979 edits,
7980 edit_preview,
7981 } => (id, edits, edit_preview),
7982 edit_prediction::EditPrediction::Jump {
7983 id,
7984 snapshot,
7985 target,
7986 } => {
7987 self.stale_edit_prediction_in_menu = None;
7988 self.active_edit_prediction = Some(EditPredictionState {
7989 inlay_ids: vec![],
7990 completion: EditPrediction::MoveOutside { snapshot, target },
7991 completion_id: id,
7992 invalidation_range: None,
7993 });
7994 cx.notify();
7995 return Some(());
7996 }
7997 };
7998
7999 let edits = edits
8000 .into_iter()
8001 .flat_map(|(range, new_text)| {
8002 Some((
8003 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8004 new_text,
8005 ))
8006 })
8007 .collect::<Vec<_>>();
8008 if edits.is_empty() {
8009 return None;
8010 }
8011
8012 let first_edit_start = edits.first().unwrap().0.start;
8013 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8014 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8015
8016 let last_edit_end = edits.last().unwrap().0.end;
8017 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8018 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8019
8020 let cursor_row = cursor.to_point(&multibuffer).row;
8021
8022 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8023
8024 let mut inlay_ids = Vec::new();
8025 let invalidation_row_range;
8026 let move_invalidation_row_range = if cursor_row < edit_start_row {
8027 Some(cursor_row..edit_end_row)
8028 } else if cursor_row > edit_end_row {
8029 Some(edit_start_row..cursor_row)
8030 } else {
8031 None
8032 };
8033 let supports_jump = self
8034 .edit_prediction_provider
8035 .as_ref()
8036 .map(|provider| provider.provider.supports_jump_to_edit())
8037 .unwrap_or(true);
8038
8039 let is_move = supports_jump
8040 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8041 let completion = if is_move {
8042 invalidation_row_range =
8043 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8044 let target = first_edit_start;
8045 EditPrediction::MoveWithin { target, snapshot }
8046 } else {
8047 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8048 && !self.edit_predictions_hidden_for_vim_mode;
8049
8050 if show_completions_in_buffer {
8051 if let Some(provider) = &self.edit_prediction_provider {
8052 provider.provider.did_show(cx);
8053 }
8054 if edits
8055 .iter()
8056 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8057 {
8058 let mut inlays = Vec::new();
8059 for (range, new_text) in &edits {
8060 let inlay = Inlay::edit_prediction(
8061 post_inc(&mut self.next_inlay_id),
8062 range.start,
8063 new_text.as_ref(),
8064 );
8065 inlay_ids.push(inlay.id);
8066 inlays.push(inlay);
8067 }
8068
8069 self.splice_inlays(&[], inlays, cx);
8070 } else {
8071 let background_color = cx.theme().status().deleted_background;
8072 self.highlight_text::<EditPredictionHighlight>(
8073 edits.iter().map(|(range, _)| range.clone()).collect(),
8074 HighlightStyle {
8075 background_color: Some(background_color),
8076 ..Default::default()
8077 },
8078 cx,
8079 );
8080 }
8081 }
8082
8083 invalidation_row_range = edit_start_row..edit_end_row;
8084
8085 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8086 if provider.show_tab_accept_marker() {
8087 EditDisplayMode::TabAccept
8088 } else {
8089 EditDisplayMode::Inline
8090 }
8091 } else {
8092 EditDisplayMode::DiffPopover
8093 };
8094
8095 EditPrediction::Edit {
8096 edits,
8097 edit_preview,
8098 display_mode,
8099 snapshot,
8100 }
8101 };
8102
8103 let invalidation_range = multibuffer
8104 .anchor_before(Point::new(invalidation_row_range.start, 0))
8105 ..multibuffer.anchor_after(Point::new(
8106 invalidation_row_range.end,
8107 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8108 ));
8109
8110 self.stale_edit_prediction_in_menu = None;
8111 self.active_edit_prediction = Some(EditPredictionState {
8112 inlay_ids,
8113 completion,
8114 completion_id,
8115 invalidation_range: Some(invalidation_range),
8116 });
8117
8118 cx.notify();
8119
8120 Some(())
8121 }
8122
8123 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8124 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8125 }
8126
8127 fn clear_tasks(&mut self) {
8128 self.tasks.clear()
8129 }
8130
8131 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8132 if self.tasks.insert(key, value).is_some() {
8133 // This case should hopefully be rare, but just in case...
8134 log::error!(
8135 "multiple different run targets found on a single line, only the last target will be rendered"
8136 )
8137 }
8138 }
8139
8140 /// Get all display points of breakpoints that will be rendered within editor
8141 ///
8142 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8143 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8144 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8145 fn active_breakpoints(
8146 &self,
8147 range: Range<DisplayRow>,
8148 window: &mut Window,
8149 cx: &mut Context<Self>,
8150 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8151 let mut breakpoint_display_points = HashMap::default();
8152
8153 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8154 return breakpoint_display_points;
8155 };
8156
8157 let snapshot = self.snapshot(window, cx);
8158
8159 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8160 let Some(project) = self.project() else {
8161 return breakpoint_display_points;
8162 };
8163
8164 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8165 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8166
8167 for (buffer_snapshot, range, excerpt_id) in
8168 multi_buffer_snapshot.range_to_buffer_ranges(range)
8169 {
8170 let Some(buffer) = project
8171 .read(cx)
8172 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8173 else {
8174 continue;
8175 };
8176 let breakpoints = breakpoint_store.read(cx).breakpoints(
8177 &buffer,
8178 Some(
8179 buffer_snapshot.anchor_before(range.start)
8180 ..buffer_snapshot.anchor_after(range.end),
8181 ),
8182 buffer_snapshot,
8183 cx,
8184 );
8185 for (breakpoint, state) in breakpoints {
8186 let multi_buffer_anchor =
8187 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8188 let position = multi_buffer_anchor
8189 .to_point(&multi_buffer_snapshot)
8190 .to_display_point(&snapshot);
8191
8192 breakpoint_display_points.insert(
8193 position.row(),
8194 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8195 );
8196 }
8197 }
8198
8199 breakpoint_display_points
8200 }
8201
8202 fn breakpoint_context_menu(
8203 &self,
8204 anchor: Anchor,
8205 window: &mut Window,
8206 cx: &mut Context<Self>,
8207 ) -> Entity<ui::ContextMenu> {
8208 let weak_editor = cx.weak_entity();
8209 let focus_handle = self.focus_handle(cx);
8210
8211 let row = self
8212 .buffer
8213 .read(cx)
8214 .snapshot(cx)
8215 .summary_for_anchor::<Point>(&anchor)
8216 .row;
8217
8218 let breakpoint = self
8219 .breakpoint_at_row(row, window, cx)
8220 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8221
8222 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8223 "Edit Log Breakpoint"
8224 } else {
8225 "Set Log Breakpoint"
8226 };
8227
8228 let condition_breakpoint_msg = if breakpoint
8229 .as_ref()
8230 .is_some_and(|bp| bp.1.condition.is_some())
8231 {
8232 "Edit Condition Breakpoint"
8233 } else {
8234 "Set Condition Breakpoint"
8235 };
8236
8237 let hit_condition_breakpoint_msg = if breakpoint
8238 .as_ref()
8239 .is_some_and(|bp| bp.1.hit_condition.is_some())
8240 {
8241 "Edit Hit Condition Breakpoint"
8242 } else {
8243 "Set Hit Condition Breakpoint"
8244 };
8245
8246 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8247 "Unset Breakpoint"
8248 } else {
8249 "Set Breakpoint"
8250 };
8251
8252 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8253
8254 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8255 BreakpointState::Enabled => Some("Disable"),
8256 BreakpointState::Disabled => Some("Enable"),
8257 });
8258
8259 let (anchor, breakpoint) =
8260 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8261
8262 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8263 menu.on_blur_subscription(Subscription::new(|| {}))
8264 .context(focus_handle)
8265 .when(run_to_cursor, |this| {
8266 let weak_editor = weak_editor.clone();
8267 this.entry("Run to cursor", None, move |window, cx| {
8268 weak_editor
8269 .update(cx, |editor, cx| {
8270 editor.change_selections(
8271 SelectionEffects::no_scroll(),
8272 window,
8273 cx,
8274 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8275 );
8276 })
8277 .ok();
8278
8279 window.dispatch_action(Box::new(RunToCursor), cx);
8280 })
8281 .separator()
8282 })
8283 .when_some(toggle_state_msg, |this, msg| {
8284 this.entry(msg, None, {
8285 let weak_editor = weak_editor.clone();
8286 let breakpoint = breakpoint.clone();
8287 move |_window, cx| {
8288 weak_editor
8289 .update(cx, |this, cx| {
8290 this.edit_breakpoint_at_anchor(
8291 anchor,
8292 breakpoint.as_ref().clone(),
8293 BreakpointEditAction::InvertState,
8294 cx,
8295 );
8296 })
8297 .log_err();
8298 }
8299 })
8300 })
8301 .entry(set_breakpoint_msg, None, {
8302 let weak_editor = weak_editor.clone();
8303 let breakpoint = breakpoint.clone();
8304 move |_window, cx| {
8305 weak_editor
8306 .update(cx, |this, cx| {
8307 this.edit_breakpoint_at_anchor(
8308 anchor,
8309 breakpoint.as_ref().clone(),
8310 BreakpointEditAction::Toggle,
8311 cx,
8312 );
8313 })
8314 .log_err();
8315 }
8316 })
8317 .entry(log_breakpoint_msg, None, {
8318 let breakpoint = breakpoint.clone();
8319 let weak_editor = weak_editor.clone();
8320 move |window, cx| {
8321 weak_editor
8322 .update(cx, |this, cx| {
8323 this.add_edit_breakpoint_block(
8324 anchor,
8325 breakpoint.as_ref(),
8326 BreakpointPromptEditAction::Log,
8327 window,
8328 cx,
8329 );
8330 })
8331 .log_err();
8332 }
8333 })
8334 .entry(condition_breakpoint_msg, None, {
8335 let breakpoint = breakpoint.clone();
8336 let weak_editor = weak_editor.clone();
8337 move |window, cx| {
8338 weak_editor
8339 .update(cx, |this, cx| {
8340 this.add_edit_breakpoint_block(
8341 anchor,
8342 breakpoint.as_ref(),
8343 BreakpointPromptEditAction::Condition,
8344 window,
8345 cx,
8346 );
8347 })
8348 .log_err();
8349 }
8350 })
8351 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8352 weak_editor
8353 .update(cx, |this, cx| {
8354 this.add_edit_breakpoint_block(
8355 anchor,
8356 breakpoint.as_ref(),
8357 BreakpointPromptEditAction::HitCondition,
8358 window,
8359 cx,
8360 );
8361 })
8362 .log_err();
8363 })
8364 })
8365 }
8366
8367 fn render_breakpoint(
8368 &self,
8369 position: Anchor,
8370 row: DisplayRow,
8371 breakpoint: &Breakpoint,
8372 state: Option<BreakpointSessionState>,
8373 cx: &mut Context<Self>,
8374 ) -> IconButton {
8375 let is_rejected = state.is_some_and(|s| !s.verified);
8376 // Is it a breakpoint that shows up when hovering over gutter?
8377 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8378 (false, false),
8379 |PhantomBreakpointIndicator {
8380 is_active,
8381 display_row,
8382 collides_with_existing_breakpoint,
8383 }| {
8384 (
8385 is_active && display_row == row,
8386 collides_with_existing_breakpoint,
8387 )
8388 },
8389 );
8390
8391 let (color, icon) = {
8392 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8393 (false, false) => ui::IconName::DebugBreakpoint,
8394 (true, false) => ui::IconName::DebugLogBreakpoint,
8395 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8396 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8397 };
8398
8399 let color = if is_phantom {
8400 Color::Hint
8401 } else if is_rejected {
8402 Color::Disabled
8403 } else {
8404 Color::Debugger
8405 };
8406
8407 (color, icon)
8408 };
8409
8410 let breakpoint = Arc::from(breakpoint.clone());
8411
8412 let alt_as_text = gpui::Keystroke {
8413 modifiers: Modifiers::secondary_key(),
8414 ..Default::default()
8415 };
8416 let primary_action_text = if breakpoint.is_disabled() {
8417 "Enable breakpoint"
8418 } else if is_phantom && !collides_with_existing {
8419 "Set breakpoint"
8420 } else {
8421 "Unset breakpoint"
8422 };
8423 let focus_handle = self.focus_handle.clone();
8424
8425 let meta = if is_rejected {
8426 SharedString::from("No executable code is associated with this line.")
8427 } else if collides_with_existing && !breakpoint.is_disabled() {
8428 SharedString::from(format!(
8429 "{alt_as_text}-click to disable,\nright-click for more options."
8430 ))
8431 } else {
8432 SharedString::from("Right-click for more options.")
8433 };
8434 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8435 .icon_size(IconSize::XSmall)
8436 .size(ui::ButtonSize::None)
8437 .when(is_rejected, |this| {
8438 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8439 })
8440 .icon_color(color)
8441 .style(ButtonStyle::Transparent)
8442 .on_click(cx.listener({
8443 move |editor, event: &ClickEvent, window, cx| {
8444 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8445 BreakpointEditAction::InvertState
8446 } else {
8447 BreakpointEditAction::Toggle
8448 };
8449
8450 window.focus(&editor.focus_handle(cx));
8451 editor.edit_breakpoint_at_anchor(
8452 position,
8453 breakpoint.as_ref().clone(),
8454 edit_action,
8455 cx,
8456 );
8457 }
8458 }))
8459 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8460 editor.set_breakpoint_context_menu(
8461 row,
8462 Some(position),
8463 event.position(),
8464 window,
8465 cx,
8466 );
8467 }))
8468 .tooltip(move |_window, cx| {
8469 Tooltip::with_meta_in(
8470 primary_action_text,
8471 Some(&ToggleBreakpoint),
8472 meta.clone(),
8473 &focus_handle,
8474 cx,
8475 )
8476 })
8477 }
8478
8479 fn build_tasks_context(
8480 project: &Entity<Project>,
8481 buffer: &Entity<Buffer>,
8482 buffer_row: u32,
8483 tasks: &Arc<RunnableTasks>,
8484 cx: &mut Context<Self>,
8485 ) -> Task<Option<task::TaskContext>> {
8486 let position = Point::new(buffer_row, tasks.column);
8487 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8488 let location = Location {
8489 buffer: buffer.clone(),
8490 range: range_start..range_start,
8491 };
8492 // Fill in the environmental variables from the tree-sitter captures
8493 let mut captured_task_variables = TaskVariables::default();
8494 for (capture_name, value) in tasks.extra_variables.clone() {
8495 captured_task_variables.insert(
8496 task::VariableName::Custom(capture_name.into()),
8497 value.clone(),
8498 );
8499 }
8500 project.update(cx, |project, cx| {
8501 project.task_store().update(cx, |task_store, cx| {
8502 task_store.task_context_for_location(captured_task_variables, location, cx)
8503 })
8504 })
8505 }
8506
8507 pub fn spawn_nearest_task(
8508 &mut self,
8509 action: &SpawnNearestTask,
8510 window: &mut Window,
8511 cx: &mut Context<Self>,
8512 ) {
8513 let Some((workspace, _)) = self.workspace.clone() else {
8514 return;
8515 };
8516 let Some(project) = self.project.clone() else {
8517 return;
8518 };
8519
8520 // Try to find a closest, enclosing node using tree-sitter that has a task
8521 let Some((buffer, buffer_row, tasks)) = self
8522 .find_enclosing_node_task(cx)
8523 // Or find the task that's closest in row-distance.
8524 .or_else(|| self.find_closest_task(cx))
8525 else {
8526 return;
8527 };
8528
8529 let reveal_strategy = action.reveal;
8530 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8531 cx.spawn_in(window, async move |_, cx| {
8532 let context = task_context.await?;
8533 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8534
8535 let resolved = &mut resolved_task.resolved;
8536 resolved.reveal = reveal_strategy;
8537
8538 workspace
8539 .update_in(cx, |workspace, window, cx| {
8540 workspace.schedule_resolved_task(
8541 task_source_kind,
8542 resolved_task,
8543 false,
8544 window,
8545 cx,
8546 );
8547 })
8548 .ok()
8549 })
8550 .detach();
8551 }
8552
8553 fn find_closest_task(
8554 &mut self,
8555 cx: &mut Context<Self>,
8556 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8557 let cursor_row = self
8558 .selections
8559 .newest_adjusted(&self.display_snapshot(cx))
8560 .head()
8561 .row;
8562
8563 let ((buffer_id, row), tasks) = self
8564 .tasks
8565 .iter()
8566 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8567
8568 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8569 let tasks = Arc::new(tasks.to_owned());
8570 Some((buffer, *row, tasks))
8571 }
8572
8573 fn find_enclosing_node_task(
8574 &mut self,
8575 cx: &mut Context<Self>,
8576 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8577 let snapshot = self.buffer.read(cx).snapshot(cx);
8578 let offset = self
8579 .selections
8580 .newest::<usize>(&self.display_snapshot(cx))
8581 .head();
8582 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8583 let buffer_id = excerpt.buffer().remote_id();
8584
8585 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8586 let mut cursor = layer.node().walk();
8587
8588 while cursor.goto_first_child_for_byte(offset).is_some() {
8589 if cursor.node().end_byte() == offset {
8590 cursor.goto_next_sibling();
8591 }
8592 }
8593
8594 // Ascend to the smallest ancestor that contains the range and has a task.
8595 loop {
8596 let node = cursor.node();
8597 let node_range = node.byte_range();
8598 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8599
8600 // Check if this node contains our offset
8601 if node_range.start <= offset && node_range.end >= offset {
8602 // If it contains offset, check for task
8603 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8604 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8605 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8606 }
8607 }
8608
8609 if !cursor.goto_parent() {
8610 break;
8611 }
8612 }
8613 None
8614 }
8615
8616 fn render_run_indicator(
8617 &self,
8618 _style: &EditorStyle,
8619 is_active: bool,
8620 row: DisplayRow,
8621 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8622 cx: &mut Context<Self>,
8623 ) -> IconButton {
8624 let color = Color::Muted;
8625 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8626
8627 IconButton::new(
8628 ("run_indicator", row.0 as usize),
8629 ui::IconName::PlayOutlined,
8630 )
8631 .shape(ui::IconButtonShape::Square)
8632 .icon_size(IconSize::XSmall)
8633 .icon_color(color)
8634 .toggle_state(is_active)
8635 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8636 let quick_launch = match e {
8637 ClickEvent::Keyboard(_) => true,
8638 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8639 };
8640
8641 window.focus(&editor.focus_handle(cx));
8642 editor.toggle_code_actions(
8643 &ToggleCodeActions {
8644 deployed_from: Some(CodeActionSource::RunMenu(row)),
8645 quick_launch,
8646 },
8647 window,
8648 cx,
8649 );
8650 }))
8651 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8652 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8653 }))
8654 }
8655
8656 pub fn context_menu_visible(&self) -> bool {
8657 !self.edit_prediction_preview_is_active()
8658 && self
8659 .context_menu
8660 .borrow()
8661 .as_ref()
8662 .is_some_and(|menu| menu.visible())
8663 }
8664
8665 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8666 self.context_menu
8667 .borrow()
8668 .as_ref()
8669 .map(|menu| menu.origin())
8670 }
8671
8672 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8673 self.context_menu_options = Some(options);
8674 }
8675
8676 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8677 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8678
8679 fn render_edit_prediction_popover(
8680 &mut self,
8681 text_bounds: &Bounds<Pixels>,
8682 content_origin: gpui::Point<Pixels>,
8683 right_margin: Pixels,
8684 editor_snapshot: &EditorSnapshot,
8685 visible_row_range: Range<DisplayRow>,
8686 scroll_top: ScrollOffset,
8687 scroll_bottom: ScrollOffset,
8688 line_layouts: &[LineWithInvisibles],
8689 line_height: Pixels,
8690 scroll_position: gpui::Point<ScrollOffset>,
8691 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8692 newest_selection_head: Option<DisplayPoint>,
8693 editor_width: Pixels,
8694 style: &EditorStyle,
8695 window: &mut Window,
8696 cx: &mut App,
8697 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8698 if self.mode().is_minimap() {
8699 return None;
8700 }
8701 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8702
8703 if self.edit_prediction_visible_in_cursor_popover(true) {
8704 return None;
8705 }
8706
8707 match &active_edit_prediction.completion {
8708 EditPrediction::MoveWithin { target, .. } => {
8709 let target_display_point = target.to_display_point(editor_snapshot);
8710
8711 if self.edit_prediction_requires_modifier() {
8712 if !self.edit_prediction_preview_is_active() {
8713 return None;
8714 }
8715
8716 self.render_edit_prediction_modifier_jump_popover(
8717 text_bounds,
8718 content_origin,
8719 visible_row_range,
8720 line_layouts,
8721 line_height,
8722 scroll_pixel_position,
8723 newest_selection_head,
8724 target_display_point,
8725 window,
8726 cx,
8727 )
8728 } else {
8729 self.render_edit_prediction_eager_jump_popover(
8730 text_bounds,
8731 content_origin,
8732 editor_snapshot,
8733 visible_row_range,
8734 scroll_top,
8735 scroll_bottom,
8736 line_height,
8737 scroll_pixel_position,
8738 target_display_point,
8739 editor_width,
8740 window,
8741 cx,
8742 )
8743 }
8744 }
8745 EditPrediction::Edit {
8746 display_mode: EditDisplayMode::Inline,
8747 ..
8748 } => None,
8749 EditPrediction::Edit {
8750 display_mode: EditDisplayMode::TabAccept,
8751 edits,
8752 ..
8753 } => {
8754 let range = &edits.first()?.0;
8755 let target_display_point = range.end.to_display_point(editor_snapshot);
8756
8757 self.render_edit_prediction_end_of_line_popover(
8758 "Accept",
8759 editor_snapshot,
8760 visible_row_range,
8761 target_display_point,
8762 line_height,
8763 scroll_pixel_position,
8764 content_origin,
8765 editor_width,
8766 window,
8767 cx,
8768 )
8769 }
8770 EditPrediction::Edit {
8771 edits,
8772 edit_preview,
8773 display_mode: EditDisplayMode::DiffPopover,
8774 snapshot,
8775 } => self.render_edit_prediction_diff_popover(
8776 text_bounds,
8777 content_origin,
8778 right_margin,
8779 editor_snapshot,
8780 visible_row_range,
8781 line_layouts,
8782 line_height,
8783 scroll_position,
8784 scroll_pixel_position,
8785 newest_selection_head,
8786 editor_width,
8787 style,
8788 edits,
8789 edit_preview,
8790 snapshot,
8791 window,
8792 cx,
8793 ),
8794 EditPrediction::MoveOutside { snapshot, .. } => {
8795 let file_name = snapshot
8796 .file()
8797 .map(|file| file.file_name(cx))
8798 .unwrap_or("untitled");
8799 let mut element = self
8800 .render_edit_prediction_line_popover(
8801 format!("Jump to {file_name}"),
8802 Some(IconName::ZedPredict),
8803 window,
8804 cx,
8805 )
8806 .into_any();
8807
8808 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8809 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8810 let origin_y = text_bounds.size.height - size.height - px(30.);
8811 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8812 element.prepaint_at(origin, window, cx);
8813
8814 Some((element, origin))
8815 }
8816 }
8817 }
8818
8819 fn render_edit_prediction_modifier_jump_popover(
8820 &mut self,
8821 text_bounds: &Bounds<Pixels>,
8822 content_origin: gpui::Point<Pixels>,
8823 visible_row_range: Range<DisplayRow>,
8824 line_layouts: &[LineWithInvisibles],
8825 line_height: Pixels,
8826 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8827 newest_selection_head: Option<DisplayPoint>,
8828 target_display_point: DisplayPoint,
8829 window: &mut Window,
8830 cx: &mut App,
8831 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8832 let scrolled_content_origin =
8833 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8834
8835 const SCROLL_PADDING_Y: Pixels = px(12.);
8836
8837 if target_display_point.row() < visible_row_range.start {
8838 return self.render_edit_prediction_scroll_popover(
8839 |_| SCROLL_PADDING_Y,
8840 IconName::ArrowUp,
8841 visible_row_range,
8842 line_layouts,
8843 newest_selection_head,
8844 scrolled_content_origin,
8845 window,
8846 cx,
8847 );
8848 } else if target_display_point.row() >= visible_row_range.end {
8849 return self.render_edit_prediction_scroll_popover(
8850 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8851 IconName::ArrowDown,
8852 visible_row_range,
8853 line_layouts,
8854 newest_selection_head,
8855 scrolled_content_origin,
8856 window,
8857 cx,
8858 );
8859 }
8860
8861 const POLE_WIDTH: Pixels = px(2.);
8862
8863 let line_layout =
8864 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8865 let target_column = target_display_point.column() as usize;
8866
8867 let target_x = line_layout.x_for_index(target_column);
8868 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8869 - scroll_pixel_position.y;
8870
8871 let flag_on_right = target_x < text_bounds.size.width / 2.;
8872
8873 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8874 border_color.l += 0.001;
8875
8876 let mut element = v_flex()
8877 .items_end()
8878 .when(flag_on_right, |el| el.items_start())
8879 .child(if flag_on_right {
8880 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8881 .rounded_bl(px(0.))
8882 .rounded_tl(px(0.))
8883 .border_l_2()
8884 .border_color(border_color)
8885 } else {
8886 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8887 .rounded_br(px(0.))
8888 .rounded_tr(px(0.))
8889 .border_r_2()
8890 .border_color(border_color)
8891 })
8892 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8893 .into_any();
8894
8895 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8896
8897 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8898 - point(
8899 if flag_on_right {
8900 POLE_WIDTH
8901 } else {
8902 size.width - POLE_WIDTH
8903 },
8904 size.height - line_height,
8905 );
8906
8907 origin.x = origin.x.max(content_origin.x);
8908
8909 element.prepaint_at(origin, window, cx);
8910
8911 Some((element, origin))
8912 }
8913
8914 fn render_edit_prediction_scroll_popover(
8915 &mut self,
8916 to_y: impl Fn(Size<Pixels>) -> Pixels,
8917 scroll_icon: IconName,
8918 visible_row_range: Range<DisplayRow>,
8919 line_layouts: &[LineWithInvisibles],
8920 newest_selection_head: Option<DisplayPoint>,
8921 scrolled_content_origin: gpui::Point<Pixels>,
8922 window: &mut Window,
8923 cx: &mut App,
8924 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8925 let mut element = self
8926 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8927 .into_any();
8928
8929 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8930
8931 let cursor = newest_selection_head?;
8932 let cursor_row_layout =
8933 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8934 let cursor_column = cursor.column() as usize;
8935
8936 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8937
8938 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8939
8940 element.prepaint_at(origin, window, cx);
8941 Some((element, origin))
8942 }
8943
8944 fn render_edit_prediction_eager_jump_popover(
8945 &mut self,
8946 text_bounds: &Bounds<Pixels>,
8947 content_origin: gpui::Point<Pixels>,
8948 editor_snapshot: &EditorSnapshot,
8949 visible_row_range: Range<DisplayRow>,
8950 scroll_top: ScrollOffset,
8951 scroll_bottom: ScrollOffset,
8952 line_height: Pixels,
8953 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8954 target_display_point: DisplayPoint,
8955 editor_width: Pixels,
8956 window: &mut Window,
8957 cx: &mut App,
8958 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8959 if target_display_point.row().as_f64() < scroll_top {
8960 let mut element = self
8961 .render_edit_prediction_line_popover(
8962 "Jump to Edit",
8963 Some(IconName::ArrowUp),
8964 window,
8965 cx,
8966 )
8967 .into_any();
8968
8969 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8970 let offset = point(
8971 (text_bounds.size.width - size.width) / 2.,
8972 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8973 );
8974
8975 let origin = text_bounds.origin + offset;
8976 element.prepaint_at(origin, window, cx);
8977 Some((element, origin))
8978 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8979 let mut element = self
8980 .render_edit_prediction_line_popover(
8981 "Jump to Edit",
8982 Some(IconName::ArrowDown),
8983 window,
8984 cx,
8985 )
8986 .into_any();
8987
8988 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8989 let offset = point(
8990 (text_bounds.size.width - size.width) / 2.,
8991 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8992 );
8993
8994 let origin = text_bounds.origin + offset;
8995 element.prepaint_at(origin, window, cx);
8996 Some((element, origin))
8997 } else {
8998 self.render_edit_prediction_end_of_line_popover(
8999 "Jump to Edit",
9000 editor_snapshot,
9001 visible_row_range,
9002 target_display_point,
9003 line_height,
9004 scroll_pixel_position,
9005 content_origin,
9006 editor_width,
9007 window,
9008 cx,
9009 )
9010 }
9011 }
9012
9013 fn render_edit_prediction_end_of_line_popover(
9014 self: &mut Editor,
9015 label: &'static str,
9016 editor_snapshot: &EditorSnapshot,
9017 visible_row_range: Range<DisplayRow>,
9018 target_display_point: DisplayPoint,
9019 line_height: Pixels,
9020 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9021 content_origin: gpui::Point<Pixels>,
9022 editor_width: Pixels,
9023 window: &mut Window,
9024 cx: &mut App,
9025 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9026 let target_line_end = DisplayPoint::new(
9027 target_display_point.row(),
9028 editor_snapshot.line_len(target_display_point.row()),
9029 );
9030
9031 let mut element = self
9032 .render_edit_prediction_line_popover(label, None, window, cx)
9033 .into_any();
9034
9035 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9036
9037 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9038
9039 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9040 let mut origin = start_point
9041 + line_origin
9042 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9043 origin.x = origin.x.max(content_origin.x);
9044
9045 let max_x = content_origin.x + editor_width - size.width;
9046
9047 if origin.x > max_x {
9048 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9049
9050 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9051 origin.y += offset;
9052 IconName::ArrowUp
9053 } else {
9054 origin.y -= offset;
9055 IconName::ArrowDown
9056 };
9057
9058 element = self
9059 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9060 .into_any();
9061
9062 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9063
9064 origin.x = content_origin.x + editor_width - size.width - px(2.);
9065 }
9066
9067 element.prepaint_at(origin, window, cx);
9068 Some((element, origin))
9069 }
9070
9071 fn render_edit_prediction_diff_popover(
9072 self: &Editor,
9073 text_bounds: &Bounds<Pixels>,
9074 content_origin: gpui::Point<Pixels>,
9075 right_margin: Pixels,
9076 editor_snapshot: &EditorSnapshot,
9077 visible_row_range: Range<DisplayRow>,
9078 line_layouts: &[LineWithInvisibles],
9079 line_height: Pixels,
9080 scroll_position: gpui::Point<ScrollOffset>,
9081 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9082 newest_selection_head: Option<DisplayPoint>,
9083 editor_width: Pixels,
9084 style: &EditorStyle,
9085 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9086 edit_preview: &Option<language::EditPreview>,
9087 snapshot: &language::BufferSnapshot,
9088 window: &mut Window,
9089 cx: &mut App,
9090 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9091 let edit_start = edits
9092 .first()
9093 .unwrap()
9094 .0
9095 .start
9096 .to_display_point(editor_snapshot);
9097 let edit_end = edits
9098 .last()
9099 .unwrap()
9100 .0
9101 .end
9102 .to_display_point(editor_snapshot);
9103
9104 let is_visible = visible_row_range.contains(&edit_start.row())
9105 || visible_row_range.contains(&edit_end.row());
9106 if !is_visible {
9107 return None;
9108 }
9109
9110 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9111 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9112 } else {
9113 // Fallback for providers without edit_preview
9114 crate::edit_prediction_fallback_text(edits, cx)
9115 };
9116
9117 let styled_text = highlighted_edits.to_styled_text(&style.text);
9118 let line_count = highlighted_edits.text.lines().count();
9119
9120 const BORDER_WIDTH: Pixels = px(1.);
9121
9122 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9123 let has_keybind = keybind.is_some();
9124
9125 let mut element = h_flex()
9126 .items_start()
9127 .child(
9128 h_flex()
9129 .bg(cx.theme().colors().editor_background)
9130 .border(BORDER_WIDTH)
9131 .shadow_xs()
9132 .border_color(cx.theme().colors().border)
9133 .rounded_l_lg()
9134 .when(line_count > 1, |el| el.rounded_br_lg())
9135 .pr_1()
9136 .child(styled_text),
9137 )
9138 .child(
9139 h_flex()
9140 .h(line_height + BORDER_WIDTH * 2.)
9141 .px_1p5()
9142 .gap_1()
9143 // Workaround: For some reason, there's a gap if we don't do this
9144 .ml(-BORDER_WIDTH)
9145 .shadow(vec![gpui::BoxShadow {
9146 color: gpui::black().opacity(0.05),
9147 offset: point(px(1.), px(1.)),
9148 blur_radius: px(2.),
9149 spread_radius: px(0.),
9150 }])
9151 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9152 .border(BORDER_WIDTH)
9153 .border_color(cx.theme().colors().border)
9154 .rounded_r_lg()
9155 .id("edit_prediction_diff_popover_keybind")
9156 .when(!has_keybind, |el| {
9157 let status_colors = cx.theme().status();
9158
9159 el.bg(status_colors.error_background)
9160 .border_color(status_colors.error.opacity(0.6))
9161 .child(Icon::new(IconName::Info).color(Color::Error))
9162 .cursor_default()
9163 .hoverable_tooltip(move |_window, cx| {
9164 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9165 })
9166 })
9167 .children(keybind),
9168 )
9169 .into_any();
9170
9171 let longest_row =
9172 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9173 let longest_line_width = if visible_row_range.contains(&longest_row) {
9174 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9175 } else {
9176 layout_line(
9177 longest_row,
9178 editor_snapshot,
9179 style,
9180 editor_width,
9181 |_| false,
9182 window,
9183 cx,
9184 )
9185 .width
9186 };
9187
9188 let viewport_bounds =
9189 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9190 right: -right_margin,
9191 ..Default::default()
9192 });
9193
9194 let x_after_longest = Pixels::from(
9195 ScrollPixelOffset::from(
9196 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9197 ) - scroll_pixel_position.x,
9198 );
9199
9200 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9201
9202 // Fully visible if it can be displayed within the window (allow overlapping other
9203 // panes). However, this is only allowed if the popover starts within text_bounds.
9204 let can_position_to_the_right = x_after_longest < text_bounds.right()
9205 && x_after_longest + element_bounds.width < viewport_bounds.right();
9206
9207 let mut origin = if can_position_to_the_right {
9208 point(
9209 x_after_longest,
9210 text_bounds.origin.y
9211 + Pixels::from(
9212 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9213 - scroll_pixel_position.y,
9214 ),
9215 )
9216 } else {
9217 let cursor_row = newest_selection_head.map(|head| head.row());
9218 let above_edit = edit_start
9219 .row()
9220 .0
9221 .checked_sub(line_count as u32)
9222 .map(DisplayRow);
9223 let below_edit = Some(edit_end.row() + 1);
9224 let above_cursor =
9225 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9226 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9227
9228 // Place the edit popover adjacent to the edit if there is a location
9229 // available that is onscreen and does not obscure the cursor. Otherwise,
9230 // place it adjacent to the cursor.
9231 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9232 .into_iter()
9233 .flatten()
9234 .find(|&start_row| {
9235 let end_row = start_row + line_count as u32;
9236 visible_row_range.contains(&start_row)
9237 && visible_row_range.contains(&end_row)
9238 && cursor_row
9239 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9240 })?;
9241
9242 content_origin
9243 + point(
9244 Pixels::from(-scroll_pixel_position.x),
9245 Pixels::from(
9246 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9247 ),
9248 )
9249 };
9250
9251 origin.x -= BORDER_WIDTH;
9252
9253 window.defer_draw(element, origin, 1);
9254
9255 // Do not return an element, since it will already be drawn due to defer_draw.
9256 None
9257 }
9258
9259 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9260 px(30.)
9261 }
9262
9263 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9264 if self.read_only(cx) {
9265 cx.theme().players().read_only()
9266 } else {
9267 self.style.as_ref().unwrap().local_player
9268 }
9269 }
9270
9271 fn render_edit_prediction_accept_keybind(
9272 &self,
9273 window: &mut Window,
9274 cx: &mut App,
9275 ) -> Option<AnyElement> {
9276 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9277 let accept_keystroke = accept_binding.keystroke()?;
9278
9279 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9280
9281 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9282 Color::Accent
9283 } else {
9284 Color::Muted
9285 };
9286
9287 h_flex()
9288 .px_0p5()
9289 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9290 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9291 .text_size(TextSize::XSmall.rems(cx))
9292 .child(h_flex().children(ui::render_modifiers(
9293 accept_keystroke.modifiers(),
9294 PlatformStyle::platform(),
9295 Some(modifiers_color),
9296 Some(IconSize::XSmall.rems().into()),
9297 true,
9298 )))
9299 .when(is_platform_style_mac, |parent| {
9300 parent.child(accept_keystroke.key().to_string())
9301 })
9302 .when(!is_platform_style_mac, |parent| {
9303 parent.child(
9304 Key::new(
9305 util::capitalize(accept_keystroke.key()),
9306 Some(Color::Default),
9307 )
9308 .size(Some(IconSize::XSmall.rems().into())),
9309 )
9310 })
9311 .into_any()
9312 .into()
9313 }
9314
9315 fn render_edit_prediction_line_popover(
9316 &self,
9317 label: impl Into<SharedString>,
9318 icon: Option<IconName>,
9319 window: &mut Window,
9320 cx: &mut App,
9321 ) -> Stateful<Div> {
9322 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9323
9324 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9325 let has_keybind = keybind.is_some();
9326
9327 h_flex()
9328 .id("ep-line-popover")
9329 .py_0p5()
9330 .pl_1()
9331 .pr(padding_right)
9332 .gap_1()
9333 .rounded_md()
9334 .border_1()
9335 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9336 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9337 .shadow_xs()
9338 .when(!has_keybind, |el| {
9339 let status_colors = cx.theme().status();
9340
9341 el.bg(status_colors.error_background)
9342 .border_color(status_colors.error.opacity(0.6))
9343 .pl_2()
9344 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9345 .cursor_default()
9346 .hoverable_tooltip(move |_window, cx| {
9347 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9348 })
9349 })
9350 .children(keybind)
9351 .child(
9352 Label::new(label)
9353 .size(LabelSize::Small)
9354 .when(!has_keybind, |el| {
9355 el.color(cx.theme().status().error.into()).strikethrough()
9356 }),
9357 )
9358 .when(!has_keybind, |el| {
9359 el.child(
9360 h_flex().ml_1().child(
9361 Icon::new(IconName::Info)
9362 .size(IconSize::Small)
9363 .color(cx.theme().status().error.into()),
9364 ),
9365 )
9366 })
9367 .when_some(icon, |element, icon| {
9368 element.child(
9369 div()
9370 .mt(px(1.5))
9371 .child(Icon::new(icon).size(IconSize::Small)),
9372 )
9373 })
9374 }
9375
9376 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9377 let accent_color = cx.theme().colors().text_accent;
9378 let editor_bg_color = cx.theme().colors().editor_background;
9379 editor_bg_color.blend(accent_color.opacity(0.1))
9380 }
9381
9382 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9383 let accent_color = cx.theme().colors().text_accent;
9384 let editor_bg_color = cx.theme().colors().editor_background;
9385 editor_bg_color.blend(accent_color.opacity(0.6))
9386 }
9387 fn get_prediction_provider_icon_name(
9388 provider: &Option<RegisteredEditPredictionProvider>,
9389 ) -> IconName {
9390 match provider {
9391 Some(provider) => match provider.provider.name() {
9392 "copilot" => IconName::Copilot,
9393 "supermaven" => IconName::Supermaven,
9394 _ => IconName::ZedPredict,
9395 },
9396 None => IconName::ZedPredict,
9397 }
9398 }
9399
9400 fn render_edit_prediction_cursor_popover(
9401 &self,
9402 min_width: Pixels,
9403 max_width: Pixels,
9404 cursor_point: Point,
9405 style: &EditorStyle,
9406 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9407 _window: &Window,
9408 cx: &mut Context<Editor>,
9409 ) -> Option<AnyElement> {
9410 let provider = self.edit_prediction_provider.as_ref()?;
9411 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9412
9413 let is_refreshing = provider.provider.is_refreshing(cx);
9414
9415 fn pending_completion_container(icon: IconName) -> Div {
9416 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9417 }
9418
9419 let completion = match &self.active_edit_prediction {
9420 Some(prediction) => {
9421 if !self.has_visible_completions_menu() {
9422 const RADIUS: Pixels = px(6.);
9423 const BORDER_WIDTH: Pixels = px(1.);
9424
9425 return Some(
9426 h_flex()
9427 .elevation_2(cx)
9428 .border(BORDER_WIDTH)
9429 .border_color(cx.theme().colors().border)
9430 .when(accept_keystroke.is_none(), |el| {
9431 el.border_color(cx.theme().status().error)
9432 })
9433 .rounded(RADIUS)
9434 .rounded_tl(px(0.))
9435 .overflow_hidden()
9436 .child(div().px_1p5().child(match &prediction.completion {
9437 EditPrediction::MoveWithin { target, snapshot } => {
9438 use text::ToPoint as _;
9439 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9440 {
9441 Icon::new(IconName::ZedPredictDown)
9442 } else {
9443 Icon::new(IconName::ZedPredictUp)
9444 }
9445 }
9446 EditPrediction::MoveOutside { .. } => {
9447 // TODO [zeta2] custom icon for external jump?
9448 Icon::new(provider_icon)
9449 }
9450 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9451 }))
9452 .child(
9453 h_flex()
9454 .gap_1()
9455 .py_1()
9456 .px_2()
9457 .rounded_r(RADIUS - BORDER_WIDTH)
9458 .border_l_1()
9459 .border_color(cx.theme().colors().border)
9460 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9461 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9462 el.child(
9463 Label::new("Hold")
9464 .size(LabelSize::Small)
9465 .when(accept_keystroke.is_none(), |el| {
9466 el.strikethrough()
9467 })
9468 .line_height_style(LineHeightStyle::UiLabel),
9469 )
9470 })
9471 .id("edit_prediction_cursor_popover_keybind")
9472 .when(accept_keystroke.is_none(), |el| {
9473 let status_colors = cx.theme().status();
9474
9475 el.bg(status_colors.error_background)
9476 .border_color(status_colors.error.opacity(0.6))
9477 .child(Icon::new(IconName::Info).color(Color::Error))
9478 .cursor_default()
9479 .hoverable_tooltip(move |_window, cx| {
9480 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9481 .into()
9482 })
9483 })
9484 .when_some(
9485 accept_keystroke.as_ref(),
9486 |el, accept_keystroke| {
9487 el.child(h_flex().children(ui::render_modifiers(
9488 accept_keystroke.modifiers(),
9489 PlatformStyle::platform(),
9490 Some(Color::Default),
9491 Some(IconSize::XSmall.rems().into()),
9492 false,
9493 )))
9494 },
9495 ),
9496 )
9497 .into_any(),
9498 );
9499 }
9500
9501 self.render_edit_prediction_cursor_popover_preview(
9502 prediction,
9503 cursor_point,
9504 style,
9505 cx,
9506 )?
9507 }
9508
9509 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9510 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9511 stale_completion,
9512 cursor_point,
9513 style,
9514 cx,
9515 )?,
9516
9517 None => pending_completion_container(provider_icon)
9518 .child(Label::new("...").size(LabelSize::Small)),
9519 },
9520
9521 None => pending_completion_container(provider_icon)
9522 .child(Label::new("...").size(LabelSize::Small)),
9523 };
9524
9525 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9526 completion
9527 .with_animation(
9528 "loading-completion",
9529 Animation::new(Duration::from_secs(2))
9530 .repeat()
9531 .with_easing(pulsating_between(0.4, 0.8)),
9532 |label, delta| label.opacity(delta),
9533 )
9534 .into_any_element()
9535 } else {
9536 completion.into_any_element()
9537 };
9538
9539 let has_completion = self.active_edit_prediction.is_some();
9540
9541 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9542 Some(
9543 h_flex()
9544 .min_w(min_width)
9545 .max_w(max_width)
9546 .flex_1()
9547 .elevation_2(cx)
9548 .border_color(cx.theme().colors().border)
9549 .child(
9550 div()
9551 .flex_1()
9552 .py_1()
9553 .px_2()
9554 .overflow_hidden()
9555 .child(completion),
9556 )
9557 .when_some(accept_keystroke, |el, accept_keystroke| {
9558 if !accept_keystroke.modifiers().modified() {
9559 return el;
9560 }
9561
9562 el.child(
9563 h_flex()
9564 .h_full()
9565 .border_l_1()
9566 .rounded_r_lg()
9567 .border_color(cx.theme().colors().border)
9568 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9569 .gap_1()
9570 .py_1()
9571 .px_2()
9572 .child(
9573 h_flex()
9574 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9575 .when(is_platform_style_mac, |parent| parent.gap_1())
9576 .child(h_flex().children(ui::render_modifiers(
9577 accept_keystroke.modifiers(),
9578 PlatformStyle::platform(),
9579 Some(if !has_completion {
9580 Color::Muted
9581 } else {
9582 Color::Default
9583 }),
9584 None,
9585 false,
9586 ))),
9587 )
9588 .child(Label::new("Preview").into_any_element())
9589 .opacity(if has_completion { 1.0 } else { 0.4 }),
9590 )
9591 })
9592 .into_any(),
9593 )
9594 }
9595
9596 fn render_edit_prediction_cursor_popover_preview(
9597 &self,
9598 completion: &EditPredictionState,
9599 cursor_point: Point,
9600 style: &EditorStyle,
9601 cx: &mut Context<Editor>,
9602 ) -> Option<Div> {
9603 use text::ToPoint as _;
9604
9605 fn render_relative_row_jump(
9606 prefix: impl Into<String>,
9607 current_row: u32,
9608 target_row: u32,
9609 ) -> Div {
9610 let (row_diff, arrow) = if target_row < current_row {
9611 (current_row - target_row, IconName::ArrowUp)
9612 } else {
9613 (target_row - current_row, IconName::ArrowDown)
9614 };
9615
9616 h_flex()
9617 .child(
9618 Label::new(format!("{}{}", prefix.into(), row_diff))
9619 .color(Color::Muted)
9620 .size(LabelSize::Small),
9621 )
9622 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9623 }
9624
9625 let supports_jump = self
9626 .edit_prediction_provider
9627 .as_ref()
9628 .map(|provider| provider.provider.supports_jump_to_edit())
9629 .unwrap_or(true);
9630
9631 match &completion.completion {
9632 EditPrediction::MoveWithin {
9633 target, snapshot, ..
9634 } => {
9635 if !supports_jump {
9636 return None;
9637 }
9638
9639 Some(
9640 h_flex()
9641 .px_2()
9642 .gap_2()
9643 .flex_1()
9644 .child(
9645 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9646 Icon::new(IconName::ZedPredictDown)
9647 } else {
9648 Icon::new(IconName::ZedPredictUp)
9649 },
9650 )
9651 .child(Label::new("Jump to Edit")),
9652 )
9653 }
9654 EditPrediction::MoveOutside { snapshot, .. } => {
9655 let file_name = snapshot
9656 .file()
9657 .map(|file| file.file_name(cx))
9658 .unwrap_or("untitled");
9659 Some(
9660 h_flex()
9661 .px_2()
9662 .gap_2()
9663 .flex_1()
9664 .child(Icon::new(IconName::ZedPredict))
9665 .child(Label::new(format!("Jump to {file_name}"))),
9666 )
9667 }
9668 EditPrediction::Edit {
9669 edits,
9670 edit_preview,
9671 snapshot,
9672 display_mode: _,
9673 } => {
9674 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9675
9676 let (highlighted_edits, has_more_lines) =
9677 if let Some(edit_preview) = edit_preview.as_ref() {
9678 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9679 .first_line_preview()
9680 } else {
9681 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9682 };
9683
9684 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9685 .with_default_highlights(&style.text, highlighted_edits.highlights);
9686
9687 let preview = h_flex()
9688 .gap_1()
9689 .min_w_16()
9690 .child(styled_text)
9691 .when(has_more_lines, |parent| parent.child("…"));
9692
9693 let left = if supports_jump && first_edit_row != cursor_point.row {
9694 render_relative_row_jump("", cursor_point.row, first_edit_row)
9695 .into_any_element()
9696 } else {
9697 let icon_name =
9698 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9699 Icon::new(icon_name).into_any_element()
9700 };
9701
9702 Some(
9703 h_flex()
9704 .h_full()
9705 .flex_1()
9706 .gap_2()
9707 .pr_1()
9708 .overflow_x_hidden()
9709 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9710 .child(left)
9711 .child(preview),
9712 )
9713 }
9714 }
9715 }
9716
9717 pub fn render_context_menu(
9718 &self,
9719 style: &EditorStyle,
9720 max_height_in_lines: u32,
9721 window: &mut Window,
9722 cx: &mut Context<Editor>,
9723 ) -> Option<AnyElement> {
9724 let menu = self.context_menu.borrow();
9725 let menu = menu.as_ref()?;
9726 if !menu.visible() {
9727 return None;
9728 };
9729 Some(menu.render(style, max_height_in_lines, window, cx))
9730 }
9731
9732 fn render_context_menu_aside(
9733 &mut self,
9734 max_size: Size<Pixels>,
9735 window: &mut Window,
9736 cx: &mut Context<Editor>,
9737 ) -> Option<AnyElement> {
9738 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9739 if menu.visible() {
9740 menu.render_aside(max_size, window, cx)
9741 } else {
9742 None
9743 }
9744 })
9745 }
9746
9747 fn hide_context_menu(
9748 &mut self,
9749 window: &mut Window,
9750 cx: &mut Context<Self>,
9751 ) -> Option<CodeContextMenu> {
9752 cx.notify();
9753 self.completion_tasks.clear();
9754 let context_menu = self.context_menu.borrow_mut().take();
9755 self.stale_edit_prediction_in_menu.take();
9756 self.update_visible_edit_prediction(window, cx);
9757 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9758 && let Some(completion_provider) = &self.completion_provider
9759 {
9760 completion_provider.selection_changed(None, window, cx);
9761 }
9762 context_menu
9763 }
9764
9765 fn show_snippet_choices(
9766 &mut self,
9767 choices: &Vec<String>,
9768 selection: Range<Anchor>,
9769 cx: &mut Context<Self>,
9770 ) {
9771 let Some((_, buffer, _)) = self
9772 .buffer()
9773 .read(cx)
9774 .excerpt_containing(selection.start, cx)
9775 else {
9776 return;
9777 };
9778 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9779 else {
9780 return;
9781 };
9782 if buffer != end_buffer {
9783 log::error!("expected anchor range to have matching buffer IDs");
9784 return;
9785 }
9786
9787 let id = post_inc(&mut self.next_completion_id);
9788 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9789 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9790 CompletionsMenu::new_snippet_choices(
9791 id,
9792 true,
9793 choices,
9794 selection,
9795 buffer,
9796 snippet_sort_order,
9797 ),
9798 ));
9799 }
9800
9801 pub fn insert_snippet(
9802 &mut self,
9803 insertion_ranges: &[Range<usize>],
9804 snippet: Snippet,
9805 window: &mut Window,
9806 cx: &mut Context<Self>,
9807 ) -> Result<()> {
9808 struct Tabstop<T> {
9809 is_end_tabstop: bool,
9810 ranges: Vec<Range<T>>,
9811 choices: Option<Vec<String>>,
9812 }
9813
9814 let tabstops = self.buffer.update(cx, |buffer, cx| {
9815 let snippet_text: Arc<str> = snippet.text.clone().into();
9816 let edits = insertion_ranges
9817 .iter()
9818 .cloned()
9819 .map(|range| (range, snippet_text.clone()));
9820 let autoindent_mode = AutoindentMode::Block {
9821 original_indent_columns: Vec::new(),
9822 };
9823 buffer.edit(edits, Some(autoindent_mode), cx);
9824
9825 let snapshot = &*buffer.read(cx);
9826 let snippet = &snippet;
9827 snippet
9828 .tabstops
9829 .iter()
9830 .map(|tabstop| {
9831 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9832 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9833 });
9834 let mut tabstop_ranges = tabstop
9835 .ranges
9836 .iter()
9837 .flat_map(|tabstop_range| {
9838 let mut delta = 0_isize;
9839 insertion_ranges.iter().map(move |insertion_range| {
9840 let insertion_start = insertion_range.start as isize + delta;
9841 delta +=
9842 snippet.text.len() as isize - insertion_range.len() as isize;
9843
9844 let start = ((insertion_start + tabstop_range.start) as usize)
9845 .min(snapshot.len());
9846 let end = ((insertion_start + tabstop_range.end) as usize)
9847 .min(snapshot.len());
9848 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9849 })
9850 })
9851 .collect::<Vec<_>>();
9852 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9853
9854 Tabstop {
9855 is_end_tabstop,
9856 ranges: tabstop_ranges,
9857 choices: tabstop.choices.clone(),
9858 }
9859 })
9860 .collect::<Vec<_>>()
9861 });
9862 if let Some(tabstop) = tabstops.first() {
9863 self.change_selections(Default::default(), window, cx, |s| {
9864 // Reverse order so that the first range is the newest created selection.
9865 // Completions will use it and autoscroll will prioritize it.
9866 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9867 });
9868
9869 if let Some(choices) = &tabstop.choices
9870 && let Some(selection) = tabstop.ranges.first()
9871 {
9872 self.show_snippet_choices(choices, selection.clone(), cx)
9873 }
9874
9875 // If we're already at the last tabstop and it's at the end of the snippet,
9876 // we're done, we don't need to keep the state around.
9877 if !tabstop.is_end_tabstop {
9878 let choices = tabstops
9879 .iter()
9880 .map(|tabstop| tabstop.choices.clone())
9881 .collect();
9882
9883 let ranges = tabstops
9884 .into_iter()
9885 .map(|tabstop| tabstop.ranges)
9886 .collect::<Vec<_>>();
9887
9888 self.snippet_stack.push(SnippetState {
9889 active_index: 0,
9890 ranges,
9891 choices,
9892 });
9893 }
9894
9895 // Check whether the just-entered snippet ends with an auto-closable bracket.
9896 if self.autoclose_regions.is_empty() {
9897 let snapshot = self.buffer.read(cx).snapshot(cx);
9898 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9899 let selection_head = selection.head();
9900 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9901 continue;
9902 };
9903
9904 let mut bracket_pair = None;
9905 let max_lookup_length = scope
9906 .brackets()
9907 .map(|(pair, _)| {
9908 pair.start
9909 .as_str()
9910 .chars()
9911 .count()
9912 .max(pair.end.as_str().chars().count())
9913 })
9914 .max();
9915 if let Some(max_lookup_length) = max_lookup_length {
9916 let next_text = snapshot
9917 .chars_at(selection_head)
9918 .take(max_lookup_length)
9919 .collect::<String>();
9920 let prev_text = snapshot
9921 .reversed_chars_at(selection_head)
9922 .take(max_lookup_length)
9923 .collect::<String>();
9924
9925 for (pair, enabled) in scope.brackets() {
9926 if enabled
9927 && pair.close
9928 && prev_text.starts_with(pair.start.as_str())
9929 && next_text.starts_with(pair.end.as_str())
9930 {
9931 bracket_pair = Some(pair.clone());
9932 break;
9933 }
9934 }
9935 }
9936
9937 if let Some(pair) = bracket_pair {
9938 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9939 let autoclose_enabled =
9940 self.use_autoclose && snapshot_settings.use_autoclose;
9941 if autoclose_enabled {
9942 let start = snapshot.anchor_after(selection_head);
9943 let end = snapshot.anchor_after(selection_head);
9944 self.autoclose_regions.push(AutocloseRegion {
9945 selection_id: selection.id,
9946 range: start..end,
9947 pair,
9948 });
9949 }
9950 }
9951 }
9952 }
9953 }
9954 Ok(())
9955 }
9956
9957 pub fn move_to_next_snippet_tabstop(
9958 &mut self,
9959 window: &mut Window,
9960 cx: &mut Context<Self>,
9961 ) -> bool {
9962 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9963 }
9964
9965 pub fn move_to_prev_snippet_tabstop(
9966 &mut self,
9967 window: &mut Window,
9968 cx: &mut Context<Self>,
9969 ) -> bool {
9970 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9971 }
9972
9973 pub fn move_to_snippet_tabstop(
9974 &mut self,
9975 bias: Bias,
9976 window: &mut Window,
9977 cx: &mut Context<Self>,
9978 ) -> bool {
9979 if let Some(mut snippet) = self.snippet_stack.pop() {
9980 match bias {
9981 Bias::Left => {
9982 if snippet.active_index > 0 {
9983 snippet.active_index -= 1;
9984 } else {
9985 self.snippet_stack.push(snippet);
9986 return false;
9987 }
9988 }
9989 Bias::Right => {
9990 if snippet.active_index + 1 < snippet.ranges.len() {
9991 snippet.active_index += 1;
9992 } else {
9993 self.snippet_stack.push(snippet);
9994 return false;
9995 }
9996 }
9997 }
9998 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9999 self.change_selections(Default::default(), window, cx, |s| {
10000 // Reverse order so that the first range is the newest created selection.
10001 // Completions will use it and autoscroll will prioritize it.
10002 s.select_ranges(current_ranges.iter().rev().cloned())
10003 });
10004
10005 if let Some(choices) = &snippet.choices[snippet.active_index]
10006 && let Some(selection) = current_ranges.first()
10007 {
10008 self.show_snippet_choices(choices, selection.clone(), cx);
10009 }
10010
10011 // If snippet state is not at the last tabstop, push it back on the stack
10012 if snippet.active_index + 1 < snippet.ranges.len() {
10013 self.snippet_stack.push(snippet);
10014 }
10015 return true;
10016 }
10017 }
10018
10019 false
10020 }
10021
10022 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10023 self.transact(window, cx, |this, window, cx| {
10024 this.select_all(&SelectAll, window, cx);
10025 this.insert("", window, cx);
10026 });
10027 }
10028
10029 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10030 if self.read_only(cx) {
10031 return;
10032 }
10033 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10034 self.transact(window, cx, |this, window, cx| {
10035 this.select_autoclose_pair(window, cx);
10036
10037 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10038
10039 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10040 if !this.linked_edit_ranges.is_empty() {
10041 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10042 let snapshot = this.buffer.read(cx).snapshot(cx);
10043
10044 for selection in selections.iter() {
10045 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10046 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10047 if selection_start.buffer_id != selection_end.buffer_id {
10048 continue;
10049 }
10050 if let Some(ranges) =
10051 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10052 {
10053 for (buffer, entries) in ranges {
10054 linked_ranges.entry(buffer).or_default().extend(entries);
10055 }
10056 }
10057 }
10058 }
10059
10060 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10061 for selection in &mut selections {
10062 if selection.is_empty() {
10063 let old_head = selection.head();
10064 let mut new_head =
10065 movement::left(&display_map, old_head.to_display_point(&display_map))
10066 .to_point(&display_map);
10067 if let Some((buffer, line_buffer_range)) = display_map
10068 .buffer_snapshot()
10069 .buffer_line_for_row(MultiBufferRow(old_head.row))
10070 {
10071 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10072 let indent_len = match indent_size.kind {
10073 IndentKind::Space => {
10074 buffer.settings_at(line_buffer_range.start, cx).tab_size
10075 }
10076 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10077 };
10078 if old_head.column <= indent_size.len && old_head.column > 0 {
10079 let indent_len = indent_len.get();
10080 new_head = cmp::min(
10081 new_head,
10082 MultiBufferPoint::new(
10083 old_head.row,
10084 ((old_head.column - 1) / indent_len) * indent_len,
10085 ),
10086 );
10087 }
10088 }
10089
10090 selection.set_head(new_head, SelectionGoal::None);
10091 }
10092 }
10093
10094 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10095 this.insert("", window, cx);
10096 let empty_str: Arc<str> = Arc::from("");
10097 for (buffer, edits) in linked_ranges {
10098 let snapshot = buffer.read(cx).snapshot();
10099 use text::ToPoint as TP;
10100
10101 let edits = edits
10102 .into_iter()
10103 .map(|range| {
10104 let end_point = TP::to_point(&range.end, &snapshot);
10105 let mut start_point = TP::to_point(&range.start, &snapshot);
10106
10107 if end_point == start_point {
10108 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10109 .saturating_sub(1);
10110 start_point =
10111 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10112 };
10113
10114 (start_point..end_point, empty_str.clone())
10115 })
10116 .sorted_by_key(|(range, _)| range.start)
10117 .collect::<Vec<_>>();
10118 buffer.update(cx, |this, cx| {
10119 this.edit(edits, None, cx);
10120 })
10121 }
10122 this.refresh_edit_prediction(true, false, window, cx);
10123 refresh_linked_ranges(this, window, cx);
10124 });
10125 }
10126
10127 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10128 if self.read_only(cx) {
10129 return;
10130 }
10131 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10132 self.transact(window, cx, |this, window, cx| {
10133 this.change_selections(Default::default(), window, cx, |s| {
10134 s.move_with(|map, selection| {
10135 if selection.is_empty() {
10136 let cursor = movement::right(map, selection.head());
10137 selection.end = cursor;
10138 selection.reversed = true;
10139 selection.goal = SelectionGoal::None;
10140 }
10141 })
10142 });
10143 this.insert("", window, cx);
10144 this.refresh_edit_prediction(true, false, window, cx);
10145 });
10146 }
10147
10148 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10149 if self.mode.is_single_line() {
10150 cx.propagate();
10151 return;
10152 }
10153
10154 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10155 if self.move_to_prev_snippet_tabstop(window, cx) {
10156 return;
10157 }
10158 self.outdent(&Outdent, window, cx);
10159 }
10160
10161 pub fn next_snippet_tabstop(
10162 &mut self,
10163 _: &NextSnippetTabstop,
10164 window: &mut Window,
10165 cx: &mut Context<Self>,
10166 ) {
10167 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10168 cx.propagate();
10169 return;
10170 }
10171
10172 if self.move_to_next_snippet_tabstop(window, cx) {
10173 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10174 return;
10175 }
10176 cx.propagate();
10177 }
10178
10179 pub fn previous_snippet_tabstop(
10180 &mut self,
10181 _: &PreviousSnippetTabstop,
10182 window: &mut Window,
10183 cx: &mut Context<Self>,
10184 ) {
10185 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10186 cx.propagate();
10187 return;
10188 }
10189
10190 if self.move_to_prev_snippet_tabstop(window, cx) {
10191 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10192 return;
10193 }
10194 cx.propagate();
10195 }
10196
10197 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10198 if self.mode.is_single_line() {
10199 cx.propagate();
10200 return;
10201 }
10202
10203 if self.move_to_next_snippet_tabstop(window, cx) {
10204 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10205 return;
10206 }
10207 if self.read_only(cx) {
10208 return;
10209 }
10210 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10211 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10212 let buffer = self.buffer.read(cx);
10213 let snapshot = buffer.snapshot(cx);
10214 let rows_iter = selections.iter().map(|s| s.head().row);
10215 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10216
10217 let has_some_cursor_in_whitespace = selections
10218 .iter()
10219 .filter(|selection| selection.is_empty())
10220 .any(|selection| {
10221 let cursor = selection.head();
10222 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10223 cursor.column < current_indent.len
10224 });
10225
10226 let mut edits = Vec::new();
10227 let mut prev_edited_row = 0;
10228 let mut row_delta = 0;
10229 for selection in &mut selections {
10230 if selection.start.row != prev_edited_row {
10231 row_delta = 0;
10232 }
10233 prev_edited_row = selection.end.row;
10234
10235 // If the selection is non-empty, then increase the indentation of the selected lines.
10236 if !selection.is_empty() {
10237 row_delta =
10238 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10239 continue;
10240 }
10241
10242 let cursor = selection.head();
10243 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10244 if let Some(suggested_indent) =
10245 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10246 {
10247 // Don't do anything if already at suggested indent
10248 // and there is any other cursor which is not
10249 if has_some_cursor_in_whitespace
10250 && cursor.column == current_indent.len
10251 && current_indent.len == suggested_indent.len
10252 {
10253 continue;
10254 }
10255
10256 // Adjust line and move cursor to suggested indent
10257 // if cursor is not at suggested indent
10258 if cursor.column < suggested_indent.len
10259 && cursor.column <= current_indent.len
10260 && current_indent.len <= suggested_indent.len
10261 {
10262 selection.start = Point::new(cursor.row, suggested_indent.len);
10263 selection.end = selection.start;
10264 if row_delta == 0 {
10265 edits.extend(Buffer::edit_for_indent_size_adjustment(
10266 cursor.row,
10267 current_indent,
10268 suggested_indent,
10269 ));
10270 row_delta = suggested_indent.len - current_indent.len;
10271 }
10272 continue;
10273 }
10274
10275 // If current indent is more than suggested indent
10276 // only move cursor to current indent and skip indent
10277 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10278 selection.start = Point::new(cursor.row, current_indent.len);
10279 selection.end = selection.start;
10280 continue;
10281 }
10282 }
10283
10284 // Otherwise, insert a hard or soft tab.
10285 let settings = buffer.language_settings_at(cursor, cx);
10286 let tab_size = if settings.hard_tabs {
10287 IndentSize::tab()
10288 } else {
10289 let tab_size = settings.tab_size.get();
10290 let indent_remainder = snapshot
10291 .text_for_range(Point::new(cursor.row, 0)..cursor)
10292 .flat_map(str::chars)
10293 .fold(row_delta % tab_size, |counter: u32, c| {
10294 if c == '\t' {
10295 0
10296 } else {
10297 (counter + 1) % tab_size
10298 }
10299 });
10300
10301 let chars_to_next_tab_stop = tab_size - indent_remainder;
10302 IndentSize::spaces(chars_to_next_tab_stop)
10303 };
10304 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10305 selection.end = selection.start;
10306 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10307 row_delta += tab_size.len;
10308 }
10309
10310 self.transact(window, cx, |this, window, cx| {
10311 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10312 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10313 this.refresh_edit_prediction(true, false, window, cx);
10314 });
10315 }
10316
10317 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10318 if self.read_only(cx) {
10319 return;
10320 }
10321 if self.mode.is_single_line() {
10322 cx.propagate();
10323 return;
10324 }
10325
10326 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10327 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10328 let mut prev_edited_row = 0;
10329 let mut row_delta = 0;
10330 let mut edits = Vec::new();
10331 let buffer = self.buffer.read(cx);
10332 let snapshot = buffer.snapshot(cx);
10333 for selection in &mut selections {
10334 if selection.start.row != prev_edited_row {
10335 row_delta = 0;
10336 }
10337 prev_edited_row = selection.end.row;
10338
10339 row_delta =
10340 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10341 }
10342
10343 self.transact(window, cx, |this, window, cx| {
10344 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10345 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10346 });
10347 }
10348
10349 fn indent_selection(
10350 buffer: &MultiBuffer,
10351 snapshot: &MultiBufferSnapshot,
10352 selection: &mut Selection<Point>,
10353 edits: &mut Vec<(Range<Point>, String)>,
10354 delta_for_start_row: u32,
10355 cx: &App,
10356 ) -> u32 {
10357 let settings = buffer.language_settings_at(selection.start, cx);
10358 let tab_size = settings.tab_size.get();
10359 let indent_kind = if settings.hard_tabs {
10360 IndentKind::Tab
10361 } else {
10362 IndentKind::Space
10363 };
10364 let mut start_row = selection.start.row;
10365 let mut end_row = selection.end.row + 1;
10366
10367 // If a selection ends at the beginning of a line, don't indent
10368 // that last line.
10369 if selection.end.column == 0 && selection.end.row > selection.start.row {
10370 end_row -= 1;
10371 }
10372
10373 // Avoid re-indenting a row that has already been indented by a
10374 // previous selection, but still update this selection's column
10375 // to reflect that indentation.
10376 if delta_for_start_row > 0 {
10377 start_row += 1;
10378 selection.start.column += delta_for_start_row;
10379 if selection.end.row == selection.start.row {
10380 selection.end.column += delta_for_start_row;
10381 }
10382 }
10383
10384 let mut delta_for_end_row = 0;
10385 let has_multiple_rows = start_row + 1 != end_row;
10386 for row in start_row..end_row {
10387 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10388 let indent_delta = match (current_indent.kind, indent_kind) {
10389 (IndentKind::Space, IndentKind::Space) => {
10390 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10391 IndentSize::spaces(columns_to_next_tab_stop)
10392 }
10393 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10394 (_, IndentKind::Tab) => IndentSize::tab(),
10395 };
10396
10397 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10398 0
10399 } else {
10400 selection.start.column
10401 };
10402 let row_start = Point::new(row, start);
10403 edits.push((
10404 row_start..row_start,
10405 indent_delta.chars().collect::<String>(),
10406 ));
10407
10408 // Update this selection's endpoints to reflect the indentation.
10409 if row == selection.start.row {
10410 selection.start.column += indent_delta.len;
10411 }
10412 if row == selection.end.row {
10413 selection.end.column += indent_delta.len;
10414 delta_for_end_row = indent_delta.len;
10415 }
10416 }
10417
10418 if selection.start.row == selection.end.row {
10419 delta_for_start_row + delta_for_end_row
10420 } else {
10421 delta_for_end_row
10422 }
10423 }
10424
10425 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10426 if self.read_only(cx) {
10427 return;
10428 }
10429 if self.mode.is_single_line() {
10430 cx.propagate();
10431 return;
10432 }
10433
10434 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10435 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10436 let selections = self.selections.all::<Point>(&display_map);
10437 let mut deletion_ranges = Vec::new();
10438 let mut last_outdent = None;
10439 {
10440 let buffer = self.buffer.read(cx);
10441 let snapshot = buffer.snapshot(cx);
10442 for selection in &selections {
10443 let settings = buffer.language_settings_at(selection.start, cx);
10444 let tab_size = settings.tab_size.get();
10445 let mut rows = selection.spanned_rows(false, &display_map);
10446
10447 // Avoid re-outdenting a row that has already been outdented by a
10448 // previous selection.
10449 if let Some(last_row) = last_outdent
10450 && last_row == rows.start
10451 {
10452 rows.start = rows.start.next_row();
10453 }
10454 let has_multiple_rows = rows.len() > 1;
10455 for row in rows.iter_rows() {
10456 let indent_size = snapshot.indent_size_for_line(row);
10457 if indent_size.len > 0 {
10458 let deletion_len = match indent_size.kind {
10459 IndentKind::Space => {
10460 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10461 if columns_to_prev_tab_stop == 0 {
10462 tab_size
10463 } else {
10464 columns_to_prev_tab_stop
10465 }
10466 }
10467 IndentKind::Tab => 1,
10468 };
10469 let start = if has_multiple_rows
10470 || deletion_len > selection.start.column
10471 || indent_size.len < selection.start.column
10472 {
10473 0
10474 } else {
10475 selection.start.column - deletion_len
10476 };
10477 deletion_ranges.push(
10478 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10479 );
10480 last_outdent = Some(row);
10481 }
10482 }
10483 }
10484 }
10485
10486 self.transact(window, cx, |this, window, cx| {
10487 this.buffer.update(cx, |buffer, cx| {
10488 let empty_str: Arc<str> = Arc::default();
10489 buffer.edit(
10490 deletion_ranges
10491 .into_iter()
10492 .map(|range| (range, empty_str.clone())),
10493 None,
10494 cx,
10495 );
10496 });
10497 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10498 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10499 });
10500 }
10501
10502 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10503 if self.read_only(cx) {
10504 return;
10505 }
10506 if self.mode.is_single_line() {
10507 cx.propagate();
10508 return;
10509 }
10510
10511 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10512 let selections = self
10513 .selections
10514 .all::<usize>(&self.display_snapshot(cx))
10515 .into_iter()
10516 .map(|s| s.range());
10517
10518 self.transact(window, cx, |this, window, cx| {
10519 this.buffer.update(cx, |buffer, cx| {
10520 buffer.autoindent_ranges(selections, cx);
10521 });
10522 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10523 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10524 });
10525 }
10526
10527 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10528 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10529 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10530 let selections = self.selections.all::<Point>(&display_map);
10531
10532 let mut new_cursors = Vec::new();
10533 let mut edit_ranges = Vec::new();
10534 let mut selections = selections.iter().peekable();
10535 while let Some(selection) = selections.next() {
10536 let mut rows = selection.spanned_rows(false, &display_map);
10537
10538 // Accumulate contiguous regions of rows that we want to delete.
10539 while let Some(next_selection) = selections.peek() {
10540 let next_rows = next_selection.spanned_rows(false, &display_map);
10541 if next_rows.start <= rows.end {
10542 rows.end = next_rows.end;
10543 selections.next().unwrap();
10544 } else {
10545 break;
10546 }
10547 }
10548
10549 let buffer = display_map.buffer_snapshot();
10550 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10551 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10552 // If there's a line after the range, delete the \n from the end of the row range
10553 (
10554 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10555 rows.end,
10556 )
10557 } else {
10558 // If there isn't a line after the range, delete the \n from the line before the
10559 // start of the row range
10560 edit_start = edit_start.saturating_sub(1);
10561 (buffer.len(), rows.start.previous_row())
10562 };
10563
10564 let text_layout_details = self.text_layout_details(window);
10565 let x = display_map.x_for_display_point(
10566 selection.head().to_display_point(&display_map),
10567 &text_layout_details,
10568 );
10569 let row = Point::new(target_row.0, 0)
10570 .to_display_point(&display_map)
10571 .row();
10572 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10573
10574 new_cursors.push((
10575 selection.id,
10576 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10577 SelectionGoal::None,
10578 ));
10579 edit_ranges.push(edit_start..edit_end);
10580 }
10581
10582 self.transact(window, cx, |this, window, cx| {
10583 let buffer = this.buffer.update(cx, |buffer, cx| {
10584 let empty_str: Arc<str> = Arc::default();
10585 buffer.edit(
10586 edit_ranges
10587 .into_iter()
10588 .map(|range| (range, empty_str.clone())),
10589 None,
10590 cx,
10591 );
10592 buffer.snapshot(cx)
10593 });
10594 let new_selections = new_cursors
10595 .into_iter()
10596 .map(|(id, cursor, goal)| {
10597 let cursor = cursor.to_point(&buffer);
10598 Selection {
10599 id,
10600 start: cursor,
10601 end: cursor,
10602 reversed: false,
10603 goal,
10604 }
10605 })
10606 .collect();
10607
10608 this.change_selections(Default::default(), window, cx, |s| {
10609 s.select(new_selections);
10610 });
10611 });
10612 }
10613
10614 pub fn join_lines_impl(
10615 &mut self,
10616 insert_whitespace: bool,
10617 window: &mut Window,
10618 cx: &mut Context<Self>,
10619 ) {
10620 if self.read_only(cx) {
10621 return;
10622 }
10623 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10624 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10625 let start = MultiBufferRow(selection.start.row);
10626 // Treat single line selections as if they include the next line. Otherwise this action
10627 // would do nothing for single line selections individual cursors.
10628 let end = if selection.start.row == selection.end.row {
10629 MultiBufferRow(selection.start.row + 1)
10630 } else {
10631 MultiBufferRow(selection.end.row)
10632 };
10633
10634 if let Some(last_row_range) = row_ranges.last_mut()
10635 && start <= last_row_range.end
10636 {
10637 last_row_range.end = end;
10638 continue;
10639 }
10640 row_ranges.push(start..end);
10641 }
10642
10643 let snapshot = self.buffer.read(cx).snapshot(cx);
10644 let mut cursor_positions = Vec::new();
10645 for row_range in &row_ranges {
10646 let anchor = snapshot.anchor_before(Point::new(
10647 row_range.end.previous_row().0,
10648 snapshot.line_len(row_range.end.previous_row()),
10649 ));
10650 cursor_positions.push(anchor..anchor);
10651 }
10652
10653 self.transact(window, cx, |this, window, cx| {
10654 for row_range in row_ranges.into_iter().rev() {
10655 for row in row_range.iter_rows().rev() {
10656 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10657 let next_line_row = row.next_row();
10658 let indent = snapshot.indent_size_for_line(next_line_row);
10659 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10660
10661 let replace =
10662 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10663 " "
10664 } else {
10665 ""
10666 };
10667
10668 this.buffer.update(cx, |buffer, cx| {
10669 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10670 });
10671 }
10672 }
10673
10674 this.change_selections(Default::default(), window, cx, |s| {
10675 s.select_anchor_ranges(cursor_positions)
10676 });
10677 });
10678 }
10679
10680 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10681 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10682 self.join_lines_impl(true, window, cx);
10683 }
10684
10685 pub fn sort_lines_case_sensitive(
10686 &mut self,
10687 _: &SortLinesCaseSensitive,
10688 window: &mut Window,
10689 cx: &mut Context<Self>,
10690 ) {
10691 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10692 }
10693
10694 pub fn sort_lines_by_length(
10695 &mut self,
10696 _: &SortLinesByLength,
10697 window: &mut Window,
10698 cx: &mut Context<Self>,
10699 ) {
10700 self.manipulate_immutable_lines(window, cx, |lines| {
10701 lines.sort_by_key(|&line| line.chars().count())
10702 })
10703 }
10704
10705 pub fn sort_lines_case_insensitive(
10706 &mut self,
10707 _: &SortLinesCaseInsensitive,
10708 window: &mut Window,
10709 cx: &mut Context<Self>,
10710 ) {
10711 self.manipulate_immutable_lines(window, cx, |lines| {
10712 lines.sort_by_key(|line| line.to_lowercase())
10713 })
10714 }
10715
10716 pub fn unique_lines_case_insensitive(
10717 &mut self,
10718 _: &UniqueLinesCaseInsensitive,
10719 window: &mut Window,
10720 cx: &mut Context<Self>,
10721 ) {
10722 self.manipulate_immutable_lines(window, cx, |lines| {
10723 let mut seen = HashSet::default();
10724 lines.retain(|line| seen.insert(line.to_lowercase()));
10725 })
10726 }
10727
10728 pub fn unique_lines_case_sensitive(
10729 &mut self,
10730 _: &UniqueLinesCaseSensitive,
10731 window: &mut Window,
10732 cx: &mut Context<Self>,
10733 ) {
10734 self.manipulate_immutable_lines(window, cx, |lines| {
10735 let mut seen = HashSet::default();
10736 lines.retain(|line| seen.insert(*line));
10737 })
10738 }
10739
10740 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10741 let snapshot = self.buffer.read(cx).snapshot(cx);
10742 for selection in self.selections.disjoint_anchors_arc().iter() {
10743 if snapshot
10744 .language_at(selection.start)
10745 .and_then(|lang| lang.config().wrap_characters.as_ref())
10746 .is_some()
10747 {
10748 return true;
10749 }
10750 }
10751 false
10752 }
10753
10754 fn wrap_selections_in_tag(
10755 &mut self,
10756 _: &WrapSelectionsInTag,
10757 window: &mut Window,
10758 cx: &mut Context<Self>,
10759 ) {
10760 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10761
10762 let snapshot = self.buffer.read(cx).snapshot(cx);
10763
10764 let mut edits = Vec::new();
10765 let mut boundaries = Vec::new();
10766
10767 for selection in self
10768 .selections
10769 .all_adjusted(&self.display_snapshot(cx))
10770 .iter()
10771 {
10772 let Some(wrap_config) = snapshot
10773 .language_at(selection.start)
10774 .and_then(|lang| lang.config().wrap_characters.clone())
10775 else {
10776 continue;
10777 };
10778
10779 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10780 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10781
10782 let start_before = snapshot.anchor_before(selection.start);
10783 let end_after = snapshot.anchor_after(selection.end);
10784
10785 edits.push((start_before..start_before, open_tag));
10786 edits.push((end_after..end_after, close_tag));
10787
10788 boundaries.push((
10789 start_before,
10790 end_after,
10791 wrap_config.start_prefix.len(),
10792 wrap_config.end_suffix.len(),
10793 ));
10794 }
10795
10796 if edits.is_empty() {
10797 return;
10798 }
10799
10800 self.transact(window, cx, |this, window, cx| {
10801 let buffer = this.buffer.update(cx, |buffer, cx| {
10802 buffer.edit(edits, None, cx);
10803 buffer.snapshot(cx)
10804 });
10805
10806 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10807 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10808 boundaries.into_iter()
10809 {
10810 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10811 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10812 new_selections.push(open_offset..open_offset);
10813 new_selections.push(close_offset..close_offset);
10814 }
10815
10816 this.change_selections(Default::default(), window, cx, |s| {
10817 s.select_ranges(new_selections);
10818 });
10819
10820 this.request_autoscroll(Autoscroll::fit(), cx);
10821 });
10822 }
10823
10824 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10825 let Some(project) = self.project.clone() else {
10826 return;
10827 };
10828 self.reload(project, window, cx)
10829 .detach_and_notify_err(window, cx);
10830 }
10831
10832 pub fn restore_file(
10833 &mut self,
10834 _: &::git::RestoreFile,
10835 window: &mut Window,
10836 cx: &mut Context<Self>,
10837 ) {
10838 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10839 let mut buffer_ids = HashSet::default();
10840 let snapshot = self.buffer().read(cx).snapshot(cx);
10841 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10842 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10843 }
10844
10845 let buffer = self.buffer().read(cx);
10846 let ranges = buffer_ids
10847 .into_iter()
10848 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10849 .collect::<Vec<_>>();
10850
10851 self.restore_hunks_in_ranges(ranges, window, cx);
10852 }
10853
10854 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10855 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10856 let selections = self
10857 .selections
10858 .all(&self.display_snapshot(cx))
10859 .into_iter()
10860 .map(|s| s.range())
10861 .collect();
10862 self.restore_hunks_in_ranges(selections, window, cx);
10863 }
10864
10865 pub fn restore_hunks_in_ranges(
10866 &mut self,
10867 ranges: Vec<Range<Point>>,
10868 window: &mut Window,
10869 cx: &mut Context<Editor>,
10870 ) {
10871 let mut revert_changes = HashMap::default();
10872 let chunk_by = self
10873 .snapshot(window, cx)
10874 .hunks_for_ranges(ranges)
10875 .into_iter()
10876 .chunk_by(|hunk| hunk.buffer_id);
10877 for (buffer_id, hunks) in &chunk_by {
10878 let hunks = hunks.collect::<Vec<_>>();
10879 for hunk in &hunks {
10880 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10881 }
10882 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10883 }
10884 drop(chunk_by);
10885 if !revert_changes.is_empty() {
10886 self.transact(window, cx, |editor, window, cx| {
10887 editor.restore(revert_changes, window, cx);
10888 });
10889 }
10890 }
10891
10892 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10893 if let Some(status) = self
10894 .addons
10895 .iter()
10896 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10897 {
10898 return Some(status);
10899 }
10900 self.project
10901 .as_ref()?
10902 .read(cx)
10903 .status_for_buffer_id(buffer_id, cx)
10904 }
10905
10906 pub fn open_active_item_in_terminal(
10907 &mut self,
10908 _: &OpenInTerminal,
10909 window: &mut Window,
10910 cx: &mut Context<Self>,
10911 ) {
10912 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10913 let project_path = buffer.read(cx).project_path(cx)?;
10914 let project = self.project()?.read(cx);
10915 let entry = project.entry_for_path(&project_path, cx)?;
10916 let parent = match &entry.canonical_path {
10917 Some(canonical_path) => canonical_path.to_path_buf(),
10918 None => project.absolute_path(&project_path, cx)?,
10919 }
10920 .parent()?
10921 .to_path_buf();
10922 Some(parent)
10923 }) {
10924 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10925 }
10926 }
10927
10928 fn set_breakpoint_context_menu(
10929 &mut self,
10930 display_row: DisplayRow,
10931 position: Option<Anchor>,
10932 clicked_point: gpui::Point<Pixels>,
10933 window: &mut Window,
10934 cx: &mut Context<Self>,
10935 ) {
10936 let source = self
10937 .buffer
10938 .read(cx)
10939 .snapshot(cx)
10940 .anchor_before(Point::new(display_row.0, 0u32));
10941
10942 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10943
10944 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10945 self,
10946 source,
10947 clicked_point,
10948 context_menu,
10949 window,
10950 cx,
10951 );
10952 }
10953
10954 fn add_edit_breakpoint_block(
10955 &mut self,
10956 anchor: Anchor,
10957 breakpoint: &Breakpoint,
10958 edit_action: BreakpointPromptEditAction,
10959 window: &mut Window,
10960 cx: &mut Context<Self>,
10961 ) {
10962 let weak_editor = cx.weak_entity();
10963 let bp_prompt = cx.new(|cx| {
10964 BreakpointPromptEditor::new(
10965 weak_editor,
10966 anchor,
10967 breakpoint.clone(),
10968 edit_action,
10969 window,
10970 cx,
10971 )
10972 });
10973
10974 let height = bp_prompt.update(cx, |this, cx| {
10975 this.prompt
10976 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10977 });
10978 let cloned_prompt = bp_prompt.clone();
10979 let blocks = vec![BlockProperties {
10980 style: BlockStyle::Sticky,
10981 placement: BlockPlacement::Above(anchor),
10982 height: Some(height),
10983 render: Arc::new(move |cx| {
10984 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10985 cloned_prompt.clone().into_any_element()
10986 }),
10987 priority: 0,
10988 }];
10989
10990 let focus_handle = bp_prompt.focus_handle(cx);
10991 window.focus(&focus_handle);
10992
10993 let block_ids = self.insert_blocks(blocks, None, cx);
10994 bp_prompt.update(cx, |prompt, _| {
10995 prompt.add_block_ids(block_ids);
10996 });
10997 }
10998
10999 pub(crate) fn breakpoint_at_row(
11000 &self,
11001 row: u32,
11002 window: &mut Window,
11003 cx: &mut Context<Self>,
11004 ) -> Option<(Anchor, Breakpoint)> {
11005 let snapshot = self.snapshot(window, cx);
11006 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11007
11008 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11009 }
11010
11011 pub(crate) fn breakpoint_at_anchor(
11012 &self,
11013 breakpoint_position: Anchor,
11014 snapshot: &EditorSnapshot,
11015 cx: &mut Context<Self>,
11016 ) -> Option<(Anchor, Breakpoint)> {
11017 let buffer = self
11018 .buffer
11019 .read(cx)
11020 .buffer_for_anchor(breakpoint_position, cx)?;
11021
11022 let enclosing_excerpt = breakpoint_position.excerpt_id;
11023 let buffer_snapshot = buffer.read(cx).snapshot();
11024
11025 let row = buffer_snapshot
11026 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11027 .row;
11028
11029 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11030 let anchor_end = snapshot
11031 .buffer_snapshot()
11032 .anchor_after(Point::new(row, line_len));
11033
11034 self.breakpoint_store
11035 .as_ref()?
11036 .read_with(cx, |breakpoint_store, cx| {
11037 breakpoint_store
11038 .breakpoints(
11039 &buffer,
11040 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11041 &buffer_snapshot,
11042 cx,
11043 )
11044 .next()
11045 .and_then(|(bp, _)| {
11046 let breakpoint_row = buffer_snapshot
11047 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11048 .row;
11049
11050 if breakpoint_row == row {
11051 snapshot
11052 .buffer_snapshot()
11053 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11054 .map(|position| (position, bp.bp.clone()))
11055 } else {
11056 None
11057 }
11058 })
11059 })
11060 }
11061
11062 pub fn edit_log_breakpoint(
11063 &mut self,
11064 _: &EditLogBreakpoint,
11065 window: &mut Window,
11066 cx: &mut Context<Self>,
11067 ) {
11068 if self.breakpoint_store.is_none() {
11069 return;
11070 }
11071
11072 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11073 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11074 message: None,
11075 state: BreakpointState::Enabled,
11076 condition: None,
11077 hit_condition: None,
11078 });
11079
11080 self.add_edit_breakpoint_block(
11081 anchor,
11082 &breakpoint,
11083 BreakpointPromptEditAction::Log,
11084 window,
11085 cx,
11086 );
11087 }
11088 }
11089
11090 fn breakpoints_at_cursors(
11091 &self,
11092 window: &mut Window,
11093 cx: &mut Context<Self>,
11094 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11095 let snapshot = self.snapshot(window, cx);
11096 let cursors = self
11097 .selections
11098 .disjoint_anchors_arc()
11099 .iter()
11100 .map(|selection| {
11101 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11102
11103 let breakpoint_position = self
11104 .breakpoint_at_row(cursor_position.row, window, cx)
11105 .map(|bp| bp.0)
11106 .unwrap_or_else(|| {
11107 snapshot
11108 .display_snapshot
11109 .buffer_snapshot()
11110 .anchor_after(Point::new(cursor_position.row, 0))
11111 });
11112
11113 let breakpoint = self
11114 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11115 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11116
11117 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11118 })
11119 // 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.
11120 .collect::<HashMap<Anchor, _>>();
11121
11122 cursors.into_iter().collect()
11123 }
11124
11125 pub fn enable_breakpoint(
11126 &mut self,
11127 _: &crate::actions::EnableBreakpoint,
11128 window: &mut Window,
11129 cx: &mut Context<Self>,
11130 ) {
11131 if self.breakpoint_store.is_none() {
11132 return;
11133 }
11134
11135 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11136 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11137 continue;
11138 };
11139 self.edit_breakpoint_at_anchor(
11140 anchor,
11141 breakpoint,
11142 BreakpointEditAction::InvertState,
11143 cx,
11144 );
11145 }
11146 }
11147
11148 pub fn disable_breakpoint(
11149 &mut self,
11150 _: &crate::actions::DisableBreakpoint,
11151 window: &mut Window,
11152 cx: &mut Context<Self>,
11153 ) {
11154 if self.breakpoint_store.is_none() {
11155 return;
11156 }
11157
11158 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11159 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11160 continue;
11161 };
11162 self.edit_breakpoint_at_anchor(
11163 anchor,
11164 breakpoint,
11165 BreakpointEditAction::InvertState,
11166 cx,
11167 );
11168 }
11169 }
11170
11171 pub fn toggle_breakpoint(
11172 &mut self,
11173 _: &crate::actions::ToggleBreakpoint,
11174 window: &mut Window,
11175 cx: &mut Context<Self>,
11176 ) {
11177 if self.breakpoint_store.is_none() {
11178 return;
11179 }
11180
11181 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11182 if let Some(breakpoint) = breakpoint {
11183 self.edit_breakpoint_at_anchor(
11184 anchor,
11185 breakpoint,
11186 BreakpointEditAction::Toggle,
11187 cx,
11188 );
11189 } else {
11190 self.edit_breakpoint_at_anchor(
11191 anchor,
11192 Breakpoint::new_standard(),
11193 BreakpointEditAction::Toggle,
11194 cx,
11195 );
11196 }
11197 }
11198 }
11199
11200 pub fn edit_breakpoint_at_anchor(
11201 &mut self,
11202 breakpoint_position: Anchor,
11203 breakpoint: Breakpoint,
11204 edit_action: BreakpointEditAction,
11205 cx: &mut Context<Self>,
11206 ) {
11207 let Some(breakpoint_store) = &self.breakpoint_store else {
11208 return;
11209 };
11210
11211 let Some(buffer) = self
11212 .buffer
11213 .read(cx)
11214 .buffer_for_anchor(breakpoint_position, cx)
11215 else {
11216 return;
11217 };
11218
11219 breakpoint_store.update(cx, |breakpoint_store, cx| {
11220 breakpoint_store.toggle_breakpoint(
11221 buffer,
11222 BreakpointWithPosition {
11223 position: breakpoint_position.text_anchor,
11224 bp: breakpoint,
11225 },
11226 edit_action,
11227 cx,
11228 );
11229 });
11230
11231 cx.notify();
11232 }
11233
11234 #[cfg(any(test, feature = "test-support"))]
11235 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11236 self.breakpoint_store.clone()
11237 }
11238
11239 pub fn prepare_restore_change(
11240 &self,
11241 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11242 hunk: &MultiBufferDiffHunk,
11243 cx: &mut App,
11244 ) -> Option<()> {
11245 if hunk.is_created_file() {
11246 return None;
11247 }
11248 let buffer = self.buffer.read(cx);
11249 let diff = buffer.diff_for(hunk.buffer_id)?;
11250 let buffer = buffer.buffer(hunk.buffer_id)?;
11251 let buffer = buffer.read(cx);
11252 let original_text = diff
11253 .read(cx)
11254 .base_text()
11255 .as_rope()
11256 .slice(hunk.diff_base_byte_range.clone());
11257 let buffer_snapshot = buffer.snapshot();
11258 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11259 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11260 probe
11261 .0
11262 .start
11263 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11264 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11265 }) {
11266 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11267 Some(())
11268 } else {
11269 None
11270 }
11271 }
11272
11273 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11274 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11275 }
11276
11277 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11278 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11279 }
11280
11281 fn manipulate_lines<M>(
11282 &mut self,
11283 window: &mut Window,
11284 cx: &mut Context<Self>,
11285 mut manipulate: M,
11286 ) where
11287 M: FnMut(&str) -> LineManipulationResult,
11288 {
11289 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11290
11291 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11292 let buffer = self.buffer.read(cx).snapshot(cx);
11293
11294 let mut edits = Vec::new();
11295
11296 let selections = self.selections.all::<Point>(&display_map);
11297 let mut selections = selections.iter().peekable();
11298 let mut contiguous_row_selections = Vec::new();
11299 let mut new_selections = Vec::new();
11300 let mut added_lines = 0;
11301 let mut removed_lines = 0;
11302
11303 while let Some(selection) = selections.next() {
11304 let (start_row, end_row) = consume_contiguous_rows(
11305 &mut contiguous_row_selections,
11306 selection,
11307 &display_map,
11308 &mut selections,
11309 );
11310
11311 let start_point = Point::new(start_row.0, 0);
11312 let end_point = Point::new(
11313 end_row.previous_row().0,
11314 buffer.line_len(end_row.previous_row()),
11315 );
11316 let text = buffer
11317 .text_for_range(start_point..end_point)
11318 .collect::<String>();
11319
11320 let LineManipulationResult {
11321 new_text,
11322 line_count_before,
11323 line_count_after,
11324 } = manipulate(&text);
11325
11326 edits.push((start_point..end_point, new_text));
11327
11328 // Selections must change based on added and removed line count
11329 let start_row =
11330 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11331 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11332 new_selections.push(Selection {
11333 id: selection.id,
11334 start: start_row,
11335 end: end_row,
11336 goal: SelectionGoal::None,
11337 reversed: selection.reversed,
11338 });
11339
11340 if line_count_after > line_count_before {
11341 added_lines += line_count_after - line_count_before;
11342 } else if line_count_before > line_count_after {
11343 removed_lines += line_count_before - line_count_after;
11344 }
11345 }
11346
11347 self.transact(window, cx, |this, window, cx| {
11348 let buffer = this.buffer.update(cx, |buffer, cx| {
11349 buffer.edit(edits, None, cx);
11350 buffer.snapshot(cx)
11351 });
11352
11353 // Recalculate offsets on newly edited buffer
11354 let new_selections = new_selections
11355 .iter()
11356 .map(|s| {
11357 let start_point = Point::new(s.start.0, 0);
11358 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11359 Selection {
11360 id: s.id,
11361 start: buffer.point_to_offset(start_point),
11362 end: buffer.point_to_offset(end_point),
11363 goal: s.goal,
11364 reversed: s.reversed,
11365 }
11366 })
11367 .collect();
11368
11369 this.change_selections(Default::default(), window, cx, |s| {
11370 s.select(new_selections);
11371 });
11372
11373 this.request_autoscroll(Autoscroll::fit(), cx);
11374 });
11375 }
11376
11377 fn manipulate_immutable_lines<Fn>(
11378 &mut self,
11379 window: &mut Window,
11380 cx: &mut Context<Self>,
11381 mut callback: Fn,
11382 ) where
11383 Fn: FnMut(&mut Vec<&str>),
11384 {
11385 self.manipulate_lines(window, cx, |text| {
11386 let mut lines: Vec<&str> = text.split('\n').collect();
11387 let line_count_before = lines.len();
11388
11389 callback(&mut lines);
11390
11391 LineManipulationResult {
11392 new_text: lines.join("\n"),
11393 line_count_before,
11394 line_count_after: lines.len(),
11395 }
11396 });
11397 }
11398
11399 fn manipulate_mutable_lines<Fn>(
11400 &mut self,
11401 window: &mut Window,
11402 cx: &mut Context<Self>,
11403 mut callback: Fn,
11404 ) where
11405 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11406 {
11407 self.manipulate_lines(window, cx, |text| {
11408 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11409 let line_count_before = lines.len();
11410
11411 callback(&mut lines);
11412
11413 LineManipulationResult {
11414 new_text: lines.join("\n"),
11415 line_count_before,
11416 line_count_after: lines.len(),
11417 }
11418 });
11419 }
11420
11421 pub fn convert_indentation_to_spaces(
11422 &mut self,
11423 _: &ConvertIndentationToSpaces,
11424 window: &mut Window,
11425 cx: &mut Context<Self>,
11426 ) {
11427 let settings = self.buffer.read(cx).language_settings(cx);
11428 let tab_size = settings.tab_size.get() as usize;
11429
11430 self.manipulate_mutable_lines(window, cx, |lines| {
11431 // Allocates a reasonably sized scratch buffer once for the whole loop
11432 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11433 // Avoids recomputing spaces that could be inserted many times
11434 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11435 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11436 .collect();
11437
11438 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11439 let mut chars = line.as_ref().chars();
11440 let mut col = 0;
11441 let mut changed = false;
11442
11443 for ch in chars.by_ref() {
11444 match ch {
11445 ' ' => {
11446 reindented_line.push(' ');
11447 col += 1;
11448 }
11449 '\t' => {
11450 // \t are converted to spaces depending on the current column
11451 let spaces_len = tab_size - (col % tab_size);
11452 reindented_line.extend(&space_cache[spaces_len - 1]);
11453 col += spaces_len;
11454 changed = true;
11455 }
11456 _ => {
11457 // If we dont append before break, the character is consumed
11458 reindented_line.push(ch);
11459 break;
11460 }
11461 }
11462 }
11463
11464 if !changed {
11465 reindented_line.clear();
11466 continue;
11467 }
11468 // Append the rest of the line and replace old reference with new one
11469 reindented_line.extend(chars);
11470 *line = Cow::Owned(reindented_line.clone());
11471 reindented_line.clear();
11472 }
11473 });
11474 }
11475
11476 pub fn convert_indentation_to_tabs(
11477 &mut self,
11478 _: &ConvertIndentationToTabs,
11479 window: &mut Window,
11480 cx: &mut Context<Self>,
11481 ) {
11482 let settings = self.buffer.read(cx).language_settings(cx);
11483 let tab_size = settings.tab_size.get() as usize;
11484
11485 self.manipulate_mutable_lines(window, cx, |lines| {
11486 // Allocates a reasonably sized buffer once for the whole loop
11487 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11488 // Avoids recomputing spaces that could be inserted many times
11489 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11490 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11491 .collect();
11492
11493 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11494 let mut chars = line.chars();
11495 let mut spaces_count = 0;
11496 let mut first_non_indent_char = None;
11497 let mut changed = false;
11498
11499 for ch in chars.by_ref() {
11500 match ch {
11501 ' ' => {
11502 // Keep track of spaces. Append \t when we reach tab_size
11503 spaces_count += 1;
11504 changed = true;
11505 if spaces_count == tab_size {
11506 reindented_line.push('\t');
11507 spaces_count = 0;
11508 }
11509 }
11510 '\t' => {
11511 reindented_line.push('\t');
11512 spaces_count = 0;
11513 }
11514 _ => {
11515 // Dont append it yet, we might have remaining spaces
11516 first_non_indent_char = Some(ch);
11517 break;
11518 }
11519 }
11520 }
11521
11522 if !changed {
11523 reindented_line.clear();
11524 continue;
11525 }
11526 // Remaining spaces that didn't make a full tab stop
11527 if spaces_count > 0 {
11528 reindented_line.extend(&space_cache[spaces_count - 1]);
11529 }
11530 // If we consume an extra character that was not indentation, add it back
11531 if let Some(extra_char) = first_non_indent_char {
11532 reindented_line.push(extra_char);
11533 }
11534 // Append the rest of the line and replace old reference with new one
11535 reindented_line.extend(chars);
11536 *line = Cow::Owned(reindented_line.clone());
11537 reindented_line.clear();
11538 }
11539 });
11540 }
11541
11542 pub fn convert_to_upper_case(
11543 &mut self,
11544 _: &ConvertToUpperCase,
11545 window: &mut Window,
11546 cx: &mut Context<Self>,
11547 ) {
11548 self.manipulate_text(window, cx, |text| text.to_uppercase())
11549 }
11550
11551 pub fn convert_to_lower_case(
11552 &mut self,
11553 _: &ConvertToLowerCase,
11554 window: &mut Window,
11555 cx: &mut Context<Self>,
11556 ) {
11557 self.manipulate_text(window, cx, |text| text.to_lowercase())
11558 }
11559
11560 pub fn convert_to_title_case(
11561 &mut self,
11562 _: &ConvertToTitleCase,
11563 window: &mut Window,
11564 cx: &mut Context<Self>,
11565 ) {
11566 self.manipulate_text(window, cx, |text| {
11567 text.split('\n')
11568 .map(|line| line.to_case(Case::Title))
11569 .join("\n")
11570 })
11571 }
11572
11573 pub fn convert_to_snake_case(
11574 &mut self,
11575 _: &ConvertToSnakeCase,
11576 window: &mut Window,
11577 cx: &mut Context<Self>,
11578 ) {
11579 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11580 }
11581
11582 pub fn convert_to_kebab_case(
11583 &mut self,
11584 _: &ConvertToKebabCase,
11585 window: &mut Window,
11586 cx: &mut Context<Self>,
11587 ) {
11588 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11589 }
11590
11591 pub fn convert_to_upper_camel_case(
11592 &mut self,
11593 _: &ConvertToUpperCamelCase,
11594 window: &mut Window,
11595 cx: &mut Context<Self>,
11596 ) {
11597 self.manipulate_text(window, cx, |text| {
11598 text.split('\n')
11599 .map(|line| line.to_case(Case::UpperCamel))
11600 .join("\n")
11601 })
11602 }
11603
11604 pub fn convert_to_lower_camel_case(
11605 &mut self,
11606 _: &ConvertToLowerCamelCase,
11607 window: &mut Window,
11608 cx: &mut Context<Self>,
11609 ) {
11610 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11611 }
11612
11613 pub fn convert_to_opposite_case(
11614 &mut self,
11615 _: &ConvertToOppositeCase,
11616 window: &mut Window,
11617 cx: &mut Context<Self>,
11618 ) {
11619 self.manipulate_text(window, cx, |text| {
11620 text.chars()
11621 .fold(String::with_capacity(text.len()), |mut t, c| {
11622 if c.is_uppercase() {
11623 t.extend(c.to_lowercase());
11624 } else {
11625 t.extend(c.to_uppercase());
11626 }
11627 t
11628 })
11629 })
11630 }
11631
11632 pub fn convert_to_sentence_case(
11633 &mut self,
11634 _: &ConvertToSentenceCase,
11635 window: &mut Window,
11636 cx: &mut Context<Self>,
11637 ) {
11638 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11639 }
11640
11641 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11642 self.manipulate_text(window, cx, |text| {
11643 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11644 if has_upper_case_characters {
11645 text.to_lowercase()
11646 } else {
11647 text.to_uppercase()
11648 }
11649 })
11650 }
11651
11652 pub fn convert_to_rot13(
11653 &mut self,
11654 _: &ConvertToRot13,
11655 window: &mut Window,
11656 cx: &mut Context<Self>,
11657 ) {
11658 self.manipulate_text(window, cx, |text| {
11659 text.chars()
11660 .map(|c| match c {
11661 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11662 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11663 _ => c,
11664 })
11665 .collect()
11666 })
11667 }
11668
11669 pub fn convert_to_rot47(
11670 &mut self,
11671 _: &ConvertToRot47,
11672 window: &mut Window,
11673 cx: &mut Context<Self>,
11674 ) {
11675 self.manipulate_text(window, cx, |text| {
11676 text.chars()
11677 .map(|c| {
11678 let code_point = c as u32;
11679 if code_point >= 33 && code_point <= 126 {
11680 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11681 }
11682 c
11683 })
11684 .collect()
11685 })
11686 }
11687
11688 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11689 where
11690 Fn: FnMut(&str) -> String,
11691 {
11692 let buffer = self.buffer.read(cx).snapshot(cx);
11693
11694 let mut new_selections = Vec::new();
11695 let mut edits = Vec::new();
11696 let mut selection_adjustment = 0i32;
11697
11698 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11699 let selection_is_empty = selection.is_empty();
11700
11701 let (start, end) = if selection_is_empty {
11702 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11703 (word_range.start, word_range.end)
11704 } else {
11705 (
11706 buffer.point_to_offset(selection.start),
11707 buffer.point_to_offset(selection.end),
11708 )
11709 };
11710
11711 let text = buffer.text_for_range(start..end).collect::<String>();
11712 let old_length = text.len() as i32;
11713 let text = callback(&text);
11714
11715 new_selections.push(Selection {
11716 start: (start as i32 - selection_adjustment) as usize,
11717 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11718 goal: SelectionGoal::None,
11719 id: selection.id,
11720 reversed: selection.reversed,
11721 });
11722
11723 selection_adjustment += old_length - text.len() as i32;
11724
11725 edits.push((start..end, text));
11726 }
11727
11728 self.transact(window, cx, |this, window, cx| {
11729 this.buffer.update(cx, |buffer, cx| {
11730 buffer.edit(edits, None, cx);
11731 });
11732
11733 this.change_selections(Default::default(), window, cx, |s| {
11734 s.select(new_selections);
11735 });
11736
11737 this.request_autoscroll(Autoscroll::fit(), cx);
11738 });
11739 }
11740
11741 pub fn move_selection_on_drop(
11742 &mut self,
11743 selection: &Selection<Anchor>,
11744 target: DisplayPoint,
11745 is_cut: bool,
11746 window: &mut Window,
11747 cx: &mut Context<Self>,
11748 ) {
11749 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11750 let buffer = display_map.buffer_snapshot();
11751 let mut edits = Vec::new();
11752 let insert_point = display_map
11753 .clip_point(target, Bias::Left)
11754 .to_point(&display_map);
11755 let text = buffer
11756 .text_for_range(selection.start..selection.end)
11757 .collect::<String>();
11758 if is_cut {
11759 edits.push(((selection.start..selection.end), String::new()));
11760 }
11761 let insert_anchor = buffer.anchor_before(insert_point);
11762 edits.push(((insert_anchor..insert_anchor), text));
11763 let last_edit_start = insert_anchor.bias_left(buffer);
11764 let last_edit_end = insert_anchor.bias_right(buffer);
11765 self.transact(window, cx, |this, window, cx| {
11766 this.buffer.update(cx, |buffer, cx| {
11767 buffer.edit(edits, None, cx);
11768 });
11769 this.change_selections(Default::default(), window, cx, |s| {
11770 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11771 });
11772 });
11773 }
11774
11775 pub fn clear_selection_drag_state(&mut self) {
11776 self.selection_drag_state = SelectionDragState::None;
11777 }
11778
11779 pub fn duplicate(
11780 &mut self,
11781 upwards: bool,
11782 whole_lines: bool,
11783 window: &mut Window,
11784 cx: &mut Context<Self>,
11785 ) {
11786 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11787
11788 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11789 let buffer = display_map.buffer_snapshot();
11790 let selections = self.selections.all::<Point>(&display_map);
11791
11792 let mut edits = Vec::new();
11793 let mut selections_iter = selections.iter().peekable();
11794 while let Some(selection) = selections_iter.next() {
11795 let mut rows = selection.spanned_rows(false, &display_map);
11796 // duplicate line-wise
11797 if whole_lines || selection.start == selection.end {
11798 // Avoid duplicating the same lines twice.
11799 while let Some(next_selection) = selections_iter.peek() {
11800 let next_rows = next_selection.spanned_rows(false, &display_map);
11801 if next_rows.start < rows.end {
11802 rows.end = next_rows.end;
11803 selections_iter.next().unwrap();
11804 } else {
11805 break;
11806 }
11807 }
11808
11809 // Copy the text from the selected row region and splice it either at the start
11810 // or end of the region.
11811 let start = Point::new(rows.start.0, 0);
11812 let end = Point::new(
11813 rows.end.previous_row().0,
11814 buffer.line_len(rows.end.previous_row()),
11815 );
11816
11817 let mut text = buffer.text_for_range(start..end).collect::<String>();
11818
11819 let insert_location = if upwards {
11820 // When duplicating upward, we need to insert before the current line.
11821 // If we're on the last line and it doesn't end with a newline,
11822 // we need to add a newline before the duplicated content.
11823 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11824 && buffer.max_point().column > 0
11825 && !text.ends_with('\n');
11826
11827 if needs_leading_newline {
11828 text.insert(0, '\n');
11829 end
11830 } else {
11831 text.push('\n');
11832 Point::new(rows.start.0, 0)
11833 }
11834 } else {
11835 text.push('\n');
11836 start
11837 };
11838 edits.push((insert_location..insert_location, text));
11839 } else {
11840 // duplicate character-wise
11841 let start = selection.start;
11842 let end = selection.end;
11843 let text = buffer.text_for_range(start..end).collect::<String>();
11844 edits.push((selection.end..selection.end, text));
11845 }
11846 }
11847
11848 self.transact(window, cx, |this, window, cx| {
11849 this.buffer.update(cx, |buffer, cx| {
11850 buffer.edit(edits, None, cx);
11851 });
11852
11853 // When duplicating upward with whole lines, move the cursor to the duplicated line
11854 if upwards && whole_lines {
11855 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11856
11857 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11858 let mut new_ranges = Vec::new();
11859 let selections = s.all::<Point>(&display_map);
11860 let mut selections_iter = selections.iter().peekable();
11861
11862 while let Some(first_selection) = selections_iter.next() {
11863 // Group contiguous selections together to find the total row span
11864 let mut group_selections = vec![first_selection];
11865 let mut rows = first_selection.spanned_rows(false, &display_map);
11866
11867 while let Some(next_selection) = selections_iter.peek() {
11868 let next_rows = next_selection.spanned_rows(false, &display_map);
11869 if next_rows.start < rows.end {
11870 rows.end = next_rows.end;
11871 group_selections.push(selections_iter.next().unwrap());
11872 } else {
11873 break;
11874 }
11875 }
11876
11877 let row_count = rows.end.0 - rows.start.0;
11878
11879 // Move all selections in this group up by the total number of duplicated rows
11880 for selection in group_selections {
11881 let new_start = Point::new(
11882 selection.start.row.saturating_sub(row_count),
11883 selection.start.column,
11884 );
11885
11886 let new_end = Point::new(
11887 selection.end.row.saturating_sub(row_count),
11888 selection.end.column,
11889 );
11890
11891 new_ranges.push(new_start..new_end);
11892 }
11893 }
11894
11895 s.select_ranges(new_ranges);
11896 });
11897 }
11898
11899 this.request_autoscroll(Autoscroll::fit(), cx);
11900 });
11901 }
11902
11903 pub fn duplicate_line_up(
11904 &mut self,
11905 _: &DuplicateLineUp,
11906 window: &mut Window,
11907 cx: &mut Context<Self>,
11908 ) {
11909 self.duplicate(true, true, window, cx);
11910 }
11911
11912 pub fn duplicate_line_down(
11913 &mut self,
11914 _: &DuplicateLineDown,
11915 window: &mut Window,
11916 cx: &mut Context<Self>,
11917 ) {
11918 self.duplicate(false, true, window, cx);
11919 }
11920
11921 pub fn duplicate_selection(
11922 &mut self,
11923 _: &DuplicateSelection,
11924 window: &mut Window,
11925 cx: &mut Context<Self>,
11926 ) {
11927 self.duplicate(false, false, window, cx);
11928 }
11929
11930 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11931 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11932 if self.mode.is_single_line() {
11933 cx.propagate();
11934 return;
11935 }
11936
11937 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11938 let buffer = self.buffer.read(cx).snapshot(cx);
11939
11940 let mut edits = Vec::new();
11941 let mut unfold_ranges = Vec::new();
11942 let mut refold_creases = Vec::new();
11943
11944 let selections = self.selections.all::<Point>(&display_map);
11945 let mut selections = selections.iter().peekable();
11946 let mut contiguous_row_selections = Vec::new();
11947 let mut new_selections = Vec::new();
11948
11949 while let Some(selection) = selections.next() {
11950 // Find all the selections that span a contiguous row range
11951 let (start_row, end_row) = consume_contiguous_rows(
11952 &mut contiguous_row_selections,
11953 selection,
11954 &display_map,
11955 &mut selections,
11956 );
11957
11958 // Move the text spanned by the row range to be before the line preceding the row range
11959 if start_row.0 > 0 {
11960 let range_to_move = Point::new(
11961 start_row.previous_row().0,
11962 buffer.line_len(start_row.previous_row()),
11963 )
11964 ..Point::new(
11965 end_row.previous_row().0,
11966 buffer.line_len(end_row.previous_row()),
11967 );
11968 let insertion_point = display_map
11969 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11970 .0;
11971
11972 // Don't move lines across excerpts
11973 if buffer
11974 .excerpt_containing(insertion_point..range_to_move.end)
11975 .is_some()
11976 {
11977 let text = buffer
11978 .text_for_range(range_to_move.clone())
11979 .flat_map(|s| s.chars())
11980 .skip(1)
11981 .chain(['\n'])
11982 .collect::<String>();
11983
11984 edits.push((
11985 buffer.anchor_after(range_to_move.start)
11986 ..buffer.anchor_before(range_to_move.end),
11987 String::new(),
11988 ));
11989 let insertion_anchor = buffer.anchor_after(insertion_point);
11990 edits.push((insertion_anchor..insertion_anchor, text));
11991
11992 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11993
11994 // Move selections up
11995 new_selections.extend(contiguous_row_selections.drain(..).map(
11996 |mut selection| {
11997 selection.start.row -= row_delta;
11998 selection.end.row -= row_delta;
11999 selection
12000 },
12001 ));
12002
12003 // Move folds up
12004 unfold_ranges.push(range_to_move.clone());
12005 for fold in display_map.folds_in_range(
12006 buffer.anchor_before(range_to_move.start)
12007 ..buffer.anchor_after(range_to_move.end),
12008 ) {
12009 let mut start = fold.range.start.to_point(&buffer);
12010 let mut end = fold.range.end.to_point(&buffer);
12011 start.row -= row_delta;
12012 end.row -= row_delta;
12013 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12014 }
12015 }
12016 }
12017
12018 // If we didn't move line(s), preserve the existing selections
12019 new_selections.append(&mut contiguous_row_selections);
12020 }
12021
12022 self.transact(window, cx, |this, window, cx| {
12023 this.unfold_ranges(&unfold_ranges, true, true, cx);
12024 this.buffer.update(cx, |buffer, cx| {
12025 for (range, text) in edits {
12026 buffer.edit([(range, text)], None, cx);
12027 }
12028 });
12029 this.fold_creases(refold_creases, true, window, cx);
12030 this.change_selections(Default::default(), window, cx, |s| {
12031 s.select(new_selections);
12032 })
12033 });
12034 }
12035
12036 pub fn move_line_down(
12037 &mut self,
12038 _: &MoveLineDown,
12039 window: &mut Window,
12040 cx: &mut Context<Self>,
12041 ) {
12042 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12043 if self.mode.is_single_line() {
12044 cx.propagate();
12045 return;
12046 }
12047
12048 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12049 let buffer = self.buffer.read(cx).snapshot(cx);
12050
12051 let mut edits = Vec::new();
12052 let mut unfold_ranges = Vec::new();
12053 let mut refold_creases = Vec::new();
12054
12055 let selections = self.selections.all::<Point>(&display_map);
12056 let mut selections = selections.iter().peekable();
12057 let mut contiguous_row_selections = Vec::new();
12058 let mut new_selections = Vec::new();
12059
12060 while let Some(selection) = selections.next() {
12061 // Find all the selections that span a contiguous row range
12062 let (start_row, end_row) = consume_contiguous_rows(
12063 &mut contiguous_row_selections,
12064 selection,
12065 &display_map,
12066 &mut selections,
12067 );
12068
12069 // Move the text spanned by the row range to be after the last line of the row range
12070 if end_row.0 <= buffer.max_point().row {
12071 let range_to_move =
12072 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12073 let insertion_point = display_map
12074 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12075 .0;
12076
12077 // Don't move lines across excerpt boundaries
12078 if buffer
12079 .excerpt_containing(range_to_move.start..insertion_point)
12080 .is_some()
12081 {
12082 let mut text = String::from("\n");
12083 text.extend(buffer.text_for_range(range_to_move.clone()));
12084 text.pop(); // Drop trailing newline
12085 edits.push((
12086 buffer.anchor_after(range_to_move.start)
12087 ..buffer.anchor_before(range_to_move.end),
12088 String::new(),
12089 ));
12090 let insertion_anchor = buffer.anchor_after(insertion_point);
12091 edits.push((insertion_anchor..insertion_anchor, text));
12092
12093 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12094
12095 // Move selections down
12096 new_selections.extend(contiguous_row_selections.drain(..).map(
12097 |mut selection| {
12098 selection.start.row += row_delta;
12099 selection.end.row += row_delta;
12100 selection
12101 },
12102 ));
12103
12104 // Move folds down
12105 unfold_ranges.push(range_to_move.clone());
12106 for fold in display_map.folds_in_range(
12107 buffer.anchor_before(range_to_move.start)
12108 ..buffer.anchor_after(range_to_move.end),
12109 ) {
12110 let mut start = fold.range.start.to_point(&buffer);
12111 let mut end = fold.range.end.to_point(&buffer);
12112 start.row += row_delta;
12113 end.row += row_delta;
12114 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12115 }
12116 }
12117 }
12118
12119 // If we didn't move line(s), preserve the existing selections
12120 new_selections.append(&mut contiguous_row_selections);
12121 }
12122
12123 self.transact(window, cx, |this, window, cx| {
12124 this.unfold_ranges(&unfold_ranges, true, true, cx);
12125 this.buffer.update(cx, |buffer, cx| {
12126 for (range, text) in edits {
12127 buffer.edit([(range, text)], None, cx);
12128 }
12129 });
12130 this.fold_creases(refold_creases, true, window, cx);
12131 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12132 });
12133 }
12134
12135 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12136 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12137 let text_layout_details = &self.text_layout_details(window);
12138 self.transact(window, cx, |this, window, cx| {
12139 let edits = this.change_selections(Default::default(), window, cx, |s| {
12140 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12141 s.move_with(|display_map, selection| {
12142 if !selection.is_empty() {
12143 return;
12144 }
12145
12146 let mut head = selection.head();
12147 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12148 if head.column() == display_map.line_len(head.row()) {
12149 transpose_offset = display_map
12150 .buffer_snapshot()
12151 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12152 }
12153
12154 if transpose_offset == 0 {
12155 return;
12156 }
12157
12158 *head.column_mut() += 1;
12159 head = display_map.clip_point(head, Bias::Right);
12160 let goal = SelectionGoal::HorizontalPosition(
12161 display_map
12162 .x_for_display_point(head, text_layout_details)
12163 .into(),
12164 );
12165 selection.collapse_to(head, goal);
12166
12167 let transpose_start = display_map
12168 .buffer_snapshot()
12169 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12170 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12171 let transpose_end = display_map
12172 .buffer_snapshot()
12173 .clip_offset(transpose_offset + 1, Bias::Right);
12174 if let Some(ch) = display_map
12175 .buffer_snapshot()
12176 .chars_at(transpose_start)
12177 .next()
12178 {
12179 edits.push((transpose_start..transpose_offset, String::new()));
12180 edits.push((transpose_end..transpose_end, ch.to_string()));
12181 }
12182 }
12183 });
12184 edits
12185 });
12186 this.buffer
12187 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12188 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12189 this.change_selections(Default::default(), window, cx, |s| {
12190 s.select(selections);
12191 });
12192 });
12193 }
12194
12195 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12196 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12197 if self.mode.is_single_line() {
12198 cx.propagate();
12199 return;
12200 }
12201
12202 self.rewrap_impl(RewrapOptions::default(), cx)
12203 }
12204
12205 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12206 let buffer = self.buffer.read(cx).snapshot(cx);
12207 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12208
12209 #[derive(Clone, Debug, PartialEq)]
12210 enum CommentFormat {
12211 /// single line comment, with prefix for line
12212 Line(String),
12213 /// single line within a block comment, with prefix for line
12214 BlockLine(String),
12215 /// a single line of a block comment that includes the initial delimiter
12216 BlockCommentWithStart(BlockCommentConfig),
12217 /// a single line of a block comment that includes the ending delimiter
12218 BlockCommentWithEnd(BlockCommentConfig),
12219 }
12220
12221 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12222 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12223 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12224 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12225 .peekable();
12226
12227 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12228 row
12229 } else {
12230 return Vec::new();
12231 };
12232
12233 let language_settings = buffer.language_settings_at(selection.head(), cx);
12234 let language_scope = buffer.language_scope_at(selection.head());
12235
12236 let indent_and_prefix_for_row =
12237 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12238 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12239 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12240 &language_scope
12241 {
12242 let indent_end = Point::new(row, indent.len);
12243 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12244 let line_text_after_indent = buffer
12245 .text_for_range(indent_end..line_end)
12246 .collect::<String>();
12247
12248 let is_within_comment_override = buffer
12249 .language_scope_at(indent_end)
12250 .is_some_and(|scope| scope.override_name() == Some("comment"));
12251 let comment_delimiters = if is_within_comment_override {
12252 // we are within a comment syntax node, but we don't
12253 // yet know what kind of comment: block, doc or line
12254 match (
12255 language_scope.documentation_comment(),
12256 language_scope.block_comment(),
12257 ) {
12258 (Some(config), _) | (_, Some(config))
12259 if buffer.contains_str_at(indent_end, &config.start) =>
12260 {
12261 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12262 }
12263 (Some(config), _) | (_, Some(config))
12264 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12265 {
12266 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12267 }
12268 (Some(config), _) | (_, Some(config))
12269 if buffer.contains_str_at(indent_end, &config.prefix) =>
12270 {
12271 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12272 }
12273 (_, _) => language_scope
12274 .line_comment_prefixes()
12275 .iter()
12276 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12277 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12278 }
12279 } else {
12280 // we not in an overridden comment node, but we may
12281 // be within a non-overridden line comment node
12282 language_scope
12283 .line_comment_prefixes()
12284 .iter()
12285 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12286 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12287 };
12288
12289 let rewrap_prefix = language_scope
12290 .rewrap_prefixes()
12291 .iter()
12292 .find_map(|prefix_regex| {
12293 prefix_regex.find(&line_text_after_indent).map(|mat| {
12294 if mat.start() == 0 {
12295 Some(mat.as_str().to_string())
12296 } else {
12297 None
12298 }
12299 })
12300 })
12301 .flatten();
12302 (comment_delimiters, rewrap_prefix)
12303 } else {
12304 (None, None)
12305 };
12306 (indent, comment_prefix, rewrap_prefix)
12307 };
12308
12309 let mut ranges = Vec::new();
12310 let from_empty_selection = selection.is_empty();
12311
12312 let mut current_range_start = first_row;
12313 let mut prev_row = first_row;
12314 let (
12315 mut current_range_indent,
12316 mut current_range_comment_delimiters,
12317 mut current_range_rewrap_prefix,
12318 ) = indent_and_prefix_for_row(first_row);
12319
12320 for row in non_blank_rows_iter.skip(1) {
12321 let has_paragraph_break = row > prev_row + 1;
12322
12323 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12324 indent_and_prefix_for_row(row);
12325
12326 let has_indent_change = row_indent != current_range_indent;
12327 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12328
12329 let has_boundary_change = has_comment_change
12330 || row_rewrap_prefix.is_some()
12331 || (has_indent_change && current_range_comment_delimiters.is_some());
12332
12333 if has_paragraph_break || has_boundary_change {
12334 ranges.push((
12335 language_settings.clone(),
12336 Point::new(current_range_start, 0)
12337 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12338 current_range_indent,
12339 current_range_comment_delimiters.clone(),
12340 current_range_rewrap_prefix.clone(),
12341 from_empty_selection,
12342 ));
12343 current_range_start = row;
12344 current_range_indent = row_indent;
12345 current_range_comment_delimiters = row_comment_delimiters;
12346 current_range_rewrap_prefix = row_rewrap_prefix;
12347 }
12348 prev_row = row;
12349 }
12350
12351 ranges.push((
12352 language_settings.clone(),
12353 Point::new(current_range_start, 0)
12354 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12355 current_range_indent,
12356 current_range_comment_delimiters,
12357 current_range_rewrap_prefix,
12358 from_empty_selection,
12359 ));
12360
12361 ranges
12362 });
12363
12364 let mut edits = Vec::new();
12365 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12366
12367 for (
12368 language_settings,
12369 wrap_range,
12370 mut indent_size,
12371 comment_prefix,
12372 rewrap_prefix,
12373 from_empty_selection,
12374 ) in wrap_ranges
12375 {
12376 let mut start_row = wrap_range.start.row;
12377 let mut end_row = wrap_range.end.row;
12378
12379 // Skip selections that overlap with a range that has already been rewrapped.
12380 let selection_range = start_row..end_row;
12381 if rewrapped_row_ranges
12382 .iter()
12383 .any(|range| range.overlaps(&selection_range))
12384 {
12385 continue;
12386 }
12387
12388 let tab_size = language_settings.tab_size;
12389
12390 let (line_prefix, inside_comment) = match &comment_prefix {
12391 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12392 (Some(prefix.as_str()), true)
12393 }
12394 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12395 (Some(prefix.as_ref()), true)
12396 }
12397 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12398 start: _,
12399 end: _,
12400 prefix,
12401 tab_size,
12402 })) => {
12403 indent_size.len += tab_size;
12404 (Some(prefix.as_ref()), true)
12405 }
12406 None => (None, false),
12407 };
12408 let indent_prefix = indent_size.chars().collect::<String>();
12409 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12410
12411 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12412 RewrapBehavior::InComments => inside_comment,
12413 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12414 RewrapBehavior::Anywhere => true,
12415 };
12416
12417 let should_rewrap = options.override_language_settings
12418 || allow_rewrap_based_on_language
12419 || self.hard_wrap.is_some();
12420 if !should_rewrap {
12421 continue;
12422 }
12423
12424 if from_empty_selection {
12425 'expand_upwards: while start_row > 0 {
12426 let prev_row = start_row - 1;
12427 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12428 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12429 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12430 {
12431 start_row = prev_row;
12432 } else {
12433 break 'expand_upwards;
12434 }
12435 }
12436
12437 'expand_downwards: while end_row < buffer.max_point().row {
12438 let next_row = end_row + 1;
12439 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12440 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12441 && !buffer.is_line_blank(MultiBufferRow(next_row))
12442 {
12443 end_row = next_row;
12444 } else {
12445 break 'expand_downwards;
12446 }
12447 }
12448 }
12449
12450 let start = Point::new(start_row, 0);
12451 let start_offset = ToOffset::to_offset(&start, &buffer);
12452 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12453 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12454 let mut first_line_delimiter = None;
12455 let mut last_line_delimiter = None;
12456 let Some(lines_without_prefixes) = selection_text
12457 .lines()
12458 .enumerate()
12459 .map(|(ix, line)| {
12460 let line_trimmed = line.trim_start();
12461 if rewrap_prefix.is_some() && ix > 0 {
12462 Ok(line_trimmed)
12463 } else if let Some(
12464 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12465 start,
12466 prefix,
12467 end,
12468 tab_size,
12469 })
12470 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12471 start,
12472 prefix,
12473 end,
12474 tab_size,
12475 }),
12476 ) = &comment_prefix
12477 {
12478 let line_trimmed = line_trimmed
12479 .strip_prefix(start.as_ref())
12480 .map(|s| {
12481 let mut indent_size = indent_size;
12482 indent_size.len -= tab_size;
12483 let indent_prefix: String = indent_size.chars().collect();
12484 first_line_delimiter = Some((indent_prefix, start));
12485 s.trim_start()
12486 })
12487 .unwrap_or(line_trimmed);
12488 let line_trimmed = line_trimmed
12489 .strip_suffix(end.as_ref())
12490 .map(|s| {
12491 last_line_delimiter = Some(end);
12492 s.trim_end()
12493 })
12494 .unwrap_or(line_trimmed);
12495 let line_trimmed = line_trimmed
12496 .strip_prefix(prefix.as_ref())
12497 .unwrap_or(line_trimmed);
12498 Ok(line_trimmed)
12499 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12500 line_trimmed.strip_prefix(prefix).with_context(|| {
12501 format!("line did not start with prefix {prefix:?}: {line:?}")
12502 })
12503 } else {
12504 line_trimmed
12505 .strip_prefix(&line_prefix.trim_start())
12506 .with_context(|| {
12507 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12508 })
12509 }
12510 })
12511 .collect::<Result<Vec<_>, _>>()
12512 .log_err()
12513 else {
12514 continue;
12515 };
12516
12517 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12518 buffer
12519 .language_settings_at(Point::new(start_row, 0), cx)
12520 .preferred_line_length as usize
12521 });
12522
12523 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12524 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12525 } else {
12526 line_prefix.clone()
12527 };
12528
12529 let wrapped_text = {
12530 let mut wrapped_text = wrap_with_prefix(
12531 line_prefix,
12532 subsequent_lines_prefix,
12533 lines_without_prefixes.join("\n"),
12534 wrap_column,
12535 tab_size,
12536 options.preserve_existing_whitespace,
12537 );
12538
12539 if let Some((indent, delimiter)) = first_line_delimiter {
12540 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12541 }
12542 if let Some(last_line) = last_line_delimiter {
12543 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12544 }
12545
12546 wrapped_text
12547 };
12548
12549 // TODO: should always use char-based diff while still supporting cursor behavior that
12550 // matches vim.
12551 let mut diff_options = DiffOptions::default();
12552 if options.override_language_settings {
12553 diff_options.max_word_diff_len = 0;
12554 diff_options.max_word_diff_line_count = 0;
12555 } else {
12556 diff_options.max_word_diff_len = usize::MAX;
12557 diff_options.max_word_diff_line_count = usize::MAX;
12558 }
12559
12560 for (old_range, new_text) in
12561 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12562 {
12563 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12564 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12565 edits.push((edit_start..edit_end, new_text));
12566 }
12567
12568 rewrapped_row_ranges.push(start_row..=end_row);
12569 }
12570
12571 self.buffer
12572 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12573 }
12574
12575 pub fn cut_common(
12576 &mut self,
12577 cut_no_selection_line: bool,
12578 window: &mut Window,
12579 cx: &mut Context<Self>,
12580 ) -> ClipboardItem {
12581 let mut text = String::new();
12582 let buffer = self.buffer.read(cx).snapshot(cx);
12583 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12584 let mut clipboard_selections = Vec::with_capacity(selections.len());
12585 {
12586 let max_point = buffer.max_point();
12587 let mut is_first = true;
12588 let mut prev_selection_was_entire_line = false;
12589 for selection in &mut selections {
12590 let is_entire_line =
12591 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12592 if is_entire_line {
12593 selection.start = Point::new(selection.start.row, 0);
12594 if !selection.is_empty() && selection.end.column == 0 {
12595 selection.end = cmp::min(max_point, selection.end);
12596 } else {
12597 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12598 }
12599 selection.goal = SelectionGoal::None;
12600 }
12601 if is_first {
12602 is_first = false;
12603 } else if !prev_selection_was_entire_line {
12604 text += "\n";
12605 }
12606 prev_selection_was_entire_line = is_entire_line;
12607 let mut len = 0;
12608 for chunk in buffer.text_for_range(selection.start..selection.end) {
12609 text.push_str(chunk);
12610 len += chunk.len();
12611 }
12612 clipboard_selections.push(ClipboardSelection {
12613 len,
12614 is_entire_line,
12615 first_line_indent: buffer
12616 .indent_size_for_line(MultiBufferRow(selection.start.row))
12617 .len,
12618 });
12619 }
12620 }
12621
12622 self.transact(window, cx, |this, window, cx| {
12623 this.change_selections(Default::default(), window, cx, |s| {
12624 s.select(selections);
12625 });
12626 this.insert("", window, cx);
12627 });
12628 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12629 }
12630
12631 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12632 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12633 let item = self.cut_common(true, window, cx);
12634 cx.write_to_clipboard(item);
12635 }
12636
12637 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12638 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12639 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12640 s.move_with(|snapshot, sel| {
12641 if sel.is_empty() {
12642 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12643 }
12644 if sel.is_empty() {
12645 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12646 }
12647 });
12648 });
12649 let item = self.cut_common(false, window, cx);
12650 cx.set_global(KillRing(item))
12651 }
12652
12653 pub fn kill_ring_yank(
12654 &mut self,
12655 _: &KillRingYank,
12656 window: &mut Window,
12657 cx: &mut Context<Self>,
12658 ) {
12659 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12660 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12661 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12662 (kill_ring.text().to_string(), kill_ring.metadata_json())
12663 } else {
12664 return;
12665 }
12666 } else {
12667 return;
12668 };
12669 self.do_paste(&text, metadata, false, window, cx);
12670 }
12671
12672 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12673 self.do_copy(true, cx);
12674 }
12675
12676 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12677 self.do_copy(false, cx);
12678 }
12679
12680 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12681 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12682 let buffer = self.buffer.read(cx).read(cx);
12683 let mut text = String::new();
12684
12685 let mut clipboard_selections = Vec::with_capacity(selections.len());
12686 {
12687 let max_point = buffer.max_point();
12688 let mut is_first = true;
12689 let mut prev_selection_was_entire_line = false;
12690 for selection in &selections {
12691 let mut start = selection.start;
12692 let mut end = selection.end;
12693 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12694 let mut add_trailing_newline = false;
12695 if is_entire_line {
12696 start = Point::new(start.row, 0);
12697 let next_line_start = Point::new(end.row + 1, 0);
12698 if next_line_start <= max_point {
12699 end = next_line_start;
12700 } else {
12701 // We're on the last line without a trailing newline.
12702 // Copy to the end of the line and add a newline afterwards.
12703 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12704 add_trailing_newline = true;
12705 }
12706 }
12707
12708 let mut trimmed_selections = Vec::new();
12709 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12710 let row = MultiBufferRow(start.row);
12711 let first_indent = buffer.indent_size_for_line(row);
12712 if first_indent.len == 0 || start.column > first_indent.len {
12713 trimmed_selections.push(start..end);
12714 } else {
12715 trimmed_selections.push(
12716 Point::new(row.0, first_indent.len)
12717 ..Point::new(row.0, buffer.line_len(row)),
12718 );
12719 for row in start.row + 1..=end.row {
12720 let mut line_len = buffer.line_len(MultiBufferRow(row));
12721 if row == end.row {
12722 line_len = end.column;
12723 }
12724 if line_len == 0 {
12725 trimmed_selections
12726 .push(Point::new(row, 0)..Point::new(row, line_len));
12727 continue;
12728 }
12729 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12730 if row_indent_size.len >= first_indent.len {
12731 trimmed_selections.push(
12732 Point::new(row, first_indent.len)..Point::new(row, line_len),
12733 );
12734 } else {
12735 trimmed_selections.clear();
12736 trimmed_selections.push(start..end);
12737 break;
12738 }
12739 }
12740 }
12741 } else {
12742 trimmed_selections.push(start..end);
12743 }
12744
12745 for trimmed_range in trimmed_selections {
12746 if is_first {
12747 is_first = false;
12748 } else if !prev_selection_was_entire_line {
12749 text += "\n";
12750 }
12751 prev_selection_was_entire_line = is_entire_line;
12752 let mut len = 0;
12753 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12754 text.push_str(chunk);
12755 len += chunk.len();
12756 }
12757 if add_trailing_newline {
12758 text.push('\n');
12759 len += 1;
12760 }
12761 clipboard_selections.push(ClipboardSelection {
12762 len,
12763 is_entire_line,
12764 first_line_indent: buffer
12765 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12766 .len,
12767 });
12768 }
12769 }
12770 }
12771
12772 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12773 text,
12774 clipboard_selections,
12775 ));
12776 }
12777
12778 pub fn do_paste(
12779 &mut self,
12780 text: &String,
12781 clipboard_selections: Option<Vec<ClipboardSelection>>,
12782 handle_entire_lines: bool,
12783 window: &mut Window,
12784 cx: &mut Context<Self>,
12785 ) {
12786 if self.read_only(cx) {
12787 return;
12788 }
12789
12790 let clipboard_text = Cow::Borrowed(text.as_str());
12791
12792 self.transact(window, cx, |this, window, cx| {
12793 let had_active_edit_prediction = this.has_active_edit_prediction();
12794 let display_map = this.display_snapshot(cx);
12795 let old_selections = this.selections.all::<usize>(&display_map);
12796 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12797
12798 if let Some(mut clipboard_selections) = clipboard_selections {
12799 let all_selections_were_entire_line =
12800 clipboard_selections.iter().all(|s| s.is_entire_line);
12801 let first_selection_indent_column =
12802 clipboard_selections.first().map(|s| s.first_line_indent);
12803 if clipboard_selections.len() != old_selections.len() {
12804 clipboard_selections.drain(..);
12805 }
12806 let mut auto_indent_on_paste = true;
12807
12808 this.buffer.update(cx, |buffer, cx| {
12809 let snapshot = buffer.read(cx);
12810 auto_indent_on_paste = snapshot
12811 .language_settings_at(cursor_offset, cx)
12812 .auto_indent_on_paste;
12813
12814 let mut start_offset = 0;
12815 let mut edits = Vec::new();
12816 let mut original_indent_columns = Vec::new();
12817 for (ix, selection) in old_selections.iter().enumerate() {
12818 let to_insert;
12819 let entire_line;
12820 let original_indent_column;
12821 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12822 let end_offset = start_offset + clipboard_selection.len;
12823 to_insert = &clipboard_text[start_offset..end_offset];
12824 entire_line = clipboard_selection.is_entire_line;
12825 start_offset = if entire_line {
12826 end_offset
12827 } else {
12828 end_offset + 1
12829 };
12830 original_indent_column = Some(clipboard_selection.first_line_indent);
12831 } else {
12832 to_insert = &*clipboard_text;
12833 entire_line = all_selections_were_entire_line;
12834 original_indent_column = first_selection_indent_column
12835 }
12836
12837 let (range, to_insert) =
12838 if selection.is_empty() && handle_entire_lines && entire_line {
12839 // If the corresponding selection was empty when this slice of the
12840 // clipboard text was written, then the entire line containing the
12841 // selection was copied. If this selection is also currently empty,
12842 // then paste the line before the current line of the buffer.
12843 let column = selection.start.to_point(&snapshot).column as usize;
12844 let line_start = selection.start - column;
12845 (line_start..line_start, Cow::Borrowed(to_insert))
12846 } else {
12847 let language = snapshot.language_at(selection.head());
12848 let range = selection.range();
12849 if let Some(language) = language
12850 && language.name() == "Markdown".into()
12851 {
12852 edit_for_markdown_paste(
12853 &snapshot,
12854 range,
12855 to_insert,
12856 url::Url::parse(to_insert).ok(),
12857 )
12858 } else {
12859 (range, Cow::Borrowed(to_insert))
12860 }
12861 };
12862
12863 edits.push((range, to_insert));
12864 original_indent_columns.push(original_indent_column);
12865 }
12866 drop(snapshot);
12867
12868 buffer.edit(
12869 edits,
12870 if auto_indent_on_paste {
12871 Some(AutoindentMode::Block {
12872 original_indent_columns,
12873 })
12874 } else {
12875 None
12876 },
12877 cx,
12878 );
12879 });
12880
12881 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12882 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12883 } else {
12884 let url = url::Url::parse(&clipboard_text).ok();
12885
12886 let auto_indent_mode = if !clipboard_text.is_empty() {
12887 Some(AutoindentMode::Block {
12888 original_indent_columns: Vec::new(),
12889 })
12890 } else {
12891 None
12892 };
12893
12894 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12895 let snapshot = buffer.snapshot(cx);
12896
12897 let anchors = old_selections
12898 .iter()
12899 .map(|s| {
12900 let anchor = snapshot.anchor_after(s.head());
12901 s.map(|_| anchor)
12902 })
12903 .collect::<Vec<_>>();
12904
12905 let mut edits = Vec::new();
12906
12907 for selection in old_selections.iter() {
12908 let language = snapshot.language_at(selection.head());
12909 let range = selection.range();
12910
12911 let (edit_range, edit_text) = if let Some(language) = language
12912 && language.name() == "Markdown".into()
12913 {
12914 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12915 } else {
12916 (range, clipboard_text.clone())
12917 };
12918
12919 edits.push((edit_range, edit_text));
12920 }
12921
12922 drop(snapshot);
12923 buffer.edit(edits, auto_indent_mode, cx);
12924
12925 anchors
12926 });
12927
12928 this.change_selections(Default::default(), window, cx, |s| {
12929 s.select_anchors(selection_anchors);
12930 });
12931 }
12932
12933 // 🤔 | .. | show_in_menu |
12934 // | .. | true true
12935 // | had_edit_prediction | false true
12936
12937 let trigger_in_words =
12938 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12939
12940 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12941 });
12942 }
12943
12944 pub fn diff_clipboard_with_selection(
12945 &mut self,
12946 _: &DiffClipboardWithSelection,
12947 window: &mut Window,
12948 cx: &mut Context<Self>,
12949 ) {
12950 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12951
12952 if selections.is_empty() {
12953 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12954 return;
12955 };
12956
12957 let clipboard_text = match cx.read_from_clipboard() {
12958 Some(item) => match item.entries().first() {
12959 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12960 _ => None,
12961 },
12962 None => None,
12963 };
12964
12965 let Some(clipboard_text) = clipboard_text else {
12966 log::warn!("Clipboard doesn't contain text.");
12967 return;
12968 };
12969
12970 window.dispatch_action(
12971 Box::new(DiffClipboardWithSelectionData {
12972 clipboard_text,
12973 editor: cx.entity(),
12974 }),
12975 cx,
12976 );
12977 }
12978
12979 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12980 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12981 if let Some(item) = cx.read_from_clipboard() {
12982 let entries = item.entries();
12983
12984 match entries.first() {
12985 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12986 // of all the pasted entries.
12987 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12988 .do_paste(
12989 clipboard_string.text(),
12990 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12991 true,
12992 window,
12993 cx,
12994 ),
12995 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12996 }
12997 }
12998 }
12999
13000 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13001 if self.read_only(cx) {
13002 return;
13003 }
13004
13005 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13006
13007 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13008 if let Some((selections, _)) =
13009 self.selection_history.transaction(transaction_id).cloned()
13010 {
13011 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13012 s.select_anchors(selections.to_vec());
13013 });
13014 } else {
13015 log::error!(
13016 "No entry in selection_history found for undo. \
13017 This may correspond to a bug where undo does not update the selection. \
13018 If this is occurring, please add details to \
13019 https://github.com/zed-industries/zed/issues/22692"
13020 );
13021 }
13022 self.request_autoscroll(Autoscroll::fit(), cx);
13023 self.unmark_text(window, cx);
13024 self.refresh_edit_prediction(true, false, window, cx);
13025 cx.emit(EditorEvent::Edited { transaction_id });
13026 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13027 }
13028 }
13029
13030 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13031 if self.read_only(cx) {
13032 return;
13033 }
13034
13035 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13036
13037 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13038 if let Some((_, Some(selections))) =
13039 self.selection_history.transaction(transaction_id).cloned()
13040 {
13041 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13042 s.select_anchors(selections.to_vec());
13043 });
13044 } else {
13045 log::error!(
13046 "No entry in selection_history found for redo. \
13047 This may correspond to a bug where undo does not update the selection. \
13048 If this is occurring, please add details to \
13049 https://github.com/zed-industries/zed/issues/22692"
13050 );
13051 }
13052 self.request_autoscroll(Autoscroll::fit(), cx);
13053 self.unmark_text(window, cx);
13054 self.refresh_edit_prediction(true, false, window, cx);
13055 cx.emit(EditorEvent::Edited { transaction_id });
13056 }
13057 }
13058
13059 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13060 self.buffer
13061 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13062 }
13063
13064 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13065 self.buffer
13066 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13067 }
13068
13069 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13070 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13071 self.change_selections(Default::default(), window, cx, |s| {
13072 s.move_with(|map, selection| {
13073 let cursor = if selection.is_empty() {
13074 movement::left(map, selection.start)
13075 } else {
13076 selection.start
13077 };
13078 selection.collapse_to(cursor, SelectionGoal::None);
13079 });
13080 })
13081 }
13082
13083 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13084 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13085 self.change_selections(Default::default(), window, cx, |s| {
13086 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13087 })
13088 }
13089
13090 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13091 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13092 self.change_selections(Default::default(), window, cx, |s| {
13093 s.move_with(|map, selection| {
13094 let cursor = if selection.is_empty() {
13095 movement::right(map, selection.end)
13096 } else {
13097 selection.end
13098 };
13099 selection.collapse_to(cursor, SelectionGoal::None)
13100 });
13101 })
13102 }
13103
13104 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13105 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13106 self.change_selections(Default::default(), window, cx, |s| {
13107 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13108 });
13109 }
13110
13111 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13112 if self.take_rename(true, window, cx).is_some() {
13113 return;
13114 }
13115
13116 if self.mode.is_single_line() {
13117 cx.propagate();
13118 return;
13119 }
13120
13121 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13122
13123 let text_layout_details = &self.text_layout_details(window);
13124 let selection_count = self.selections.count();
13125 let first_selection = self.selections.first_anchor();
13126
13127 self.change_selections(Default::default(), window, cx, |s| {
13128 s.move_with(|map, selection| {
13129 if !selection.is_empty() {
13130 selection.goal = SelectionGoal::None;
13131 }
13132 let (cursor, goal) = movement::up(
13133 map,
13134 selection.start,
13135 selection.goal,
13136 false,
13137 text_layout_details,
13138 );
13139 selection.collapse_to(cursor, goal);
13140 });
13141 });
13142
13143 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13144 {
13145 cx.propagate();
13146 }
13147 }
13148
13149 pub fn move_up_by_lines(
13150 &mut self,
13151 action: &MoveUpByLines,
13152 window: &mut Window,
13153 cx: &mut Context<Self>,
13154 ) {
13155 if self.take_rename(true, window, cx).is_some() {
13156 return;
13157 }
13158
13159 if self.mode.is_single_line() {
13160 cx.propagate();
13161 return;
13162 }
13163
13164 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13165
13166 let text_layout_details = &self.text_layout_details(window);
13167
13168 self.change_selections(Default::default(), window, cx, |s| {
13169 s.move_with(|map, selection| {
13170 if !selection.is_empty() {
13171 selection.goal = SelectionGoal::None;
13172 }
13173 let (cursor, goal) = movement::up_by_rows(
13174 map,
13175 selection.start,
13176 action.lines,
13177 selection.goal,
13178 false,
13179 text_layout_details,
13180 );
13181 selection.collapse_to(cursor, goal);
13182 });
13183 })
13184 }
13185
13186 pub fn move_down_by_lines(
13187 &mut self,
13188 action: &MoveDownByLines,
13189 window: &mut Window,
13190 cx: &mut Context<Self>,
13191 ) {
13192 if self.take_rename(true, window, cx).is_some() {
13193 return;
13194 }
13195
13196 if self.mode.is_single_line() {
13197 cx.propagate();
13198 return;
13199 }
13200
13201 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13202
13203 let text_layout_details = &self.text_layout_details(window);
13204
13205 self.change_selections(Default::default(), window, cx, |s| {
13206 s.move_with(|map, selection| {
13207 if !selection.is_empty() {
13208 selection.goal = SelectionGoal::None;
13209 }
13210 let (cursor, goal) = movement::down_by_rows(
13211 map,
13212 selection.start,
13213 action.lines,
13214 selection.goal,
13215 false,
13216 text_layout_details,
13217 );
13218 selection.collapse_to(cursor, goal);
13219 });
13220 })
13221 }
13222
13223 pub fn select_down_by_lines(
13224 &mut self,
13225 action: &SelectDownByLines,
13226 window: &mut Window,
13227 cx: &mut Context<Self>,
13228 ) {
13229 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13230 let text_layout_details = &self.text_layout_details(window);
13231 self.change_selections(Default::default(), window, cx, |s| {
13232 s.move_heads_with(|map, head, goal| {
13233 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13234 })
13235 })
13236 }
13237
13238 pub fn select_up_by_lines(
13239 &mut self,
13240 action: &SelectUpByLines,
13241 window: &mut Window,
13242 cx: &mut Context<Self>,
13243 ) {
13244 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13245 let text_layout_details = &self.text_layout_details(window);
13246 self.change_selections(Default::default(), window, cx, |s| {
13247 s.move_heads_with(|map, head, goal| {
13248 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13249 })
13250 })
13251 }
13252
13253 pub fn select_page_up(
13254 &mut self,
13255 _: &SelectPageUp,
13256 window: &mut Window,
13257 cx: &mut Context<Self>,
13258 ) {
13259 let Some(row_count) = self.visible_row_count() else {
13260 return;
13261 };
13262
13263 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13264
13265 let text_layout_details = &self.text_layout_details(window);
13266
13267 self.change_selections(Default::default(), window, cx, |s| {
13268 s.move_heads_with(|map, head, goal| {
13269 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13270 })
13271 })
13272 }
13273
13274 pub fn move_page_up(
13275 &mut self,
13276 action: &MovePageUp,
13277 window: &mut Window,
13278 cx: &mut Context<Self>,
13279 ) {
13280 if self.take_rename(true, window, cx).is_some() {
13281 return;
13282 }
13283
13284 if self
13285 .context_menu
13286 .borrow_mut()
13287 .as_mut()
13288 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13289 .unwrap_or(false)
13290 {
13291 return;
13292 }
13293
13294 if matches!(self.mode, EditorMode::SingleLine) {
13295 cx.propagate();
13296 return;
13297 }
13298
13299 let Some(row_count) = self.visible_row_count() else {
13300 return;
13301 };
13302
13303 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13304
13305 let effects = if action.center_cursor {
13306 SelectionEffects::scroll(Autoscroll::center())
13307 } else {
13308 SelectionEffects::default()
13309 };
13310
13311 let text_layout_details = &self.text_layout_details(window);
13312
13313 self.change_selections(effects, window, cx, |s| {
13314 s.move_with(|map, selection| {
13315 if !selection.is_empty() {
13316 selection.goal = SelectionGoal::None;
13317 }
13318 let (cursor, goal) = movement::up_by_rows(
13319 map,
13320 selection.end,
13321 row_count,
13322 selection.goal,
13323 false,
13324 text_layout_details,
13325 );
13326 selection.collapse_to(cursor, goal);
13327 });
13328 });
13329 }
13330
13331 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13332 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13333 let text_layout_details = &self.text_layout_details(window);
13334 self.change_selections(Default::default(), window, cx, |s| {
13335 s.move_heads_with(|map, head, goal| {
13336 movement::up(map, head, goal, false, text_layout_details)
13337 })
13338 })
13339 }
13340
13341 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13342 self.take_rename(true, window, cx);
13343
13344 if self.mode.is_single_line() {
13345 cx.propagate();
13346 return;
13347 }
13348
13349 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13350
13351 let text_layout_details = &self.text_layout_details(window);
13352 let selection_count = self.selections.count();
13353 let first_selection = self.selections.first_anchor();
13354
13355 self.change_selections(Default::default(), window, cx, |s| {
13356 s.move_with(|map, selection| {
13357 if !selection.is_empty() {
13358 selection.goal = SelectionGoal::None;
13359 }
13360 let (cursor, goal) = movement::down(
13361 map,
13362 selection.end,
13363 selection.goal,
13364 false,
13365 text_layout_details,
13366 );
13367 selection.collapse_to(cursor, goal);
13368 });
13369 });
13370
13371 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13372 {
13373 cx.propagate();
13374 }
13375 }
13376
13377 pub fn select_page_down(
13378 &mut self,
13379 _: &SelectPageDown,
13380 window: &mut Window,
13381 cx: &mut Context<Self>,
13382 ) {
13383 let Some(row_count) = self.visible_row_count() else {
13384 return;
13385 };
13386
13387 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13388
13389 let text_layout_details = &self.text_layout_details(window);
13390
13391 self.change_selections(Default::default(), window, cx, |s| {
13392 s.move_heads_with(|map, head, goal| {
13393 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13394 })
13395 })
13396 }
13397
13398 pub fn move_page_down(
13399 &mut self,
13400 action: &MovePageDown,
13401 window: &mut Window,
13402 cx: &mut Context<Self>,
13403 ) {
13404 if self.take_rename(true, window, cx).is_some() {
13405 return;
13406 }
13407
13408 if self
13409 .context_menu
13410 .borrow_mut()
13411 .as_mut()
13412 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13413 .unwrap_or(false)
13414 {
13415 return;
13416 }
13417
13418 if matches!(self.mode, EditorMode::SingleLine) {
13419 cx.propagate();
13420 return;
13421 }
13422
13423 let Some(row_count) = self.visible_row_count() else {
13424 return;
13425 };
13426
13427 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13428
13429 let effects = if action.center_cursor {
13430 SelectionEffects::scroll(Autoscroll::center())
13431 } else {
13432 SelectionEffects::default()
13433 };
13434
13435 let text_layout_details = &self.text_layout_details(window);
13436 self.change_selections(effects, window, cx, |s| {
13437 s.move_with(|map, selection| {
13438 if !selection.is_empty() {
13439 selection.goal = SelectionGoal::None;
13440 }
13441 let (cursor, goal) = movement::down_by_rows(
13442 map,
13443 selection.end,
13444 row_count,
13445 selection.goal,
13446 false,
13447 text_layout_details,
13448 );
13449 selection.collapse_to(cursor, goal);
13450 });
13451 });
13452 }
13453
13454 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13455 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13456 let text_layout_details = &self.text_layout_details(window);
13457 self.change_selections(Default::default(), window, cx, |s| {
13458 s.move_heads_with(|map, head, goal| {
13459 movement::down(map, head, goal, false, text_layout_details)
13460 })
13461 });
13462 }
13463
13464 pub fn context_menu_first(
13465 &mut self,
13466 _: &ContextMenuFirst,
13467 window: &mut Window,
13468 cx: &mut Context<Self>,
13469 ) {
13470 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13471 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13472 }
13473 }
13474
13475 pub fn context_menu_prev(
13476 &mut self,
13477 _: &ContextMenuPrevious,
13478 window: &mut Window,
13479 cx: &mut Context<Self>,
13480 ) {
13481 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13482 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13483 }
13484 }
13485
13486 pub fn context_menu_next(
13487 &mut self,
13488 _: &ContextMenuNext,
13489 window: &mut Window,
13490 cx: &mut Context<Self>,
13491 ) {
13492 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13493 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13494 }
13495 }
13496
13497 pub fn context_menu_last(
13498 &mut self,
13499 _: &ContextMenuLast,
13500 window: &mut Window,
13501 cx: &mut Context<Self>,
13502 ) {
13503 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13504 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13505 }
13506 }
13507
13508 pub fn signature_help_prev(
13509 &mut self,
13510 _: &SignatureHelpPrevious,
13511 _: &mut Window,
13512 cx: &mut Context<Self>,
13513 ) {
13514 if let Some(popover) = self.signature_help_state.popover_mut() {
13515 if popover.current_signature == 0 {
13516 popover.current_signature = popover.signatures.len() - 1;
13517 } else {
13518 popover.current_signature -= 1;
13519 }
13520 cx.notify();
13521 }
13522 }
13523
13524 pub fn signature_help_next(
13525 &mut self,
13526 _: &SignatureHelpNext,
13527 _: &mut Window,
13528 cx: &mut Context<Self>,
13529 ) {
13530 if let Some(popover) = self.signature_help_state.popover_mut() {
13531 if popover.current_signature + 1 == popover.signatures.len() {
13532 popover.current_signature = 0;
13533 } else {
13534 popover.current_signature += 1;
13535 }
13536 cx.notify();
13537 }
13538 }
13539
13540 pub fn move_to_previous_word_start(
13541 &mut self,
13542 _: &MoveToPreviousWordStart,
13543 window: &mut Window,
13544 cx: &mut Context<Self>,
13545 ) {
13546 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13547 self.change_selections(Default::default(), window, cx, |s| {
13548 s.move_cursors_with(|map, head, _| {
13549 (
13550 movement::previous_word_start(map, head),
13551 SelectionGoal::None,
13552 )
13553 });
13554 })
13555 }
13556
13557 pub fn move_to_previous_subword_start(
13558 &mut self,
13559 _: &MoveToPreviousSubwordStart,
13560 window: &mut Window,
13561 cx: &mut Context<Self>,
13562 ) {
13563 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13564 self.change_selections(Default::default(), window, cx, |s| {
13565 s.move_cursors_with(|map, head, _| {
13566 (
13567 movement::previous_subword_start(map, head),
13568 SelectionGoal::None,
13569 )
13570 });
13571 })
13572 }
13573
13574 pub fn select_to_previous_word_start(
13575 &mut self,
13576 _: &SelectToPreviousWordStart,
13577 window: &mut Window,
13578 cx: &mut Context<Self>,
13579 ) {
13580 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13581 self.change_selections(Default::default(), window, cx, |s| {
13582 s.move_heads_with(|map, head, _| {
13583 (
13584 movement::previous_word_start(map, head),
13585 SelectionGoal::None,
13586 )
13587 });
13588 })
13589 }
13590
13591 pub fn select_to_previous_subword_start(
13592 &mut self,
13593 _: &SelectToPreviousSubwordStart,
13594 window: &mut Window,
13595 cx: &mut Context<Self>,
13596 ) {
13597 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13598 self.change_selections(Default::default(), window, cx, |s| {
13599 s.move_heads_with(|map, head, _| {
13600 (
13601 movement::previous_subword_start(map, head),
13602 SelectionGoal::None,
13603 )
13604 });
13605 })
13606 }
13607
13608 pub fn delete_to_previous_word_start(
13609 &mut self,
13610 action: &DeleteToPreviousWordStart,
13611 window: &mut Window,
13612 cx: &mut Context<Self>,
13613 ) {
13614 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13615 self.transact(window, cx, |this, window, cx| {
13616 this.select_autoclose_pair(window, cx);
13617 this.change_selections(Default::default(), window, cx, |s| {
13618 s.move_with(|map, selection| {
13619 if selection.is_empty() {
13620 let mut cursor = if action.ignore_newlines {
13621 movement::previous_word_start(map, selection.head())
13622 } else {
13623 movement::previous_word_start_or_newline(map, selection.head())
13624 };
13625 cursor = movement::adjust_greedy_deletion(
13626 map,
13627 selection.head(),
13628 cursor,
13629 action.ignore_brackets,
13630 );
13631 selection.set_head(cursor, SelectionGoal::None);
13632 }
13633 });
13634 });
13635 this.insert("", window, cx);
13636 });
13637 }
13638
13639 pub fn delete_to_previous_subword_start(
13640 &mut self,
13641 _: &DeleteToPreviousSubwordStart,
13642 window: &mut Window,
13643 cx: &mut Context<Self>,
13644 ) {
13645 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13646 self.transact(window, cx, |this, window, cx| {
13647 this.select_autoclose_pair(window, cx);
13648 this.change_selections(Default::default(), window, cx, |s| {
13649 s.move_with(|map, selection| {
13650 if selection.is_empty() {
13651 let mut cursor = movement::previous_subword_start(map, selection.head());
13652 cursor =
13653 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13654 selection.set_head(cursor, SelectionGoal::None);
13655 }
13656 });
13657 });
13658 this.insert("", window, cx);
13659 });
13660 }
13661
13662 pub fn move_to_next_word_end(
13663 &mut self,
13664 _: &MoveToNextWordEnd,
13665 window: &mut Window,
13666 cx: &mut Context<Self>,
13667 ) {
13668 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13669 self.change_selections(Default::default(), window, cx, |s| {
13670 s.move_cursors_with(|map, head, _| {
13671 (movement::next_word_end(map, head), SelectionGoal::None)
13672 });
13673 })
13674 }
13675
13676 pub fn move_to_next_subword_end(
13677 &mut self,
13678 _: &MoveToNextSubwordEnd,
13679 window: &mut Window,
13680 cx: &mut Context<Self>,
13681 ) {
13682 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13683 self.change_selections(Default::default(), window, cx, |s| {
13684 s.move_cursors_with(|map, head, _| {
13685 (movement::next_subword_end(map, head), SelectionGoal::None)
13686 });
13687 })
13688 }
13689
13690 pub fn select_to_next_word_end(
13691 &mut self,
13692 _: &SelectToNextWordEnd,
13693 window: &mut Window,
13694 cx: &mut Context<Self>,
13695 ) {
13696 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13697 self.change_selections(Default::default(), window, cx, |s| {
13698 s.move_heads_with(|map, head, _| {
13699 (movement::next_word_end(map, head), SelectionGoal::None)
13700 });
13701 })
13702 }
13703
13704 pub fn select_to_next_subword_end(
13705 &mut self,
13706 _: &SelectToNextSubwordEnd,
13707 window: &mut Window,
13708 cx: &mut Context<Self>,
13709 ) {
13710 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13711 self.change_selections(Default::default(), window, cx, |s| {
13712 s.move_heads_with(|map, head, _| {
13713 (movement::next_subword_end(map, head), SelectionGoal::None)
13714 });
13715 })
13716 }
13717
13718 pub fn delete_to_next_word_end(
13719 &mut self,
13720 action: &DeleteToNextWordEnd,
13721 window: &mut Window,
13722 cx: &mut Context<Self>,
13723 ) {
13724 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13725 self.transact(window, cx, |this, window, cx| {
13726 this.change_selections(Default::default(), window, cx, |s| {
13727 s.move_with(|map, selection| {
13728 if selection.is_empty() {
13729 let mut cursor = if action.ignore_newlines {
13730 movement::next_word_end(map, selection.head())
13731 } else {
13732 movement::next_word_end_or_newline(map, selection.head())
13733 };
13734 cursor = movement::adjust_greedy_deletion(
13735 map,
13736 selection.head(),
13737 cursor,
13738 action.ignore_brackets,
13739 );
13740 selection.set_head(cursor, SelectionGoal::None);
13741 }
13742 });
13743 });
13744 this.insert("", window, cx);
13745 });
13746 }
13747
13748 pub fn delete_to_next_subword_end(
13749 &mut self,
13750 _: &DeleteToNextSubwordEnd,
13751 window: &mut Window,
13752 cx: &mut Context<Self>,
13753 ) {
13754 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13755 self.transact(window, cx, |this, window, cx| {
13756 this.change_selections(Default::default(), window, cx, |s| {
13757 s.move_with(|map, selection| {
13758 if selection.is_empty() {
13759 let mut cursor = movement::next_subword_end(map, selection.head());
13760 cursor =
13761 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13762 selection.set_head(cursor, SelectionGoal::None);
13763 }
13764 });
13765 });
13766 this.insert("", window, cx);
13767 });
13768 }
13769
13770 pub fn move_to_beginning_of_line(
13771 &mut self,
13772 action: &MoveToBeginningOfLine,
13773 window: &mut Window,
13774 cx: &mut Context<Self>,
13775 ) {
13776 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13777 self.change_selections(Default::default(), window, cx, |s| {
13778 s.move_cursors_with(|map, head, _| {
13779 (
13780 movement::indented_line_beginning(
13781 map,
13782 head,
13783 action.stop_at_soft_wraps,
13784 action.stop_at_indent,
13785 ),
13786 SelectionGoal::None,
13787 )
13788 });
13789 })
13790 }
13791
13792 pub fn select_to_beginning_of_line(
13793 &mut self,
13794 action: &SelectToBeginningOfLine,
13795 window: &mut Window,
13796 cx: &mut Context<Self>,
13797 ) {
13798 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13799 self.change_selections(Default::default(), window, cx, |s| {
13800 s.move_heads_with(|map, head, _| {
13801 (
13802 movement::indented_line_beginning(
13803 map,
13804 head,
13805 action.stop_at_soft_wraps,
13806 action.stop_at_indent,
13807 ),
13808 SelectionGoal::None,
13809 )
13810 });
13811 });
13812 }
13813
13814 pub fn delete_to_beginning_of_line(
13815 &mut self,
13816 action: &DeleteToBeginningOfLine,
13817 window: &mut Window,
13818 cx: &mut Context<Self>,
13819 ) {
13820 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13821 self.transact(window, cx, |this, window, cx| {
13822 this.change_selections(Default::default(), window, cx, |s| {
13823 s.move_with(|_, selection| {
13824 selection.reversed = true;
13825 });
13826 });
13827
13828 this.select_to_beginning_of_line(
13829 &SelectToBeginningOfLine {
13830 stop_at_soft_wraps: false,
13831 stop_at_indent: action.stop_at_indent,
13832 },
13833 window,
13834 cx,
13835 );
13836 this.backspace(&Backspace, window, cx);
13837 });
13838 }
13839
13840 pub fn move_to_end_of_line(
13841 &mut self,
13842 action: &MoveToEndOfLine,
13843 window: &mut Window,
13844 cx: &mut Context<Self>,
13845 ) {
13846 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13847 self.change_selections(Default::default(), window, cx, |s| {
13848 s.move_cursors_with(|map, head, _| {
13849 (
13850 movement::line_end(map, head, action.stop_at_soft_wraps),
13851 SelectionGoal::None,
13852 )
13853 });
13854 })
13855 }
13856
13857 pub fn select_to_end_of_line(
13858 &mut self,
13859 action: &SelectToEndOfLine,
13860 window: &mut Window,
13861 cx: &mut Context<Self>,
13862 ) {
13863 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13864 self.change_selections(Default::default(), window, cx, |s| {
13865 s.move_heads_with(|map, head, _| {
13866 (
13867 movement::line_end(map, head, action.stop_at_soft_wraps),
13868 SelectionGoal::None,
13869 )
13870 });
13871 })
13872 }
13873
13874 pub fn delete_to_end_of_line(
13875 &mut self,
13876 _: &DeleteToEndOfLine,
13877 window: &mut Window,
13878 cx: &mut Context<Self>,
13879 ) {
13880 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13881 self.transact(window, cx, |this, window, cx| {
13882 this.select_to_end_of_line(
13883 &SelectToEndOfLine {
13884 stop_at_soft_wraps: false,
13885 },
13886 window,
13887 cx,
13888 );
13889 this.delete(&Delete, window, cx);
13890 });
13891 }
13892
13893 pub fn cut_to_end_of_line(
13894 &mut self,
13895 action: &CutToEndOfLine,
13896 window: &mut Window,
13897 cx: &mut Context<Self>,
13898 ) {
13899 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13900 self.transact(window, cx, |this, window, cx| {
13901 this.select_to_end_of_line(
13902 &SelectToEndOfLine {
13903 stop_at_soft_wraps: false,
13904 },
13905 window,
13906 cx,
13907 );
13908 if !action.stop_at_newlines {
13909 this.change_selections(Default::default(), window, cx, |s| {
13910 s.move_with(|_, sel| {
13911 if sel.is_empty() {
13912 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13913 }
13914 });
13915 });
13916 }
13917 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13918 let item = this.cut_common(false, window, cx);
13919 cx.write_to_clipboard(item);
13920 });
13921 }
13922
13923 pub fn move_to_start_of_paragraph(
13924 &mut self,
13925 _: &MoveToStartOfParagraph,
13926 window: &mut Window,
13927 cx: &mut Context<Self>,
13928 ) {
13929 if matches!(self.mode, EditorMode::SingleLine) {
13930 cx.propagate();
13931 return;
13932 }
13933 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13934 self.change_selections(Default::default(), window, cx, |s| {
13935 s.move_with(|map, selection| {
13936 selection.collapse_to(
13937 movement::start_of_paragraph(map, selection.head(), 1),
13938 SelectionGoal::None,
13939 )
13940 });
13941 })
13942 }
13943
13944 pub fn move_to_end_of_paragraph(
13945 &mut self,
13946 _: &MoveToEndOfParagraph,
13947 window: &mut Window,
13948 cx: &mut Context<Self>,
13949 ) {
13950 if matches!(self.mode, EditorMode::SingleLine) {
13951 cx.propagate();
13952 return;
13953 }
13954 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13955 self.change_selections(Default::default(), window, cx, |s| {
13956 s.move_with(|map, selection| {
13957 selection.collapse_to(
13958 movement::end_of_paragraph(map, selection.head(), 1),
13959 SelectionGoal::None,
13960 )
13961 });
13962 })
13963 }
13964
13965 pub fn select_to_start_of_paragraph(
13966 &mut self,
13967 _: &SelectToStartOfParagraph,
13968 window: &mut Window,
13969 cx: &mut Context<Self>,
13970 ) {
13971 if matches!(self.mode, EditorMode::SingleLine) {
13972 cx.propagate();
13973 return;
13974 }
13975 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13976 self.change_selections(Default::default(), window, cx, |s| {
13977 s.move_heads_with(|map, head, _| {
13978 (
13979 movement::start_of_paragraph(map, head, 1),
13980 SelectionGoal::None,
13981 )
13982 });
13983 })
13984 }
13985
13986 pub fn select_to_end_of_paragraph(
13987 &mut self,
13988 _: &SelectToEndOfParagraph,
13989 window: &mut Window,
13990 cx: &mut Context<Self>,
13991 ) {
13992 if matches!(self.mode, EditorMode::SingleLine) {
13993 cx.propagate();
13994 return;
13995 }
13996 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13997 self.change_selections(Default::default(), window, cx, |s| {
13998 s.move_heads_with(|map, head, _| {
13999 (
14000 movement::end_of_paragraph(map, head, 1),
14001 SelectionGoal::None,
14002 )
14003 });
14004 })
14005 }
14006
14007 pub fn move_to_start_of_excerpt(
14008 &mut self,
14009 _: &MoveToStartOfExcerpt,
14010 window: &mut Window,
14011 cx: &mut Context<Self>,
14012 ) {
14013 if matches!(self.mode, EditorMode::SingleLine) {
14014 cx.propagate();
14015 return;
14016 }
14017 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14018 self.change_selections(Default::default(), window, cx, |s| {
14019 s.move_with(|map, selection| {
14020 selection.collapse_to(
14021 movement::start_of_excerpt(
14022 map,
14023 selection.head(),
14024 workspace::searchable::Direction::Prev,
14025 ),
14026 SelectionGoal::None,
14027 )
14028 });
14029 })
14030 }
14031
14032 pub fn move_to_start_of_next_excerpt(
14033 &mut self,
14034 _: &MoveToStartOfNextExcerpt,
14035 window: &mut Window,
14036 cx: &mut Context<Self>,
14037 ) {
14038 if matches!(self.mode, EditorMode::SingleLine) {
14039 cx.propagate();
14040 return;
14041 }
14042
14043 self.change_selections(Default::default(), window, cx, |s| {
14044 s.move_with(|map, selection| {
14045 selection.collapse_to(
14046 movement::start_of_excerpt(
14047 map,
14048 selection.head(),
14049 workspace::searchable::Direction::Next,
14050 ),
14051 SelectionGoal::None,
14052 )
14053 });
14054 })
14055 }
14056
14057 pub fn move_to_end_of_excerpt(
14058 &mut self,
14059 _: &MoveToEndOfExcerpt,
14060 window: &mut Window,
14061 cx: &mut Context<Self>,
14062 ) {
14063 if matches!(self.mode, EditorMode::SingleLine) {
14064 cx.propagate();
14065 return;
14066 }
14067 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14068 self.change_selections(Default::default(), window, cx, |s| {
14069 s.move_with(|map, selection| {
14070 selection.collapse_to(
14071 movement::end_of_excerpt(
14072 map,
14073 selection.head(),
14074 workspace::searchable::Direction::Next,
14075 ),
14076 SelectionGoal::None,
14077 )
14078 });
14079 })
14080 }
14081
14082 pub fn move_to_end_of_previous_excerpt(
14083 &mut self,
14084 _: &MoveToEndOfPreviousExcerpt,
14085 window: &mut Window,
14086 cx: &mut Context<Self>,
14087 ) {
14088 if matches!(self.mode, EditorMode::SingleLine) {
14089 cx.propagate();
14090 return;
14091 }
14092 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14093 self.change_selections(Default::default(), window, cx, |s| {
14094 s.move_with(|map, selection| {
14095 selection.collapse_to(
14096 movement::end_of_excerpt(
14097 map,
14098 selection.head(),
14099 workspace::searchable::Direction::Prev,
14100 ),
14101 SelectionGoal::None,
14102 )
14103 });
14104 })
14105 }
14106
14107 pub fn select_to_start_of_excerpt(
14108 &mut self,
14109 _: &SelectToStartOfExcerpt,
14110 window: &mut Window,
14111 cx: &mut Context<Self>,
14112 ) {
14113 if matches!(self.mode, EditorMode::SingleLine) {
14114 cx.propagate();
14115 return;
14116 }
14117 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14118 self.change_selections(Default::default(), window, cx, |s| {
14119 s.move_heads_with(|map, head, _| {
14120 (
14121 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14122 SelectionGoal::None,
14123 )
14124 });
14125 })
14126 }
14127
14128 pub fn select_to_start_of_next_excerpt(
14129 &mut self,
14130 _: &SelectToStartOfNextExcerpt,
14131 window: &mut Window,
14132 cx: &mut Context<Self>,
14133 ) {
14134 if matches!(self.mode, EditorMode::SingleLine) {
14135 cx.propagate();
14136 return;
14137 }
14138 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14139 self.change_selections(Default::default(), window, cx, |s| {
14140 s.move_heads_with(|map, head, _| {
14141 (
14142 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14143 SelectionGoal::None,
14144 )
14145 });
14146 })
14147 }
14148
14149 pub fn select_to_end_of_excerpt(
14150 &mut self,
14151 _: &SelectToEndOfExcerpt,
14152 window: &mut Window,
14153 cx: &mut Context<Self>,
14154 ) {
14155 if matches!(self.mode, EditorMode::SingleLine) {
14156 cx.propagate();
14157 return;
14158 }
14159 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14160 self.change_selections(Default::default(), window, cx, |s| {
14161 s.move_heads_with(|map, head, _| {
14162 (
14163 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14164 SelectionGoal::None,
14165 )
14166 });
14167 })
14168 }
14169
14170 pub fn select_to_end_of_previous_excerpt(
14171 &mut self,
14172 _: &SelectToEndOfPreviousExcerpt,
14173 window: &mut Window,
14174 cx: &mut Context<Self>,
14175 ) {
14176 if matches!(self.mode, EditorMode::SingleLine) {
14177 cx.propagate();
14178 return;
14179 }
14180 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14181 self.change_selections(Default::default(), window, cx, |s| {
14182 s.move_heads_with(|map, head, _| {
14183 (
14184 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14185 SelectionGoal::None,
14186 )
14187 });
14188 })
14189 }
14190
14191 pub fn move_to_beginning(
14192 &mut self,
14193 _: &MoveToBeginning,
14194 window: &mut Window,
14195 cx: &mut Context<Self>,
14196 ) {
14197 if matches!(self.mode, EditorMode::SingleLine) {
14198 cx.propagate();
14199 return;
14200 }
14201 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14202 self.change_selections(Default::default(), window, cx, |s| {
14203 s.select_ranges(vec![0..0]);
14204 });
14205 }
14206
14207 pub fn select_to_beginning(
14208 &mut self,
14209 _: &SelectToBeginning,
14210 window: &mut Window,
14211 cx: &mut Context<Self>,
14212 ) {
14213 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14214 selection.set_head(Point::zero(), SelectionGoal::None);
14215 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14216 self.change_selections(Default::default(), window, cx, |s| {
14217 s.select(vec![selection]);
14218 });
14219 }
14220
14221 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14222 if matches!(self.mode, EditorMode::SingleLine) {
14223 cx.propagate();
14224 return;
14225 }
14226 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14227 let cursor = self.buffer.read(cx).read(cx).len();
14228 self.change_selections(Default::default(), window, cx, |s| {
14229 s.select_ranges(vec![cursor..cursor])
14230 });
14231 }
14232
14233 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14234 self.nav_history = nav_history;
14235 }
14236
14237 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14238 self.nav_history.as_ref()
14239 }
14240
14241 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14242 self.push_to_nav_history(
14243 self.selections.newest_anchor().head(),
14244 None,
14245 false,
14246 true,
14247 cx,
14248 );
14249 }
14250
14251 fn push_to_nav_history(
14252 &mut self,
14253 cursor_anchor: Anchor,
14254 new_position: Option<Point>,
14255 is_deactivate: bool,
14256 always: bool,
14257 cx: &mut Context<Self>,
14258 ) {
14259 if let Some(nav_history) = self.nav_history.as_mut() {
14260 let buffer = self.buffer.read(cx).read(cx);
14261 let cursor_position = cursor_anchor.to_point(&buffer);
14262 let scroll_state = self.scroll_manager.anchor();
14263 let scroll_top_row = scroll_state.top_row(&buffer);
14264 drop(buffer);
14265
14266 if let Some(new_position) = new_position {
14267 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14268 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14269 return;
14270 }
14271 }
14272
14273 nav_history.push(
14274 Some(NavigationData {
14275 cursor_anchor,
14276 cursor_position,
14277 scroll_anchor: scroll_state,
14278 scroll_top_row,
14279 }),
14280 cx,
14281 );
14282 cx.emit(EditorEvent::PushedToNavHistory {
14283 anchor: cursor_anchor,
14284 is_deactivate,
14285 })
14286 }
14287 }
14288
14289 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14290 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14291 let buffer = self.buffer.read(cx).snapshot(cx);
14292 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14293 selection.set_head(buffer.len(), SelectionGoal::None);
14294 self.change_selections(Default::default(), window, cx, |s| {
14295 s.select(vec![selection]);
14296 });
14297 }
14298
14299 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14300 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14301 let end = self.buffer.read(cx).read(cx).len();
14302 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14303 s.select_ranges(vec![0..end]);
14304 });
14305 }
14306
14307 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14308 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14309 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14310 let mut selections = self.selections.all::<Point>(&display_map);
14311 let max_point = display_map.buffer_snapshot().max_point();
14312 for selection in &mut selections {
14313 let rows = selection.spanned_rows(true, &display_map);
14314 selection.start = Point::new(rows.start.0, 0);
14315 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14316 selection.reversed = false;
14317 }
14318 self.change_selections(Default::default(), window, cx, |s| {
14319 s.select(selections);
14320 });
14321 }
14322
14323 pub fn split_selection_into_lines(
14324 &mut self,
14325 action: &SplitSelectionIntoLines,
14326 window: &mut Window,
14327 cx: &mut Context<Self>,
14328 ) {
14329 let selections = self
14330 .selections
14331 .all::<Point>(&self.display_snapshot(cx))
14332 .into_iter()
14333 .map(|selection| selection.start..selection.end)
14334 .collect::<Vec<_>>();
14335 self.unfold_ranges(&selections, true, true, cx);
14336
14337 let mut new_selection_ranges = Vec::new();
14338 {
14339 let buffer = self.buffer.read(cx).read(cx);
14340 for selection in selections {
14341 for row in selection.start.row..selection.end.row {
14342 let line_start = Point::new(row, 0);
14343 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14344
14345 if action.keep_selections {
14346 // Keep the selection range for each line
14347 let selection_start = if row == selection.start.row {
14348 selection.start
14349 } else {
14350 line_start
14351 };
14352 new_selection_ranges.push(selection_start..line_end);
14353 } else {
14354 // Collapse to cursor at end of line
14355 new_selection_ranges.push(line_end..line_end);
14356 }
14357 }
14358
14359 let is_multiline_selection = selection.start.row != selection.end.row;
14360 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14361 // so this action feels more ergonomic when paired with other selection operations
14362 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14363 if !should_skip_last {
14364 if action.keep_selections {
14365 if is_multiline_selection {
14366 let line_start = Point::new(selection.end.row, 0);
14367 new_selection_ranges.push(line_start..selection.end);
14368 } else {
14369 new_selection_ranges.push(selection.start..selection.end);
14370 }
14371 } else {
14372 new_selection_ranges.push(selection.end..selection.end);
14373 }
14374 }
14375 }
14376 }
14377 self.change_selections(Default::default(), window, cx, |s| {
14378 s.select_ranges(new_selection_ranges);
14379 });
14380 }
14381
14382 pub fn add_selection_above(
14383 &mut self,
14384 action: &AddSelectionAbove,
14385 window: &mut Window,
14386 cx: &mut Context<Self>,
14387 ) {
14388 self.add_selection(true, action.skip_soft_wrap, window, cx);
14389 }
14390
14391 pub fn add_selection_below(
14392 &mut self,
14393 action: &AddSelectionBelow,
14394 window: &mut Window,
14395 cx: &mut Context<Self>,
14396 ) {
14397 self.add_selection(false, action.skip_soft_wrap, window, cx);
14398 }
14399
14400 fn add_selection(
14401 &mut self,
14402 above: bool,
14403 skip_soft_wrap: bool,
14404 window: &mut Window,
14405 cx: &mut Context<Self>,
14406 ) {
14407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14408
14409 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14410 let all_selections = self.selections.all::<Point>(&display_map);
14411 let text_layout_details = self.text_layout_details(window);
14412
14413 let (mut columnar_selections, new_selections_to_columnarize) = {
14414 if let Some(state) = self.add_selections_state.as_ref() {
14415 let columnar_selection_ids: HashSet<_> = state
14416 .groups
14417 .iter()
14418 .flat_map(|group| group.stack.iter())
14419 .copied()
14420 .collect();
14421
14422 all_selections
14423 .into_iter()
14424 .partition(|s| columnar_selection_ids.contains(&s.id))
14425 } else {
14426 (Vec::new(), all_selections)
14427 }
14428 };
14429
14430 let mut state = self
14431 .add_selections_state
14432 .take()
14433 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14434
14435 for selection in new_selections_to_columnarize {
14436 let range = selection.display_range(&display_map).sorted();
14437 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14438 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14439 let positions = start_x.min(end_x)..start_x.max(end_x);
14440 let mut stack = Vec::new();
14441 for row in range.start.row().0..=range.end.row().0 {
14442 if let Some(selection) = self.selections.build_columnar_selection(
14443 &display_map,
14444 DisplayRow(row),
14445 &positions,
14446 selection.reversed,
14447 &text_layout_details,
14448 ) {
14449 stack.push(selection.id);
14450 columnar_selections.push(selection);
14451 }
14452 }
14453 if !stack.is_empty() {
14454 if above {
14455 stack.reverse();
14456 }
14457 state.groups.push(AddSelectionsGroup { above, stack });
14458 }
14459 }
14460
14461 let mut final_selections = Vec::new();
14462 let end_row = if above {
14463 DisplayRow(0)
14464 } else {
14465 display_map.max_point().row()
14466 };
14467
14468 let mut last_added_item_per_group = HashMap::default();
14469 for group in state.groups.iter_mut() {
14470 if let Some(last_id) = group.stack.last() {
14471 last_added_item_per_group.insert(*last_id, group);
14472 }
14473 }
14474
14475 for selection in columnar_selections {
14476 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14477 if above == group.above {
14478 let range = selection.display_range(&display_map).sorted();
14479 debug_assert_eq!(range.start.row(), range.end.row());
14480 let mut row = range.start.row();
14481 let positions =
14482 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14483 Pixels::from(start)..Pixels::from(end)
14484 } else {
14485 let start_x =
14486 display_map.x_for_display_point(range.start, &text_layout_details);
14487 let end_x =
14488 display_map.x_for_display_point(range.end, &text_layout_details);
14489 start_x.min(end_x)..start_x.max(end_x)
14490 };
14491
14492 let mut maybe_new_selection = None;
14493 let direction = if above { -1 } else { 1 };
14494
14495 while row != end_row {
14496 if skip_soft_wrap {
14497 row = display_map
14498 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14499 .row();
14500 } else if above {
14501 row.0 -= 1;
14502 } else {
14503 row.0 += 1;
14504 }
14505
14506 if let Some(new_selection) = self.selections.build_columnar_selection(
14507 &display_map,
14508 row,
14509 &positions,
14510 selection.reversed,
14511 &text_layout_details,
14512 ) {
14513 maybe_new_selection = Some(new_selection);
14514 break;
14515 }
14516 }
14517
14518 if let Some(new_selection) = maybe_new_selection {
14519 group.stack.push(new_selection.id);
14520 if above {
14521 final_selections.push(new_selection);
14522 final_selections.push(selection);
14523 } else {
14524 final_selections.push(selection);
14525 final_selections.push(new_selection);
14526 }
14527 } else {
14528 final_selections.push(selection);
14529 }
14530 } else {
14531 group.stack.pop();
14532 }
14533 } else {
14534 final_selections.push(selection);
14535 }
14536 }
14537
14538 self.change_selections(Default::default(), window, cx, |s| {
14539 s.select(final_selections);
14540 });
14541
14542 let final_selection_ids: HashSet<_> = self
14543 .selections
14544 .all::<Point>(&display_map)
14545 .iter()
14546 .map(|s| s.id)
14547 .collect();
14548 state.groups.retain_mut(|group| {
14549 // selections might get merged above so we remove invalid items from stacks
14550 group.stack.retain(|id| final_selection_ids.contains(id));
14551
14552 // single selection in stack can be treated as initial state
14553 group.stack.len() > 1
14554 });
14555
14556 if !state.groups.is_empty() {
14557 self.add_selections_state = Some(state);
14558 }
14559 }
14560
14561 fn select_match_ranges(
14562 &mut self,
14563 range: Range<usize>,
14564 reversed: bool,
14565 replace_newest: bool,
14566 auto_scroll: Option<Autoscroll>,
14567 window: &mut Window,
14568 cx: &mut Context<Editor>,
14569 ) {
14570 self.unfold_ranges(
14571 std::slice::from_ref(&range),
14572 false,
14573 auto_scroll.is_some(),
14574 cx,
14575 );
14576 let effects = if let Some(scroll) = auto_scroll {
14577 SelectionEffects::scroll(scroll)
14578 } else {
14579 SelectionEffects::no_scroll()
14580 };
14581 self.change_selections(effects, window, cx, |s| {
14582 if replace_newest {
14583 s.delete(s.newest_anchor().id);
14584 }
14585 if reversed {
14586 s.insert_range(range.end..range.start);
14587 } else {
14588 s.insert_range(range);
14589 }
14590 });
14591 }
14592
14593 pub fn select_next_match_internal(
14594 &mut self,
14595 display_map: &DisplaySnapshot,
14596 replace_newest: bool,
14597 autoscroll: Option<Autoscroll>,
14598 window: &mut Window,
14599 cx: &mut Context<Self>,
14600 ) -> Result<()> {
14601 let buffer = display_map.buffer_snapshot();
14602 let mut selections = self.selections.all::<usize>(&display_map);
14603 if let Some(mut select_next_state) = self.select_next_state.take() {
14604 let query = &select_next_state.query;
14605 if !select_next_state.done {
14606 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14607 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14608 let mut next_selected_range = None;
14609
14610 let bytes_after_last_selection =
14611 buffer.bytes_in_range(last_selection.end..buffer.len());
14612 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14613 let query_matches = query
14614 .stream_find_iter(bytes_after_last_selection)
14615 .map(|result| (last_selection.end, result))
14616 .chain(
14617 query
14618 .stream_find_iter(bytes_before_first_selection)
14619 .map(|result| (0, result)),
14620 );
14621
14622 for (start_offset, query_match) in query_matches {
14623 let query_match = query_match.unwrap(); // can only fail due to I/O
14624 let offset_range =
14625 start_offset + query_match.start()..start_offset + query_match.end();
14626
14627 if !select_next_state.wordwise
14628 || (!buffer.is_inside_word(offset_range.start, None)
14629 && !buffer.is_inside_word(offset_range.end, None))
14630 {
14631 let idx = selections
14632 .partition_point(|selection| selection.end <= offset_range.start);
14633 let overlaps = selections
14634 .get(idx)
14635 .map_or(false, |selection| selection.start < offset_range.end);
14636
14637 if !overlaps {
14638 next_selected_range = Some(offset_range);
14639 break;
14640 }
14641 }
14642 }
14643
14644 if let Some(next_selected_range) = next_selected_range {
14645 self.select_match_ranges(
14646 next_selected_range,
14647 last_selection.reversed,
14648 replace_newest,
14649 autoscroll,
14650 window,
14651 cx,
14652 );
14653 } else {
14654 select_next_state.done = true;
14655 }
14656 }
14657
14658 self.select_next_state = Some(select_next_state);
14659 } else {
14660 let mut only_carets = true;
14661 let mut same_text_selected = true;
14662 let mut selected_text = None;
14663
14664 let mut selections_iter = selections.iter().peekable();
14665 while let Some(selection) = selections_iter.next() {
14666 if selection.start != selection.end {
14667 only_carets = false;
14668 }
14669
14670 if same_text_selected {
14671 if selected_text.is_none() {
14672 selected_text =
14673 Some(buffer.text_for_range(selection.range()).collect::<String>());
14674 }
14675
14676 if let Some(next_selection) = selections_iter.peek() {
14677 if next_selection.range().len() == selection.range().len() {
14678 let next_selected_text = buffer
14679 .text_for_range(next_selection.range())
14680 .collect::<String>();
14681 if Some(next_selected_text) != selected_text {
14682 same_text_selected = false;
14683 selected_text = None;
14684 }
14685 } else {
14686 same_text_selected = false;
14687 selected_text = None;
14688 }
14689 }
14690 }
14691 }
14692
14693 if only_carets {
14694 for selection in &mut selections {
14695 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14696 selection.start = word_range.start;
14697 selection.end = word_range.end;
14698 selection.goal = SelectionGoal::None;
14699 selection.reversed = false;
14700 self.select_match_ranges(
14701 selection.start..selection.end,
14702 selection.reversed,
14703 replace_newest,
14704 autoscroll,
14705 window,
14706 cx,
14707 );
14708 }
14709
14710 if selections.len() == 1 {
14711 let selection = selections
14712 .last()
14713 .expect("ensured that there's only one selection");
14714 let query = buffer
14715 .text_for_range(selection.start..selection.end)
14716 .collect::<String>();
14717 let is_empty = query.is_empty();
14718 let select_state = SelectNextState {
14719 query: self.build_query(&[query], cx)?,
14720 wordwise: true,
14721 done: is_empty,
14722 };
14723 self.select_next_state = Some(select_state);
14724 } else {
14725 self.select_next_state = None;
14726 }
14727 } else if let Some(selected_text) = selected_text {
14728 self.select_next_state = Some(SelectNextState {
14729 query: self.build_query(&[selected_text], cx)?,
14730 wordwise: false,
14731 done: false,
14732 });
14733 self.select_next_match_internal(
14734 display_map,
14735 replace_newest,
14736 autoscroll,
14737 window,
14738 cx,
14739 )?;
14740 }
14741 }
14742 Ok(())
14743 }
14744
14745 pub fn select_all_matches(
14746 &mut self,
14747 _action: &SelectAllMatches,
14748 window: &mut Window,
14749 cx: &mut Context<Self>,
14750 ) -> Result<()> {
14751 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14752
14753 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14754
14755 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14756 let Some(select_next_state) = self.select_next_state.as_mut() else {
14757 return Ok(());
14758 };
14759 if select_next_state.done {
14760 return Ok(());
14761 }
14762
14763 let mut new_selections = Vec::new();
14764
14765 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14766 let buffer = display_map.buffer_snapshot();
14767 let query_matches = select_next_state
14768 .query
14769 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14770
14771 for query_match in query_matches.into_iter() {
14772 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14773 let offset_range = if reversed {
14774 query_match.end()..query_match.start()
14775 } else {
14776 query_match.start()..query_match.end()
14777 };
14778
14779 if !select_next_state.wordwise
14780 || (!buffer.is_inside_word(offset_range.start, None)
14781 && !buffer.is_inside_word(offset_range.end, None))
14782 {
14783 new_selections.push(offset_range.start..offset_range.end);
14784 }
14785 }
14786
14787 select_next_state.done = true;
14788
14789 if new_selections.is_empty() {
14790 log::error!("bug: new_selections is empty in select_all_matches");
14791 return Ok(());
14792 }
14793
14794 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14795 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14796 selections.select_ranges(new_selections)
14797 });
14798
14799 Ok(())
14800 }
14801
14802 pub fn select_next(
14803 &mut self,
14804 action: &SelectNext,
14805 window: &mut Window,
14806 cx: &mut Context<Self>,
14807 ) -> Result<()> {
14808 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14809 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14810 self.select_next_match_internal(
14811 &display_map,
14812 action.replace_newest,
14813 Some(Autoscroll::newest()),
14814 window,
14815 cx,
14816 )?;
14817 Ok(())
14818 }
14819
14820 pub fn select_previous(
14821 &mut self,
14822 action: &SelectPrevious,
14823 window: &mut Window,
14824 cx: &mut Context<Self>,
14825 ) -> Result<()> {
14826 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14827 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14828 let buffer = display_map.buffer_snapshot();
14829 let mut selections = self.selections.all::<usize>(&display_map);
14830 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14831 let query = &select_prev_state.query;
14832 if !select_prev_state.done {
14833 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14834 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14835 let mut next_selected_range = None;
14836 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14837 let bytes_before_last_selection =
14838 buffer.reversed_bytes_in_range(0..last_selection.start);
14839 let bytes_after_first_selection =
14840 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14841 let query_matches = query
14842 .stream_find_iter(bytes_before_last_selection)
14843 .map(|result| (last_selection.start, result))
14844 .chain(
14845 query
14846 .stream_find_iter(bytes_after_first_selection)
14847 .map(|result| (buffer.len(), result)),
14848 );
14849 for (end_offset, query_match) in query_matches {
14850 let query_match = query_match.unwrap(); // can only fail due to I/O
14851 let offset_range =
14852 end_offset - query_match.end()..end_offset - query_match.start();
14853
14854 if !select_prev_state.wordwise
14855 || (!buffer.is_inside_word(offset_range.start, None)
14856 && !buffer.is_inside_word(offset_range.end, None))
14857 {
14858 next_selected_range = Some(offset_range);
14859 break;
14860 }
14861 }
14862
14863 if let Some(next_selected_range) = next_selected_range {
14864 self.select_match_ranges(
14865 next_selected_range,
14866 last_selection.reversed,
14867 action.replace_newest,
14868 Some(Autoscroll::newest()),
14869 window,
14870 cx,
14871 );
14872 } else {
14873 select_prev_state.done = true;
14874 }
14875 }
14876
14877 self.select_prev_state = Some(select_prev_state);
14878 } else {
14879 let mut only_carets = true;
14880 let mut same_text_selected = true;
14881 let mut selected_text = None;
14882
14883 let mut selections_iter = selections.iter().peekable();
14884 while let Some(selection) = selections_iter.next() {
14885 if selection.start != selection.end {
14886 only_carets = false;
14887 }
14888
14889 if same_text_selected {
14890 if selected_text.is_none() {
14891 selected_text =
14892 Some(buffer.text_for_range(selection.range()).collect::<String>());
14893 }
14894
14895 if let Some(next_selection) = selections_iter.peek() {
14896 if next_selection.range().len() == selection.range().len() {
14897 let next_selected_text = buffer
14898 .text_for_range(next_selection.range())
14899 .collect::<String>();
14900 if Some(next_selected_text) != selected_text {
14901 same_text_selected = false;
14902 selected_text = None;
14903 }
14904 } else {
14905 same_text_selected = false;
14906 selected_text = None;
14907 }
14908 }
14909 }
14910 }
14911
14912 if only_carets {
14913 for selection in &mut selections {
14914 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14915 selection.start = word_range.start;
14916 selection.end = word_range.end;
14917 selection.goal = SelectionGoal::None;
14918 selection.reversed = false;
14919 self.select_match_ranges(
14920 selection.start..selection.end,
14921 selection.reversed,
14922 action.replace_newest,
14923 Some(Autoscroll::newest()),
14924 window,
14925 cx,
14926 );
14927 }
14928 if selections.len() == 1 {
14929 let selection = selections
14930 .last()
14931 .expect("ensured that there's only one selection");
14932 let query = buffer
14933 .text_for_range(selection.start..selection.end)
14934 .collect::<String>();
14935 let is_empty = query.is_empty();
14936 let select_state = SelectNextState {
14937 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
14938 wordwise: true,
14939 done: is_empty,
14940 };
14941 self.select_prev_state = Some(select_state);
14942 } else {
14943 self.select_prev_state = None;
14944 }
14945 } else if let Some(selected_text) = selected_text {
14946 self.select_prev_state = Some(SelectNextState {
14947 query: self
14948 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
14949 wordwise: false,
14950 done: false,
14951 });
14952 self.select_previous(action, window, cx)?;
14953 }
14954 }
14955 Ok(())
14956 }
14957
14958 /// Builds an `AhoCorasick` automaton from the provided patterns, while
14959 /// setting the case sensitivity based on the global
14960 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
14961 /// editor's settings.
14962 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
14963 where
14964 I: IntoIterator<Item = P>,
14965 P: AsRef<[u8]>,
14966 {
14967 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
14968 || EditorSettings::get_global(cx).search.case_sensitive,
14969 |value| value,
14970 );
14971
14972 let mut builder = AhoCorasickBuilder::new();
14973 builder.ascii_case_insensitive(!case_sensitive);
14974 builder.build(patterns)
14975 }
14976
14977 pub fn find_next_match(
14978 &mut self,
14979 _: &FindNextMatch,
14980 window: &mut Window,
14981 cx: &mut Context<Self>,
14982 ) -> Result<()> {
14983 let selections = self.selections.disjoint_anchors_arc();
14984 match selections.first() {
14985 Some(first) if selections.len() >= 2 => {
14986 self.change_selections(Default::default(), window, cx, |s| {
14987 s.select_ranges([first.range()]);
14988 });
14989 }
14990 _ => self.select_next(
14991 &SelectNext {
14992 replace_newest: true,
14993 },
14994 window,
14995 cx,
14996 )?,
14997 }
14998 Ok(())
14999 }
15000
15001 pub fn find_previous_match(
15002 &mut self,
15003 _: &FindPreviousMatch,
15004 window: &mut Window,
15005 cx: &mut Context<Self>,
15006 ) -> Result<()> {
15007 let selections = self.selections.disjoint_anchors_arc();
15008 match selections.last() {
15009 Some(last) if selections.len() >= 2 => {
15010 self.change_selections(Default::default(), window, cx, |s| {
15011 s.select_ranges([last.range()]);
15012 });
15013 }
15014 _ => self.select_previous(
15015 &SelectPrevious {
15016 replace_newest: true,
15017 },
15018 window,
15019 cx,
15020 )?,
15021 }
15022 Ok(())
15023 }
15024
15025 pub fn toggle_comments(
15026 &mut self,
15027 action: &ToggleComments,
15028 window: &mut Window,
15029 cx: &mut Context<Self>,
15030 ) {
15031 if self.read_only(cx) {
15032 return;
15033 }
15034 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15035 let text_layout_details = &self.text_layout_details(window);
15036 self.transact(window, cx, |this, window, cx| {
15037 let mut selections = this
15038 .selections
15039 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15040 let mut edits = Vec::new();
15041 let mut selection_edit_ranges = Vec::new();
15042 let mut last_toggled_row = None;
15043 let snapshot = this.buffer.read(cx).read(cx);
15044 let empty_str: Arc<str> = Arc::default();
15045 let mut suffixes_inserted = Vec::new();
15046 let ignore_indent = action.ignore_indent;
15047
15048 fn comment_prefix_range(
15049 snapshot: &MultiBufferSnapshot,
15050 row: MultiBufferRow,
15051 comment_prefix: &str,
15052 comment_prefix_whitespace: &str,
15053 ignore_indent: bool,
15054 ) -> Range<Point> {
15055 let indent_size = if ignore_indent {
15056 0
15057 } else {
15058 snapshot.indent_size_for_line(row).len
15059 };
15060
15061 let start = Point::new(row.0, indent_size);
15062
15063 let mut line_bytes = snapshot
15064 .bytes_in_range(start..snapshot.max_point())
15065 .flatten()
15066 .copied();
15067
15068 // If this line currently begins with the line comment prefix, then record
15069 // the range containing the prefix.
15070 if line_bytes
15071 .by_ref()
15072 .take(comment_prefix.len())
15073 .eq(comment_prefix.bytes())
15074 {
15075 // Include any whitespace that matches the comment prefix.
15076 let matching_whitespace_len = line_bytes
15077 .zip(comment_prefix_whitespace.bytes())
15078 .take_while(|(a, b)| a == b)
15079 .count() as u32;
15080 let end = Point::new(
15081 start.row,
15082 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15083 );
15084 start..end
15085 } else {
15086 start..start
15087 }
15088 }
15089
15090 fn comment_suffix_range(
15091 snapshot: &MultiBufferSnapshot,
15092 row: MultiBufferRow,
15093 comment_suffix: &str,
15094 comment_suffix_has_leading_space: bool,
15095 ) -> Range<Point> {
15096 let end = Point::new(row.0, snapshot.line_len(row));
15097 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15098
15099 let mut line_end_bytes = snapshot
15100 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15101 .flatten()
15102 .copied();
15103
15104 let leading_space_len = if suffix_start_column > 0
15105 && line_end_bytes.next() == Some(b' ')
15106 && comment_suffix_has_leading_space
15107 {
15108 1
15109 } else {
15110 0
15111 };
15112
15113 // If this line currently begins with the line comment prefix, then record
15114 // the range containing the prefix.
15115 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15116 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15117 start..end
15118 } else {
15119 end..end
15120 }
15121 }
15122
15123 // TODO: Handle selections that cross excerpts
15124 for selection in &mut selections {
15125 let start_column = snapshot
15126 .indent_size_for_line(MultiBufferRow(selection.start.row))
15127 .len;
15128 let language = if let Some(language) =
15129 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15130 {
15131 language
15132 } else {
15133 continue;
15134 };
15135
15136 selection_edit_ranges.clear();
15137
15138 // If multiple selections contain a given row, avoid processing that
15139 // row more than once.
15140 let mut start_row = MultiBufferRow(selection.start.row);
15141 if last_toggled_row == Some(start_row) {
15142 start_row = start_row.next_row();
15143 }
15144 let end_row =
15145 if selection.end.row > selection.start.row && selection.end.column == 0 {
15146 MultiBufferRow(selection.end.row - 1)
15147 } else {
15148 MultiBufferRow(selection.end.row)
15149 };
15150 last_toggled_row = Some(end_row);
15151
15152 if start_row > end_row {
15153 continue;
15154 }
15155
15156 // If the language has line comments, toggle those.
15157 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15158
15159 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15160 if ignore_indent {
15161 full_comment_prefixes = full_comment_prefixes
15162 .into_iter()
15163 .map(|s| Arc::from(s.trim_end()))
15164 .collect();
15165 }
15166
15167 if !full_comment_prefixes.is_empty() {
15168 let first_prefix = full_comment_prefixes
15169 .first()
15170 .expect("prefixes is non-empty");
15171 let prefix_trimmed_lengths = full_comment_prefixes
15172 .iter()
15173 .map(|p| p.trim_end_matches(' ').len())
15174 .collect::<SmallVec<[usize; 4]>>();
15175
15176 let mut all_selection_lines_are_comments = true;
15177
15178 for row in start_row.0..=end_row.0 {
15179 let row = MultiBufferRow(row);
15180 if start_row < end_row && snapshot.is_line_blank(row) {
15181 continue;
15182 }
15183
15184 let prefix_range = full_comment_prefixes
15185 .iter()
15186 .zip(prefix_trimmed_lengths.iter().copied())
15187 .map(|(prefix, trimmed_prefix_len)| {
15188 comment_prefix_range(
15189 snapshot.deref(),
15190 row,
15191 &prefix[..trimmed_prefix_len],
15192 &prefix[trimmed_prefix_len..],
15193 ignore_indent,
15194 )
15195 })
15196 .max_by_key(|range| range.end.column - range.start.column)
15197 .expect("prefixes is non-empty");
15198
15199 if prefix_range.is_empty() {
15200 all_selection_lines_are_comments = false;
15201 }
15202
15203 selection_edit_ranges.push(prefix_range);
15204 }
15205
15206 if all_selection_lines_are_comments {
15207 edits.extend(
15208 selection_edit_ranges
15209 .iter()
15210 .cloned()
15211 .map(|range| (range, empty_str.clone())),
15212 );
15213 } else {
15214 let min_column = selection_edit_ranges
15215 .iter()
15216 .map(|range| range.start.column)
15217 .min()
15218 .unwrap_or(0);
15219 edits.extend(selection_edit_ranges.iter().map(|range| {
15220 let position = Point::new(range.start.row, min_column);
15221 (position..position, first_prefix.clone())
15222 }));
15223 }
15224 } else if let Some(BlockCommentConfig {
15225 start: full_comment_prefix,
15226 end: comment_suffix,
15227 ..
15228 }) = language.block_comment()
15229 {
15230 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15231 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15232 let prefix_range = comment_prefix_range(
15233 snapshot.deref(),
15234 start_row,
15235 comment_prefix,
15236 comment_prefix_whitespace,
15237 ignore_indent,
15238 );
15239 let suffix_range = comment_suffix_range(
15240 snapshot.deref(),
15241 end_row,
15242 comment_suffix.trim_start_matches(' '),
15243 comment_suffix.starts_with(' '),
15244 );
15245
15246 if prefix_range.is_empty() || suffix_range.is_empty() {
15247 edits.push((
15248 prefix_range.start..prefix_range.start,
15249 full_comment_prefix.clone(),
15250 ));
15251 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15252 suffixes_inserted.push((end_row, comment_suffix.len()));
15253 } else {
15254 edits.push((prefix_range, empty_str.clone()));
15255 edits.push((suffix_range, empty_str.clone()));
15256 }
15257 } else {
15258 continue;
15259 }
15260 }
15261
15262 drop(snapshot);
15263 this.buffer.update(cx, |buffer, cx| {
15264 buffer.edit(edits, None, cx);
15265 });
15266
15267 // Adjust selections so that they end before any comment suffixes that
15268 // were inserted.
15269 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15270 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15271 let snapshot = this.buffer.read(cx).read(cx);
15272 for selection in &mut selections {
15273 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15274 match row.cmp(&MultiBufferRow(selection.end.row)) {
15275 Ordering::Less => {
15276 suffixes_inserted.next();
15277 continue;
15278 }
15279 Ordering::Greater => break,
15280 Ordering::Equal => {
15281 if selection.end.column == snapshot.line_len(row) {
15282 if selection.is_empty() {
15283 selection.start.column -= suffix_len as u32;
15284 }
15285 selection.end.column -= suffix_len as u32;
15286 }
15287 break;
15288 }
15289 }
15290 }
15291 }
15292
15293 drop(snapshot);
15294 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15295
15296 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15297 let selections_on_single_row = selections.windows(2).all(|selections| {
15298 selections[0].start.row == selections[1].start.row
15299 && selections[0].end.row == selections[1].end.row
15300 && selections[0].start.row == selections[0].end.row
15301 });
15302 let selections_selecting = selections
15303 .iter()
15304 .any(|selection| selection.start != selection.end);
15305 let advance_downwards = action.advance_downwards
15306 && selections_on_single_row
15307 && !selections_selecting
15308 && !matches!(this.mode, EditorMode::SingleLine);
15309
15310 if advance_downwards {
15311 let snapshot = this.buffer.read(cx).snapshot(cx);
15312
15313 this.change_selections(Default::default(), window, cx, |s| {
15314 s.move_cursors_with(|display_snapshot, display_point, _| {
15315 let mut point = display_point.to_point(display_snapshot);
15316 point.row += 1;
15317 point = snapshot.clip_point(point, Bias::Left);
15318 let display_point = point.to_display_point(display_snapshot);
15319 let goal = SelectionGoal::HorizontalPosition(
15320 display_snapshot
15321 .x_for_display_point(display_point, text_layout_details)
15322 .into(),
15323 );
15324 (display_point, goal)
15325 })
15326 });
15327 }
15328 });
15329 }
15330
15331 pub fn select_enclosing_symbol(
15332 &mut self,
15333 _: &SelectEnclosingSymbol,
15334 window: &mut Window,
15335 cx: &mut Context<Self>,
15336 ) {
15337 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15338
15339 let buffer = self.buffer.read(cx).snapshot(cx);
15340 let old_selections = self
15341 .selections
15342 .all::<usize>(&self.display_snapshot(cx))
15343 .into_boxed_slice();
15344
15345 fn update_selection(
15346 selection: &Selection<usize>,
15347 buffer_snap: &MultiBufferSnapshot,
15348 ) -> Option<Selection<usize>> {
15349 let cursor = selection.head();
15350 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15351 for symbol in symbols.iter().rev() {
15352 let start = symbol.range.start.to_offset(buffer_snap);
15353 let end = symbol.range.end.to_offset(buffer_snap);
15354 let new_range = start..end;
15355 if start < selection.start || end > selection.end {
15356 return Some(Selection {
15357 id: selection.id,
15358 start: new_range.start,
15359 end: new_range.end,
15360 goal: SelectionGoal::None,
15361 reversed: selection.reversed,
15362 });
15363 }
15364 }
15365 None
15366 }
15367
15368 let mut selected_larger_symbol = false;
15369 let new_selections = old_selections
15370 .iter()
15371 .map(|selection| match update_selection(selection, &buffer) {
15372 Some(new_selection) => {
15373 if new_selection.range() != selection.range() {
15374 selected_larger_symbol = true;
15375 }
15376 new_selection
15377 }
15378 None => selection.clone(),
15379 })
15380 .collect::<Vec<_>>();
15381
15382 if selected_larger_symbol {
15383 self.change_selections(Default::default(), window, cx, |s| {
15384 s.select(new_selections);
15385 });
15386 }
15387 }
15388
15389 pub fn select_larger_syntax_node(
15390 &mut self,
15391 _: &SelectLargerSyntaxNode,
15392 window: &mut Window,
15393 cx: &mut Context<Self>,
15394 ) {
15395 let Some(visible_row_count) = self.visible_row_count() else {
15396 return;
15397 };
15398 let old_selections: Box<[_]> = self
15399 .selections
15400 .all::<usize>(&self.display_snapshot(cx))
15401 .into();
15402 if old_selections.is_empty() {
15403 return;
15404 }
15405
15406 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15407
15408 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15409 let buffer = self.buffer.read(cx).snapshot(cx);
15410
15411 let mut selected_larger_node = false;
15412 let mut new_selections = old_selections
15413 .iter()
15414 .map(|selection| {
15415 let old_range = selection.start..selection.end;
15416
15417 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15418 // manually select word at selection
15419 if ["string_content", "inline"].contains(&node.kind()) {
15420 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15421 // ignore if word is already selected
15422 if !word_range.is_empty() && old_range != word_range {
15423 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15424 // only select word if start and end point belongs to same word
15425 if word_range == last_word_range {
15426 selected_larger_node = true;
15427 return Selection {
15428 id: selection.id,
15429 start: word_range.start,
15430 end: word_range.end,
15431 goal: SelectionGoal::None,
15432 reversed: selection.reversed,
15433 };
15434 }
15435 }
15436 }
15437 }
15438
15439 let mut new_range = old_range.clone();
15440 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15441 new_range = range;
15442 if !node.is_named() {
15443 continue;
15444 }
15445 if !display_map.intersects_fold(new_range.start)
15446 && !display_map.intersects_fold(new_range.end)
15447 {
15448 break;
15449 }
15450 }
15451
15452 selected_larger_node |= new_range != old_range;
15453 Selection {
15454 id: selection.id,
15455 start: new_range.start,
15456 end: new_range.end,
15457 goal: SelectionGoal::None,
15458 reversed: selection.reversed,
15459 }
15460 })
15461 .collect::<Vec<_>>();
15462
15463 if !selected_larger_node {
15464 return; // don't put this call in the history
15465 }
15466
15467 // scroll based on transformation done to the last selection created by the user
15468 let (last_old, last_new) = old_selections
15469 .last()
15470 .zip(new_selections.last().cloned())
15471 .expect("old_selections isn't empty");
15472
15473 // revert selection
15474 let is_selection_reversed = {
15475 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15476 new_selections.last_mut().expect("checked above").reversed =
15477 should_newest_selection_be_reversed;
15478 should_newest_selection_be_reversed
15479 };
15480
15481 if selected_larger_node {
15482 self.select_syntax_node_history.disable_clearing = true;
15483 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15484 s.select(new_selections.clone());
15485 });
15486 self.select_syntax_node_history.disable_clearing = false;
15487 }
15488
15489 let start_row = last_new.start.to_display_point(&display_map).row().0;
15490 let end_row = last_new.end.to_display_point(&display_map).row().0;
15491 let selection_height = end_row - start_row + 1;
15492 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15493
15494 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15495 let scroll_behavior = if fits_on_the_screen {
15496 self.request_autoscroll(Autoscroll::fit(), cx);
15497 SelectSyntaxNodeScrollBehavior::FitSelection
15498 } else if is_selection_reversed {
15499 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15500 SelectSyntaxNodeScrollBehavior::CursorTop
15501 } else {
15502 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15503 SelectSyntaxNodeScrollBehavior::CursorBottom
15504 };
15505
15506 self.select_syntax_node_history.push((
15507 old_selections,
15508 scroll_behavior,
15509 is_selection_reversed,
15510 ));
15511 }
15512
15513 pub fn select_smaller_syntax_node(
15514 &mut self,
15515 _: &SelectSmallerSyntaxNode,
15516 window: &mut Window,
15517 cx: &mut Context<Self>,
15518 ) {
15519 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15520
15521 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15522 self.select_syntax_node_history.pop()
15523 {
15524 if let Some(selection) = selections.last_mut() {
15525 selection.reversed = is_selection_reversed;
15526 }
15527
15528 self.select_syntax_node_history.disable_clearing = true;
15529 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15530 s.select(selections.to_vec());
15531 });
15532 self.select_syntax_node_history.disable_clearing = false;
15533
15534 match scroll_behavior {
15535 SelectSyntaxNodeScrollBehavior::CursorTop => {
15536 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15537 }
15538 SelectSyntaxNodeScrollBehavior::FitSelection => {
15539 self.request_autoscroll(Autoscroll::fit(), cx);
15540 }
15541 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15542 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15543 }
15544 }
15545 }
15546 }
15547
15548 pub fn unwrap_syntax_node(
15549 &mut self,
15550 _: &UnwrapSyntaxNode,
15551 window: &mut Window,
15552 cx: &mut Context<Self>,
15553 ) {
15554 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15555
15556 let buffer = self.buffer.read(cx).snapshot(cx);
15557 let selections = self
15558 .selections
15559 .all::<usize>(&self.display_snapshot(cx))
15560 .into_iter()
15561 // subtracting the offset requires sorting
15562 .sorted_by_key(|i| i.start);
15563
15564 let full_edits = selections
15565 .into_iter()
15566 .filter_map(|selection| {
15567 let child = if selection.is_empty()
15568 && let Some((_, ancestor_range)) =
15569 buffer.syntax_ancestor(selection.start..selection.end)
15570 {
15571 ancestor_range
15572 } else {
15573 selection.range()
15574 };
15575
15576 let mut parent = child.clone();
15577 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15578 parent = ancestor_range;
15579 if parent.start < child.start || parent.end > child.end {
15580 break;
15581 }
15582 }
15583
15584 if parent == child {
15585 return None;
15586 }
15587 let text = buffer.text_for_range(child).collect::<String>();
15588 Some((selection.id, parent, text))
15589 })
15590 .collect::<Vec<_>>();
15591 if full_edits.is_empty() {
15592 return;
15593 }
15594
15595 self.transact(window, cx, |this, window, cx| {
15596 this.buffer.update(cx, |buffer, cx| {
15597 buffer.edit(
15598 full_edits
15599 .iter()
15600 .map(|(_, p, t)| (p.clone(), t.clone()))
15601 .collect::<Vec<_>>(),
15602 None,
15603 cx,
15604 );
15605 });
15606 this.change_selections(Default::default(), window, cx, |s| {
15607 let mut offset = 0;
15608 let mut selections = vec![];
15609 for (id, parent, text) in full_edits {
15610 let start = parent.start - offset;
15611 offset += parent.len() - text.len();
15612 selections.push(Selection {
15613 id,
15614 start,
15615 end: start + text.len(),
15616 reversed: false,
15617 goal: Default::default(),
15618 });
15619 }
15620 s.select(selections);
15621 });
15622 });
15623 }
15624
15625 pub fn select_next_syntax_node(
15626 &mut self,
15627 _: &SelectNextSyntaxNode,
15628 window: &mut Window,
15629 cx: &mut Context<Self>,
15630 ) {
15631 let old_selections: Box<[_]> = self
15632 .selections
15633 .all::<usize>(&self.display_snapshot(cx))
15634 .into();
15635 if old_selections.is_empty() {
15636 return;
15637 }
15638
15639 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15640
15641 let buffer = self.buffer.read(cx).snapshot(cx);
15642 let mut selected_sibling = false;
15643
15644 let new_selections = old_selections
15645 .iter()
15646 .map(|selection| {
15647 let old_range = selection.start..selection.end;
15648
15649 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15650 let new_range = node.byte_range();
15651 selected_sibling = true;
15652 Selection {
15653 id: selection.id,
15654 start: new_range.start,
15655 end: new_range.end,
15656 goal: SelectionGoal::None,
15657 reversed: selection.reversed,
15658 }
15659 } else {
15660 selection.clone()
15661 }
15662 })
15663 .collect::<Vec<_>>();
15664
15665 if selected_sibling {
15666 self.change_selections(
15667 SelectionEffects::scroll(Autoscroll::fit()),
15668 window,
15669 cx,
15670 |s| {
15671 s.select(new_selections);
15672 },
15673 );
15674 }
15675 }
15676
15677 pub fn select_prev_syntax_node(
15678 &mut self,
15679 _: &SelectPreviousSyntaxNode,
15680 window: &mut Window,
15681 cx: &mut Context<Self>,
15682 ) {
15683 let old_selections: Box<[_]> = self
15684 .selections
15685 .all::<usize>(&self.display_snapshot(cx))
15686 .into();
15687 if old_selections.is_empty() {
15688 return;
15689 }
15690
15691 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15692
15693 let buffer = self.buffer.read(cx).snapshot(cx);
15694 let mut selected_sibling = false;
15695
15696 let new_selections = old_selections
15697 .iter()
15698 .map(|selection| {
15699 let old_range = selection.start..selection.end;
15700
15701 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15702 let new_range = node.byte_range();
15703 selected_sibling = true;
15704 Selection {
15705 id: selection.id,
15706 start: new_range.start,
15707 end: new_range.end,
15708 goal: SelectionGoal::None,
15709 reversed: selection.reversed,
15710 }
15711 } else {
15712 selection.clone()
15713 }
15714 })
15715 .collect::<Vec<_>>();
15716
15717 if selected_sibling {
15718 self.change_selections(
15719 SelectionEffects::scroll(Autoscroll::fit()),
15720 window,
15721 cx,
15722 |s| {
15723 s.select(new_selections);
15724 },
15725 );
15726 }
15727 }
15728
15729 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15730 if !EditorSettings::get_global(cx).gutter.runnables {
15731 self.clear_tasks();
15732 return Task::ready(());
15733 }
15734 let project = self.project().map(Entity::downgrade);
15735 let task_sources = self.lsp_task_sources(cx);
15736 let multi_buffer = self.buffer.downgrade();
15737 cx.spawn_in(window, async move |editor, cx| {
15738 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15739 let Some(project) = project.and_then(|p| p.upgrade()) else {
15740 return;
15741 };
15742 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15743 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15744 }) else {
15745 return;
15746 };
15747
15748 let hide_runnables = project
15749 .update(cx, |project, _| project.is_via_collab())
15750 .unwrap_or(true);
15751 if hide_runnables {
15752 return;
15753 }
15754 let new_rows =
15755 cx.background_spawn({
15756 let snapshot = display_snapshot.clone();
15757 async move {
15758 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15759 }
15760 })
15761 .await;
15762 let Ok(lsp_tasks) =
15763 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15764 else {
15765 return;
15766 };
15767 let lsp_tasks = lsp_tasks.await;
15768
15769 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15770 lsp_tasks
15771 .into_iter()
15772 .flat_map(|(kind, tasks)| {
15773 tasks.into_iter().filter_map(move |(location, task)| {
15774 Some((kind.clone(), location?, task))
15775 })
15776 })
15777 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15778 let buffer = location.target.buffer;
15779 let buffer_snapshot = buffer.read(cx).snapshot();
15780 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15781 |(excerpt_id, snapshot, _)| {
15782 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15783 display_snapshot
15784 .buffer_snapshot()
15785 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15786 } else {
15787 None
15788 }
15789 },
15790 );
15791 if let Some(offset) = offset {
15792 let task_buffer_range =
15793 location.target.range.to_point(&buffer_snapshot);
15794 let context_buffer_range =
15795 task_buffer_range.to_offset(&buffer_snapshot);
15796 let context_range = BufferOffset(context_buffer_range.start)
15797 ..BufferOffset(context_buffer_range.end);
15798
15799 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15800 .or_insert_with(|| RunnableTasks {
15801 templates: Vec::new(),
15802 offset,
15803 column: task_buffer_range.start.column,
15804 extra_variables: HashMap::default(),
15805 context_range,
15806 })
15807 .templates
15808 .push((kind, task.original_task().clone()));
15809 }
15810
15811 acc
15812 })
15813 }) else {
15814 return;
15815 };
15816
15817 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15818 buffer.language_settings(cx).tasks.prefer_lsp
15819 }) else {
15820 return;
15821 };
15822
15823 let rows = Self::runnable_rows(
15824 project,
15825 display_snapshot,
15826 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15827 new_rows,
15828 cx.clone(),
15829 )
15830 .await;
15831 editor
15832 .update(cx, |editor, _| {
15833 editor.clear_tasks();
15834 for (key, mut value) in rows {
15835 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15836 value.templates.extend(lsp_tasks.templates);
15837 }
15838
15839 editor.insert_tasks(key, value);
15840 }
15841 for (key, value) in lsp_tasks_by_rows {
15842 editor.insert_tasks(key, value);
15843 }
15844 })
15845 .ok();
15846 })
15847 }
15848 fn fetch_runnable_ranges(
15849 snapshot: &DisplaySnapshot,
15850 range: Range<Anchor>,
15851 ) -> Vec<language::RunnableRange> {
15852 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15853 }
15854
15855 fn runnable_rows(
15856 project: Entity<Project>,
15857 snapshot: DisplaySnapshot,
15858 prefer_lsp: bool,
15859 runnable_ranges: Vec<RunnableRange>,
15860 cx: AsyncWindowContext,
15861 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15862 cx.spawn(async move |cx| {
15863 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15864 for mut runnable in runnable_ranges {
15865 let Some(tasks) = cx
15866 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15867 .ok()
15868 else {
15869 continue;
15870 };
15871 let mut tasks = tasks.await;
15872
15873 if prefer_lsp {
15874 tasks.retain(|(task_kind, _)| {
15875 !matches!(task_kind, TaskSourceKind::Language { .. })
15876 });
15877 }
15878 if tasks.is_empty() {
15879 continue;
15880 }
15881
15882 let point = runnable
15883 .run_range
15884 .start
15885 .to_point(&snapshot.buffer_snapshot());
15886 let Some(row) = snapshot
15887 .buffer_snapshot()
15888 .buffer_line_for_row(MultiBufferRow(point.row))
15889 .map(|(_, range)| range.start.row)
15890 else {
15891 continue;
15892 };
15893
15894 let context_range =
15895 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15896 runnable_rows.push((
15897 (runnable.buffer_id, row),
15898 RunnableTasks {
15899 templates: tasks,
15900 offset: snapshot
15901 .buffer_snapshot()
15902 .anchor_before(runnable.run_range.start),
15903 context_range,
15904 column: point.column,
15905 extra_variables: runnable.extra_captures,
15906 },
15907 ));
15908 }
15909 runnable_rows
15910 })
15911 }
15912
15913 fn templates_with_tags(
15914 project: &Entity<Project>,
15915 runnable: &mut Runnable,
15916 cx: &mut App,
15917 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15918 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15919 let (worktree_id, file) = project
15920 .buffer_for_id(runnable.buffer, cx)
15921 .and_then(|buffer| buffer.read(cx).file())
15922 .map(|file| (file.worktree_id(cx), file.clone()))
15923 .unzip();
15924
15925 (
15926 project.task_store().read(cx).task_inventory().cloned(),
15927 worktree_id,
15928 file,
15929 )
15930 });
15931
15932 let tags = mem::take(&mut runnable.tags);
15933 let language = runnable.language.clone();
15934 cx.spawn(async move |cx| {
15935 let mut templates_with_tags = Vec::new();
15936 if let Some(inventory) = inventory {
15937 for RunnableTag(tag) in tags {
15938 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15939 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15940 }) else {
15941 return templates_with_tags;
15942 };
15943 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15944 move |(_, template)| {
15945 template.tags.iter().any(|source_tag| source_tag == &tag)
15946 },
15947 ));
15948 }
15949 }
15950 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15951
15952 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15953 // Strongest source wins; if we have worktree tag binding, prefer that to
15954 // global and language bindings;
15955 // if we have a global binding, prefer that to language binding.
15956 let first_mismatch = templates_with_tags
15957 .iter()
15958 .position(|(tag_source, _)| tag_source != leading_tag_source);
15959 if let Some(index) = first_mismatch {
15960 templates_with_tags.truncate(index);
15961 }
15962 }
15963
15964 templates_with_tags
15965 })
15966 }
15967
15968 pub fn move_to_enclosing_bracket(
15969 &mut self,
15970 _: &MoveToEnclosingBracket,
15971 window: &mut Window,
15972 cx: &mut Context<Self>,
15973 ) {
15974 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15975 self.change_selections(Default::default(), window, cx, |s| {
15976 s.move_offsets_with(|snapshot, selection| {
15977 let Some(enclosing_bracket_ranges) =
15978 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15979 else {
15980 return;
15981 };
15982
15983 let mut best_length = usize::MAX;
15984 let mut best_inside = false;
15985 let mut best_in_bracket_range = false;
15986 let mut best_destination = None;
15987 for (open, close) in enclosing_bracket_ranges {
15988 let close = close.to_inclusive();
15989 let length = close.end() - open.start;
15990 let inside = selection.start >= open.end && selection.end <= *close.start();
15991 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15992 || close.contains(&selection.head());
15993
15994 // If best is next to a bracket and current isn't, skip
15995 if !in_bracket_range && best_in_bracket_range {
15996 continue;
15997 }
15998
15999 // Prefer smaller lengths unless best is inside and current isn't
16000 if length > best_length && (best_inside || !inside) {
16001 continue;
16002 }
16003
16004 best_length = length;
16005 best_inside = inside;
16006 best_in_bracket_range = in_bracket_range;
16007 best_destination = Some(
16008 if close.contains(&selection.start) && close.contains(&selection.end) {
16009 if inside { open.end } else { open.start }
16010 } else if inside {
16011 *close.start()
16012 } else {
16013 *close.end()
16014 },
16015 );
16016 }
16017
16018 if let Some(destination) = best_destination {
16019 selection.collapse_to(destination, SelectionGoal::None);
16020 }
16021 })
16022 });
16023 }
16024
16025 pub fn undo_selection(
16026 &mut self,
16027 _: &UndoSelection,
16028 window: &mut Window,
16029 cx: &mut Context<Self>,
16030 ) {
16031 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16032 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16033 self.selection_history.mode = SelectionHistoryMode::Undoing;
16034 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16035 this.end_selection(window, cx);
16036 this.change_selections(
16037 SelectionEffects::scroll(Autoscroll::newest()),
16038 window,
16039 cx,
16040 |s| s.select_anchors(entry.selections.to_vec()),
16041 );
16042 });
16043 self.selection_history.mode = SelectionHistoryMode::Normal;
16044
16045 self.select_next_state = entry.select_next_state;
16046 self.select_prev_state = entry.select_prev_state;
16047 self.add_selections_state = entry.add_selections_state;
16048 }
16049 }
16050
16051 pub fn redo_selection(
16052 &mut self,
16053 _: &RedoSelection,
16054 window: &mut Window,
16055 cx: &mut Context<Self>,
16056 ) {
16057 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16058 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16059 self.selection_history.mode = SelectionHistoryMode::Redoing;
16060 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16061 this.end_selection(window, cx);
16062 this.change_selections(
16063 SelectionEffects::scroll(Autoscroll::newest()),
16064 window,
16065 cx,
16066 |s| s.select_anchors(entry.selections.to_vec()),
16067 );
16068 });
16069 self.selection_history.mode = SelectionHistoryMode::Normal;
16070
16071 self.select_next_state = entry.select_next_state;
16072 self.select_prev_state = entry.select_prev_state;
16073 self.add_selections_state = entry.add_selections_state;
16074 }
16075 }
16076
16077 pub fn expand_excerpts(
16078 &mut self,
16079 action: &ExpandExcerpts,
16080 _: &mut Window,
16081 cx: &mut Context<Self>,
16082 ) {
16083 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16084 }
16085
16086 pub fn expand_excerpts_down(
16087 &mut self,
16088 action: &ExpandExcerptsDown,
16089 _: &mut Window,
16090 cx: &mut Context<Self>,
16091 ) {
16092 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16093 }
16094
16095 pub fn expand_excerpts_up(
16096 &mut self,
16097 action: &ExpandExcerptsUp,
16098 _: &mut Window,
16099 cx: &mut Context<Self>,
16100 ) {
16101 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16102 }
16103
16104 pub fn expand_excerpts_for_direction(
16105 &mut self,
16106 lines: u32,
16107 direction: ExpandExcerptDirection,
16108
16109 cx: &mut Context<Self>,
16110 ) {
16111 let selections = self.selections.disjoint_anchors_arc();
16112
16113 let lines = if lines == 0 {
16114 EditorSettings::get_global(cx).expand_excerpt_lines
16115 } else {
16116 lines
16117 };
16118
16119 self.buffer.update(cx, |buffer, cx| {
16120 let snapshot = buffer.snapshot(cx);
16121 let mut excerpt_ids = selections
16122 .iter()
16123 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16124 .collect::<Vec<_>>();
16125 excerpt_ids.sort();
16126 excerpt_ids.dedup();
16127 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16128 })
16129 }
16130
16131 pub fn expand_excerpt(
16132 &mut self,
16133 excerpt: ExcerptId,
16134 direction: ExpandExcerptDirection,
16135 window: &mut Window,
16136 cx: &mut Context<Self>,
16137 ) {
16138 let current_scroll_position = self.scroll_position(cx);
16139 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16140 let mut scroll = None;
16141
16142 if direction == ExpandExcerptDirection::Down {
16143 let multi_buffer = self.buffer.read(cx);
16144 let snapshot = multi_buffer.snapshot(cx);
16145 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16146 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16147 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16148 {
16149 let buffer_snapshot = buffer.read(cx).snapshot();
16150 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16151 let last_row = buffer_snapshot.max_point().row;
16152 let lines_below = last_row.saturating_sub(excerpt_end_row);
16153 if lines_below >= lines_to_expand {
16154 scroll = Some(
16155 current_scroll_position
16156 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16157 );
16158 }
16159 }
16160 }
16161 if direction == ExpandExcerptDirection::Up
16162 && self
16163 .buffer
16164 .read(cx)
16165 .snapshot(cx)
16166 .excerpt_before(excerpt)
16167 .is_none()
16168 {
16169 scroll = Some(current_scroll_position);
16170 }
16171
16172 self.buffer.update(cx, |buffer, cx| {
16173 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16174 });
16175
16176 if let Some(new_scroll_position) = scroll {
16177 self.set_scroll_position(new_scroll_position, window, cx);
16178 }
16179 }
16180
16181 pub fn go_to_singleton_buffer_point(
16182 &mut self,
16183 point: Point,
16184 window: &mut Window,
16185 cx: &mut Context<Self>,
16186 ) {
16187 self.go_to_singleton_buffer_range(point..point, window, cx);
16188 }
16189
16190 pub fn go_to_singleton_buffer_range(
16191 &mut self,
16192 range: Range<Point>,
16193 window: &mut Window,
16194 cx: &mut Context<Self>,
16195 ) {
16196 let multibuffer = self.buffer().read(cx);
16197 let Some(buffer) = multibuffer.as_singleton() else {
16198 return;
16199 };
16200 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16201 return;
16202 };
16203 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16204 return;
16205 };
16206 self.change_selections(
16207 SelectionEffects::default().nav_history(true),
16208 window,
16209 cx,
16210 |s| s.select_anchor_ranges([start..end]),
16211 );
16212 }
16213
16214 pub fn go_to_diagnostic(
16215 &mut self,
16216 action: &GoToDiagnostic,
16217 window: &mut Window,
16218 cx: &mut Context<Self>,
16219 ) {
16220 if !self.diagnostics_enabled() {
16221 return;
16222 }
16223 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16224 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16225 }
16226
16227 pub fn go_to_prev_diagnostic(
16228 &mut self,
16229 action: &GoToPreviousDiagnostic,
16230 window: &mut Window,
16231 cx: &mut Context<Self>,
16232 ) {
16233 if !self.diagnostics_enabled() {
16234 return;
16235 }
16236 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16237 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16238 }
16239
16240 pub fn go_to_diagnostic_impl(
16241 &mut self,
16242 direction: Direction,
16243 severity: GoToDiagnosticSeverityFilter,
16244 window: &mut Window,
16245 cx: &mut Context<Self>,
16246 ) {
16247 let buffer = self.buffer.read(cx).snapshot(cx);
16248 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16249
16250 let mut active_group_id = None;
16251 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16252 && active_group.active_range.start.to_offset(&buffer) == selection.start
16253 {
16254 active_group_id = Some(active_group.group_id);
16255 }
16256
16257 fn filtered<'a>(
16258 severity: GoToDiagnosticSeverityFilter,
16259 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16260 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16261 diagnostics
16262 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16263 .filter(|entry| entry.range.start != entry.range.end)
16264 .filter(|entry| !entry.diagnostic.is_unnecessary)
16265 }
16266
16267 let before = filtered(
16268 severity,
16269 buffer
16270 .diagnostics_in_range(0..selection.start)
16271 .filter(|entry| entry.range.start <= selection.start),
16272 );
16273 let after = filtered(
16274 severity,
16275 buffer
16276 .diagnostics_in_range(selection.start..buffer.len())
16277 .filter(|entry| entry.range.start >= selection.start),
16278 );
16279
16280 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16281 if direction == Direction::Prev {
16282 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16283 {
16284 for diagnostic in prev_diagnostics.into_iter().rev() {
16285 if diagnostic.range.start != selection.start
16286 || active_group_id
16287 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16288 {
16289 found = Some(diagnostic);
16290 break 'outer;
16291 }
16292 }
16293 }
16294 } else {
16295 for diagnostic in after.chain(before) {
16296 if diagnostic.range.start != selection.start
16297 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16298 {
16299 found = Some(diagnostic);
16300 break;
16301 }
16302 }
16303 }
16304 let Some(next_diagnostic) = found else {
16305 return;
16306 };
16307
16308 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16309 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16310 return;
16311 };
16312 let snapshot = self.snapshot(window, cx);
16313 if snapshot.intersects_fold(next_diagnostic.range.start) {
16314 self.unfold_ranges(
16315 std::slice::from_ref(&next_diagnostic.range),
16316 true,
16317 false,
16318 cx,
16319 );
16320 }
16321 self.change_selections(Default::default(), window, cx, |s| {
16322 s.select_ranges(vec![
16323 next_diagnostic.range.start..next_diagnostic.range.start,
16324 ])
16325 });
16326 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16327 self.refresh_edit_prediction(false, true, window, cx);
16328 }
16329
16330 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16331 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16332 let snapshot = self.snapshot(window, cx);
16333 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16334 self.go_to_hunk_before_or_after_position(
16335 &snapshot,
16336 selection.head(),
16337 Direction::Next,
16338 window,
16339 cx,
16340 );
16341 }
16342
16343 pub fn go_to_hunk_before_or_after_position(
16344 &mut self,
16345 snapshot: &EditorSnapshot,
16346 position: Point,
16347 direction: Direction,
16348 window: &mut Window,
16349 cx: &mut Context<Editor>,
16350 ) {
16351 let row = if direction == Direction::Next {
16352 self.hunk_after_position(snapshot, position)
16353 .map(|hunk| hunk.row_range.start)
16354 } else {
16355 self.hunk_before_position(snapshot, position)
16356 };
16357
16358 if let Some(row) = row {
16359 let destination = Point::new(row.0, 0);
16360 let autoscroll = Autoscroll::center();
16361
16362 self.unfold_ranges(&[destination..destination], false, false, cx);
16363 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16364 s.select_ranges([destination..destination]);
16365 });
16366 }
16367 }
16368
16369 fn hunk_after_position(
16370 &mut self,
16371 snapshot: &EditorSnapshot,
16372 position: Point,
16373 ) -> Option<MultiBufferDiffHunk> {
16374 snapshot
16375 .buffer_snapshot()
16376 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16377 .find(|hunk| hunk.row_range.start.0 > position.row)
16378 .or_else(|| {
16379 snapshot
16380 .buffer_snapshot()
16381 .diff_hunks_in_range(Point::zero()..position)
16382 .find(|hunk| hunk.row_range.end.0 < position.row)
16383 })
16384 }
16385
16386 fn go_to_prev_hunk(
16387 &mut self,
16388 _: &GoToPreviousHunk,
16389 window: &mut Window,
16390 cx: &mut Context<Self>,
16391 ) {
16392 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16393 let snapshot = self.snapshot(window, cx);
16394 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16395 self.go_to_hunk_before_or_after_position(
16396 &snapshot,
16397 selection.head(),
16398 Direction::Prev,
16399 window,
16400 cx,
16401 );
16402 }
16403
16404 fn hunk_before_position(
16405 &mut self,
16406 snapshot: &EditorSnapshot,
16407 position: Point,
16408 ) -> Option<MultiBufferRow> {
16409 snapshot
16410 .buffer_snapshot()
16411 .diff_hunk_before(position)
16412 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16413 }
16414
16415 fn go_to_next_change(
16416 &mut self,
16417 _: &GoToNextChange,
16418 window: &mut Window,
16419 cx: &mut Context<Self>,
16420 ) {
16421 if let Some(selections) = self
16422 .change_list
16423 .next_change(1, Direction::Next)
16424 .map(|s| s.to_vec())
16425 {
16426 self.change_selections(Default::default(), window, cx, |s| {
16427 let map = s.display_snapshot();
16428 s.select_display_ranges(selections.iter().map(|a| {
16429 let point = a.to_display_point(&map);
16430 point..point
16431 }))
16432 })
16433 }
16434 }
16435
16436 fn go_to_previous_change(
16437 &mut self,
16438 _: &GoToPreviousChange,
16439 window: &mut Window,
16440 cx: &mut Context<Self>,
16441 ) {
16442 if let Some(selections) = self
16443 .change_list
16444 .next_change(1, Direction::Prev)
16445 .map(|s| s.to_vec())
16446 {
16447 self.change_selections(Default::default(), window, cx, |s| {
16448 let map = s.display_snapshot();
16449 s.select_display_ranges(selections.iter().map(|a| {
16450 let point = a.to_display_point(&map);
16451 point..point
16452 }))
16453 })
16454 }
16455 }
16456
16457 pub fn go_to_next_document_highlight(
16458 &mut self,
16459 _: &GoToNextDocumentHighlight,
16460 window: &mut Window,
16461 cx: &mut Context<Self>,
16462 ) {
16463 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16464 }
16465
16466 pub fn go_to_prev_document_highlight(
16467 &mut self,
16468 _: &GoToPreviousDocumentHighlight,
16469 window: &mut Window,
16470 cx: &mut Context<Self>,
16471 ) {
16472 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16473 }
16474
16475 pub fn go_to_document_highlight_before_or_after_position(
16476 &mut self,
16477 direction: Direction,
16478 window: &mut Window,
16479 cx: &mut Context<Editor>,
16480 ) {
16481 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16482 let snapshot = self.snapshot(window, cx);
16483 let buffer = &snapshot.buffer_snapshot();
16484 let position = self
16485 .selections
16486 .newest::<Point>(&snapshot.display_snapshot)
16487 .head();
16488 let anchor_position = buffer.anchor_after(position);
16489
16490 // Get all document highlights (both read and write)
16491 let mut all_highlights = Vec::new();
16492
16493 if let Some((_, read_highlights)) = self
16494 .background_highlights
16495 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16496 {
16497 all_highlights.extend(read_highlights.iter());
16498 }
16499
16500 if let Some((_, write_highlights)) = self
16501 .background_highlights
16502 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16503 {
16504 all_highlights.extend(write_highlights.iter());
16505 }
16506
16507 if all_highlights.is_empty() {
16508 return;
16509 }
16510
16511 // Sort highlights by position
16512 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16513
16514 let target_highlight = match direction {
16515 Direction::Next => {
16516 // Find the first highlight after the current position
16517 all_highlights
16518 .iter()
16519 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16520 }
16521 Direction::Prev => {
16522 // Find the last highlight before the current position
16523 all_highlights
16524 .iter()
16525 .rev()
16526 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16527 }
16528 };
16529
16530 if let Some(highlight) = target_highlight {
16531 let destination = highlight.start.to_point(buffer);
16532 let autoscroll = Autoscroll::center();
16533
16534 self.unfold_ranges(&[destination..destination], false, false, cx);
16535 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16536 s.select_ranges([destination..destination]);
16537 });
16538 }
16539 }
16540
16541 fn go_to_line<T: 'static>(
16542 &mut self,
16543 position: Anchor,
16544 highlight_color: Option<Hsla>,
16545 window: &mut Window,
16546 cx: &mut Context<Self>,
16547 ) {
16548 let snapshot = self.snapshot(window, cx).display_snapshot;
16549 let position = position.to_point(&snapshot.buffer_snapshot());
16550 let start = snapshot
16551 .buffer_snapshot()
16552 .clip_point(Point::new(position.row, 0), Bias::Left);
16553 let end = start + Point::new(1, 0);
16554 let start = snapshot.buffer_snapshot().anchor_before(start);
16555 let end = snapshot.buffer_snapshot().anchor_before(end);
16556
16557 self.highlight_rows::<T>(
16558 start..end,
16559 highlight_color
16560 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16561 Default::default(),
16562 cx,
16563 );
16564
16565 if self.buffer.read(cx).is_singleton() {
16566 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16567 }
16568 }
16569
16570 pub fn go_to_definition(
16571 &mut self,
16572 _: &GoToDefinition,
16573 window: &mut Window,
16574 cx: &mut Context<Self>,
16575 ) -> Task<Result<Navigated>> {
16576 let definition =
16577 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16578 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16579 cx.spawn_in(window, async move |editor, cx| {
16580 if definition.await? == Navigated::Yes {
16581 return Ok(Navigated::Yes);
16582 }
16583 match fallback_strategy {
16584 GoToDefinitionFallback::None => Ok(Navigated::No),
16585 GoToDefinitionFallback::FindAllReferences => {
16586 match editor.update_in(cx, |editor, window, cx| {
16587 editor.find_all_references(&FindAllReferences, window, cx)
16588 })? {
16589 Some(references) => references.await,
16590 None => Ok(Navigated::No),
16591 }
16592 }
16593 }
16594 })
16595 }
16596
16597 pub fn go_to_declaration(
16598 &mut self,
16599 _: &GoToDeclaration,
16600 window: &mut Window,
16601 cx: &mut Context<Self>,
16602 ) -> Task<Result<Navigated>> {
16603 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16604 }
16605
16606 pub fn go_to_declaration_split(
16607 &mut self,
16608 _: &GoToDeclaration,
16609 window: &mut Window,
16610 cx: &mut Context<Self>,
16611 ) -> Task<Result<Navigated>> {
16612 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16613 }
16614
16615 pub fn go_to_implementation(
16616 &mut self,
16617 _: &GoToImplementation,
16618 window: &mut Window,
16619 cx: &mut Context<Self>,
16620 ) -> Task<Result<Navigated>> {
16621 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16622 }
16623
16624 pub fn go_to_implementation_split(
16625 &mut self,
16626 _: &GoToImplementationSplit,
16627 window: &mut Window,
16628 cx: &mut Context<Self>,
16629 ) -> Task<Result<Navigated>> {
16630 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16631 }
16632
16633 pub fn go_to_type_definition(
16634 &mut self,
16635 _: &GoToTypeDefinition,
16636 window: &mut Window,
16637 cx: &mut Context<Self>,
16638 ) -> Task<Result<Navigated>> {
16639 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16640 }
16641
16642 pub fn go_to_definition_split(
16643 &mut self,
16644 _: &GoToDefinitionSplit,
16645 window: &mut Window,
16646 cx: &mut Context<Self>,
16647 ) -> Task<Result<Navigated>> {
16648 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16649 }
16650
16651 pub fn go_to_type_definition_split(
16652 &mut self,
16653 _: &GoToTypeDefinitionSplit,
16654 window: &mut Window,
16655 cx: &mut Context<Self>,
16656 ) -> Task<Result<Navigated>> {
16657 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16658 }
16659
16660 fn go_to_definition_of_kind(
16661 &mut self,
16662 kind: GotoDefinitionKind,
16663 split: bool,
16664 window: &mut Window,
16665 cx: &mut Context<Self>,
16666 ) -> Task<Result<Navigated>> {
16667 let Some(provider) = self.semantics_provider.clone() else {
16668 return Task::ready(Ok(Navigated::No));
16669 };
16670 let head = self
16671 .selections
16672 .newest::<usize>(&self.display_snapshot(cx))
16673 .head();
16674 let buffer = self.buffer.read(cx);
16675 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16676 return Task::ready(Ok(Navigated::No));
16677 };
16678 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16679 return Task::ready(Ok(Navigated::No));
16680 };
16681
16682 cx.spawn_in(window, async move |editor, cx| {
16683 let Some(definitions) = definitions.await? else {
16684 return Ok(Navigated::No);
16685 };
16686 let navigated = editor
16687 .update_in(cx, |editor, window, cx| {
16688 editor.navigate_to_hover_links(
16689 Some(kind),
16690 definitions
16691 .into_iter()
16692 .filter(|location| {
16693 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16694 })
16695 .map(HoverLink::Text)
16696 .collect::<Vec<_>>(),
16697 split,
16698 window,
16699 cx,
16700 )
16701 })?
16702 .await?;
16703 anyhow::Ok(navigated)
16704 })
16705 }
16706
16707 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16708 let selection = self.selections.newest_anchor();
16709 let head = selection.head();
16710 let tail = selection.tail();
16711
16712 let Some((buffer, start_position)) =
16713 self.buffer.read(cx).text_anchor_for_position(head, cx)
16714 else {
16715 return;
16716 };
16717
16718 let end_position = if head != tail {
16719 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16720 return;
16721 };
16722 Some(pos)
16723 } else {
16724 None
16725 };
16726
16727 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16728 let url = if let Some(end_pos) = end_position {
16729 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16730 } else {
16731 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16732 };
16733
16734 if let Some(url) = url {
16735 cx.update(|window, cx| {
16736 if parse_zed_link(&url, cx).is_some() {
16737 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16738 } else {
16739 cx.open_url(&url);
16740 }
16741 })?;
16742 }
16743
16744 anyhow::Ok(())
16745 });
16746
16747 url_finder.detach();
16748 }
16749
16750 pub fn open_selected_filename(
16751 &mut self,
16752 _: &OpenSelectedFilename,
16753 window: &mut Window,
16754 cx: &mut Context<Self>,
16755 ) {
16756 let Some(workspace) = self.workspace() else {
16757 return;
16758 };
16759
16760 let position = self.selections.newest_anchor().head();
16761
16762 let Some((buffer, buffer_position)) =
16763 self.buffer.read(cx).text_anchor_for_position(position, cx)
16764 else {
16765 return;
16766 };
16767
16768 let project = self.project.clone();
16769
16770 cx.spawn_in(window, async move |_, cx| {
16771 let result = find_file(&buffer, project, buffer_position, cx).await;
16772
16773 if let Some((_, path)) = result {
16774 workspace
16775 .update_in(cx, |workspace, window, cx| {
16776 workspace.open_resolved_path(path, window, cx)
16777 })?
16778 .await?;
16779 }
16780 anyhow::Ok(())
16781 })
16782 .detach();
16783 }
16784
16785 pub(crate) fn navigate_to_hover_links(
16786 &mut self,
16787 kind: Option<GotoDefinitionKind>,
16788 definitions: Vec<HoverLink>,
16789 split: bool,
16790 window: &mut Window,
16791 cx: &mut Context<Editor>,
16792 ) -> Task<Result<Navigated>> {
16793 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16794 let mut first_url_or_file = None;
16795 let definitions: Vec<_> = definitions
16796 .into_iter()
16797 .filter_map(|def| match def {
16798 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16799 HoverLink::InlayHint(lsp_location, server_id) => {
16800 let computation =
16801 self.compute_target_location(lsp_location, server_id, window, cx);
16802 Some(cx.background_spawn(computation))
16803 }
16804 HoverLink::Url(url) => {
16805 first_url_or_file = Some(Either::Left(url));
16806 None
16807 }
16808 HoverLink::File(path) => {
16809 first_url_or_file = Some(Either::Right(path));
16810 None
16811 }
16812 })
16813 .collect();
16814
16815 let workspace = self.workspace();
16816
16817 cx.spawn_in(window, async move |editor, cx| {
16818 let locations: Vec<Location> = future::join_all(definitions)
16819 .await
16820 .into_iter()
16821 .filter_map(|location| location.transpose())
16822 .collect::<Result<_>>()
16823 .context("location tasks")?;
16824 let mut locations = cx.update(|_, cx| {
16825 locations
16826 .into_iter()
16827 .map(|location| {
16828 let buffer = location.buffer.read(cx);
16829 (location.buffer, location.range.to_point(buffer))
16830 })
16831 .into_group_map()
16832 })?;
16833 let mut num_locations = 0;
16834 for ranges in locations.values_mut() {
16835 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16836 ranges.dedup();
16837 num_locations += ranges.len();
16838 }
16839
16840 if num_locations > 1 {
16841 let Some(workspace) = workspace else {
16842 return Ok(Navigated::No);
16843 };
16844
16845 let tab_kind = match kind {
16846 Some(GotoDefinitionKind::Implementation) => "Implementations",
16847 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16848 Some(GotoDefinitionKind::Declaration) => "Declarations",
16849 Some(GotoDefinitionKind::Type) => "Types",
16850 };
16851 let title = editor
16852 .update_in(cx, |_, _, cx| {
16853 let target = locations
16854 .iter()
16855 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16856 .map(|(buffer, location)| {
16857 buffer
16858 .read(cx)
16859 .text_for_range(location.clone())
16860 .collect::<String>()
16861 })
16862 .filter(|text| !text.contains('\n'))
16863 .unique()
16864 .take(3)
16865 .join(", ");
16866 if target.is_empty() {
16867 tab_kind.to_owned()
16868 } else {
16869 format!("{tab_kind} for {target}")
16870 }
16871 })
16872 .context("buffer title")?;
16873
16874 let opened = workspace
16875 .update_in(cx, |workspace, window, cx| {
16876 Self::open_locations_in_multibuffer(
16877 workspace,
16878 locations,
16879 title,
16880 split,
16881 MultibufferSelectionMode::First,
16882 window,
16883 cx,
16884 )
16885 })
16886 .is_ok();
16887
16888 anyhow::Ok(Navigated::from_bool(opened))
16889 } else if num_locations == 0 {
16890 // If there is one url or file, open it directly
16891 match first_url_or_file {
16892 Some(Either::Left(url)) => {
16893 cx.update(|_, cx| cx.open_url(&url))?;
16894 Ok(Navigated::Yes)
16895 }
16896 Some(Either::Right(path)) => {
16897 let Some(workspace) = workspace else {
16898 return Ok(Navigated::No);
16899 };
16900
16901 workspace
16902 .update_in(cx, |workspace, window, cx| {
16903 workspace.open_resolved_path(path, window, cx)
16904 })?
16905 .await?;
16906 Ok(Navigated::Yes)
16907 }
16908 None => Ok(Navigated::No),
16909 }
16910 } else {
16911 let Some(workspace) = workspace else {
16912 return Ok(Navigated::No);
16913 };
16914
16915 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16916 let target_range = target_ranges.first().unwrap().clone();
16917
16918 editor.update_in(cx, |editor, window, cx| {
16919 let range = target_range.to_point(target_buffer.read(cx));
16920 let range = editor.range_for_match(&range, false);
16921 let range = collapse_multiline_range(range);
16922
16923 if !split
16924 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16925 {
16926 editor.go_to_singleton_buffer_range(range, window, cx);
16927 } else {
16928 let pane = workspace.read(cx).active_pane().clone();
16929 window.defer(cx, move |window, cx| {
16930 let target_editor: Entity<Self> =
16931 workspace.update(cx, |workspace, cx| {
16932 let pane = if split {
16933 workspace.adjacent_pane(window, cx)
16934 } else {
16935 workspace.active_pane().clone()
16936 };
16937
16938 workspace.open_project_item(
16939 pane,
16940 target_buffer.clone(),
16941 true,
16942 true,
16943 window,
16944 cx,
16945 )
16946 });
16947 target_editor.update(cx, |target_editor, cx| {
16948 // When selecting a definition in a different buffer, disable the nav history
16949 // to avoid creating a history entry at the previous cursor location.
16950 pane.update(cx, |pane, _| pane.disable_history());
16951 target_editor.go_to_singleton_buffer_range(range, window, cx);
16952 pane.update(cx, |pane, _| pane.enable_history());
16953 });
16954 });
16955 }
16956 Navigated::Yes
16957 })
16958 }
16959 })
16960 }
16961
16962 fn compute_target_location(
16963 &self,
16964 lsp_location: lsp::Location,
16965 server_id: LanguageServerId,
16966 window: &mut Window,
16967 cx: &mut Context<Self>,
16968 ) -> Task<anyhow::Result<Option<Location>>> {
16969 let Some(project) = self.project.clone() else {
16970 return Task::ready(Ok(None));
16971 };
16972
16973 cx.spawn_in(window, async move |editor, cx| {
16974 let location_task = editor.update(cx, |_, cx| {
16975 project.update(cx, |project, cx| {
16976 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16977 })
16978 })?;
16979 let location = Some({
16980 let target_buffer_handle = location_task.await.context("open local buffer")?;
16981 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16982 let target_start = target_buffer
16983 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16984 let target_end = target_buffer
16985 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16986 target_buffer.anchor_after(target_start)
16987 ..target_buffer.anchor_before(target_end)
16988 })?;
16989 Location {
16990 buffer: target_buffer_handle,
16991 range,
16992 }
16993 });
16994 Ok(location)
16995 })
16996 }
16997
16998 fn go_to_next_reference(
16999 &mut self,
17000 _: &GoToNextReference,
17001 window: &mut Window,
17002 cx: &mut Context<Self>,
17003 ) {
17004 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17005 if let Some(task) = task {
17006 task.detach();
17007 };
17008 }
17009
17010 fn go_to_prev_reference(
17011 &mut self,
17012 _: &GoToPreviousReference,
17013 window: &mut Window,
17014 cx: &mut Context<Self>,
17015 ) {
17016 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17017 if let Some(task) = task {
17018 task.detach();
17019 };
17020 }
17021
17022 pub fn go_to_reference_before_or_after_position(
17023 &mut self,
17024 direction: Direction,
17025 count: usize,
17026 window: &mut Window,
17027 cx: &mut Context<Self>,
17028 ) -> Option<Task<Result<()>>> {
17029 let selection = self.selections.newest_anchor();
17030 let head = selection.head();
17031
17032 let multi_buffer = self.buffer.read(cx);
17033
17034 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17035 let workspace = self.workspace()?;
17036 let project = workspace.read(cx).project().clone();
17037 let references =
17038 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17039 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17040 let Some(locations) = references.await? else {
17041 return Ok(());
17042 };
17043
17044 if locations.is_empty() {
17045 // totally normal - the cursor may be on something which is not
17046 // a symbol (e.g. a keyword)
17047 log::info!("no references found under cursor");
17048 return Ok(());
17049 }
17050
17051 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17052
17053 let multi_buffer_snapshot =
17054 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
17055
17056 let (locations, current_location_index) =
17057 multi_buffer.update(cx, |multi_buffer, cx| {
17058 let mut locations = locations
17059 .into_iter()
17060 .filter_map(|loc| {
17061 let start = multi_buffer.buffer_anchor_to_anchor(
17062 &loc.buffer,
17063 loc.range.start,
17064 cx,
17065 )?;
17066 let end = multi_buffer.buffer_anchor_to_anchor(
17067 &loc.buffer,
17068 loc.range.end,
17069 cx,
17070 )?;
17071 Some(start..end)
17072 })
17073 .collect::<Vec<_>>();
17074
17075 // There is an O(n) implementation, but given this list will be
17076 // small (usually <100 items), the extra O(log(n)) factor isn't
17077 // worth the (surprisingly large amount of) extra complexity.
17078 locations
17079 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17080
17081 let head_offset = head.to_offset(&multi_buffer_snapshot);
17082
17083 let current_location_index = locations.iter().position(|loc| {
17084 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17085 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17086 });
17087
17088 (locations, current_location_index)
17089 })?;
17090
17091 let Some(current_location_index) = current_location_index else {
17092 // This indicates something has gone wrong, because we already
17093 // handle the "no references" case above
17094 log::error!(
17095 "failed to find current reference under cursor. Total references: {}",
17096 locations.len()
17097 );
17098 return Ok(());
17099 };
17100
17101 let destination_location_index = match direction {
17102 Direction::Next => (current_location_index + count) % locations.len(),
17103 Direction::Prev => {
17104 (current_location_index + locations.len() - count % locations.len())
17105 % locations.len()
17106 }
17107 };
17108
17109 // TODO(cameron): is this needed?
17110 // the thinking is to avoid "jumping to the current location" (avoid
17111 // polluting "jumplist" in vim terms)
17112 if current_location_index == destination_location_index {
17113 return Ok(());
17114 }
17115
17116 let Range { start, end } = locations[destination_location_index];
17117
17118 editor.update_in(cx, |editor, window, cx| {
17119 let effects = SelectionEffects::default();
17120
17121 editor.unfold_ranges(&[start..end], false, false, cx);
17122 editor.change_selections(effects, window, cx, |s| {
17123 s.select_ranges([start..start]);
17124 });
17125 })?;
17126
17127 Ok(())
17128 }))
17129 }
17130
17131 pub fn find_all_references(
17132 &mut self,
17133 _: &FindAllReferences,
17134 window: &mut Window,
17135 cx: &mut Context<Self>,
17136 ) -> Option<Task<Result<Navigated>>> {
17137 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
17138 let multi_buffer = self.buffer.read(cx);
17139 let head = selection.head();
17140
17141 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17142 let head_anchor = multi_buffer_snapshot.anchor_at(
17143 head,
17144 if head < selection.tail() {
17145 Bias::Right
17146 } else {
17147 Bias::Left
17148 },
17149 );
17150
17151 match self
17152 .find_all_references_task_sources
17153 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17154 {
17155 Ok(_) => {
17156 log::info!(
17157 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17158 );
17159 return None;
17160 }
17161 Err(i) => {
17162 self.find_all_references_task_sources.insert(i, head_anchor);
17163 }
17164 }
17165
17166 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17167 let workspace = self.workspace()?;
17168 let project = workspace.read(cx).project().clone();
17169 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17170 Some(cx.spawn_in(window, async move |editor, cx| {
17171 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17172 if let Ok(i) = editor
17173 .find_all_references_task_sources
17174 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17175 {
17176 editor.find_all_references_task_sources.remove(i);
17177 }
17178 });
17179
17180 let Some(locations) = references.await? else {
17181 return anyhow::Ok(Navigated::No);
17182 };
17183 let mut locations = cx.update(|_, cx| {
17184 locations
17185 .into_iter()
17186 .map(|location| {
17187 let buffer = location.buffer.read(cx);
17188 (location.buffer, location.range.to_point(buffer))
17189 })
17190 .into_group_map()
17191 })?;
17192 if locations.is_empty() {
17193 return anyhow::Ok(Navigated::No);
17194 }
17195 for ranges in locations.values_mut() {
17196 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17197 ranges.dedup();
17198 }
17199
17200 workspace.update_in(cx, |workspace, window, cx| {
17201 let target = locations
17202 .iter()
17203 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17204 .map(|(buffer, location)| {
17205 buffer
17206 .read(cx)
17207 .text_for_range(location.clone())
17208 .collect::<String>()
17209 })
17210 .filter(|text| !text.contains('\n'))
17211 .unique()
17212 .take(3)
17213 .join(", ");
17214 let title = if target.is_empty() {
17215 "References".to_owned()
17216 } else {
17217 format!("References to {target}")
17218 };
17219 Self::open_locations_in_multibuffer(
17220 workspace,
17221 locations,
17222 title,
17223 false,
17224 MultibufferSelectionMode::First,
17225 window,
17226 cx,
17227 );
17228 Navigated::Yes
17229 })
17230 }))
17231 }
17232
17233 /// Opens a multibuffer with the given project locations in it
17234 pub fn open_locations_in_multibuffer(
17235 workspace: &mut Workspace,
17236 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17237 title: String,
17238 split: bool,
17239 multibuffer_selection_mode: MultibufferSelectionMode,
17240 window: &mut Window,
17241 cx: &mut Context<Workspace>,
17242 ) {
17243 if locations.is_empty() {
17244 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17245 return;
17246 }
17247
17248 let capability = workspace.project().read(cx).capability();
17249 let mut ranges = <Vec<Range<Anchor>>>::new();
17250
17251 // a key to find existing multibuffer editors with the same set of locations
17252 // to prevent us from opening more and more multibuffer tabs for searches and the like
17253 let mut key = (title.clone(), vec![]);
17254 let excerpt_buffer = cx.new(|cx| {
17255 let key = &mut key.1;
17256 let mut multibuffer = MultiBuffer::new(capability);
17257 for (buffer, mut ranges_for_buffer) in locations {
17258 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17259 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17260 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17261 PathKey::for_buffer(&buffer, cx),
17262 buffer.clone(),
17263 ranges_for_buffer,
17264 multibuffer_context_lines(cx),
17265 cx,
17266 );
17267 ranges.extend(new_ranges)
17268 }
17269
17270 multibuffer.with_title(title)
17271 });
17272 let existing = workspace.active_pane().update(cx, |pane, cx| {
17273 pane.items()
17274 .filter_map(|item| item.downcast::<Editor>())
17275 .find(|editor| {
17276 editor
17277 .read(cx)
17278 .lookup_key
17279 .as_ref()
17280 .and_then(|it| {
17281 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17282 })
17283 .is_some_and(|it| *it == key)
17284 })
17285 });
17286 let editor = existing.unwrap_or_else(|| {
17287 cx.new(|cx| {
17288 let mut editor = Editor::for_multibuffer(
17289 excerpt_buffer,
17290 Some(workspace.project().clone()),
17291 window,
17292 cx,
17293 );
17294 editor.lookup_key = Some(Box::new(key));
17295 editor
17296 })
17297 });
17298 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17299 MultibufferSelectionMode::First => {
17300 if let Some(first_range) = ranges.first() {
17301 editor.change_selections(
17302 SelectionEffects::no_scroll(),
17303 window,
17304 cx,
17305 |selections| {
17306 selections.clear_disjoint();
17307 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17308 },
17309 );
17310 }
17311 editor.highlight_background::<Self>(
17312 &ranges,
17313 |theme| theme.colors().editor_highlighted_line_background,
17314 cx,
17315 );
17316 }
17317 MultibufferSelectionMode::All => {
17318 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17319 selections.clear_disjoint();
17320 selections.select_anchor_ranges(ranges);
17321 });
17322 }
17323 });
17324
17325 let item = Box::new(editor);
17326 let item_id = item.item_id();
17327
17328 if split {
17329 let pane = workspace.adjacent_pane(window, cx);
17330 workspace.add_item(pane, item, None, true, true, window, cx);
17331 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17332 let (preview_item_id, preview_item_idx) =
17333 workspace.active_pane().read_with(cx, |pane, _| {
17334 (pane.preview_item_id(), pane.preview_item_idx())
17335 });
17336
17337 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17338
17339 if let Some(preview_item_id) = preview_item_id {
17340 workspace.active_pane().update(cx, |pane, cx| {
17341 pane.remove_item(preview_item_id, false, false, window, cx);
17342 });
17343 }
17344 } else {
17345 workspace.add_item_to_active_pane(item, None, true, window, cx);
17346 }
17347 workspace.active_pane().update(cx, |pane, cx| {
17348 pane.set_preview_item_id(Some(item_id), cx);
17349 });
17350 }
17351
17352 pub fn rename(
17353 &mut self,
17354 _: &Rename,
17355 window: &mut Window,
17356 cx: &mut Context<Self>,
17357 ) -> Option<Task<Result<()>>> {
17358 use language::ToOffset as _;
17359
17360 let provider = self.semantics_provider.clone()?;
17361 let selection = self.selections.newest_anchor().clone();
17362 let (cursor_buffer, cursor_buffer_position) = self
17363 .buffer
17364 .read(cx)
17365 .text_anchor_for_position(selection.head(), cx)?;
17366 let (tail_buffer, cursor_buffer_position_end) = self
17367 .buffer
17368 .read(cx)
17369 .text_anchor_for_position(selection.tail(), cx)?;
17370 if tail_buffer != cursor_buffer {
17371 return None;
17372 }
17373
17374 let snapshot = cursor_buffer.read(cx).snapshot();
17375 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17376 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17377 let prepare_rename = provider
17378 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17379 .unwrap_or_else(|| Task::ready(Ok(None)));
17380 drop(snapshot);
17381
17382 Some(cx.spawn_in(window, async move |this, cx| {
17383 let rename_range = if let Some(range) = prepare_rename.await? {
17384 Some(range)
17385 } else {
17386 this.update(cx, |this, cx| {
17387 let buffer = this.buffer.read(cx).snapshot(cx);
17388 let mut buffer_highlights = this
17389 .document_highlights_for_position(selection.head(), &buffer)
17390 .filter(|highlight| {
17391 highlight.start.excerpt_id == selection.head().excerpt_id
17392 && highlight.end.excerpt_id == selection.head().excerpt_id
17393 });
17394 buffer_highlights
17395 .next()
17396 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17397 })?
17398 };
17399 if let Some(rename_range) = rename_range {
17400 this.update_in(cx, |this, window, cx| {
17401 let snapshot = cursor_buffer.read(cx).snapshot();
17402 let rename_buffer_range = rename_range.to_offset(&snapshot);
17403 let cursor_offset_in_rename_range =
17404 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17405 let cursor_offset_in_rename_range_end =
17406 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17407
17408 this.take_rename(false, window, cx);
17409 let buffer = this.buffer.read(cx).read(cx);
17410 let cursor_offset = selection.head().to_offset(&buffer);
17411 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17412 let rename_end = rename_start + rename_buffer_range.len();
17413 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17414 let mut old_highlight_id = None;
17415 let old_name: Arc<str> = buffer
17416 .chunks(rename_start..rename_end, true)
17417 .map(|chunk| {
17418 if old_highlight_id.is_none() {
17419 old_highlight_id = chunk.syntax_highlight_id;
17420 }
17421 chunk.text
17422 })
17423 .collect::<String>()
17424 .into();
17425
17426 drop(buffer);
17427
17428 // Position the selection in the rename editor so that it matches the current selection.
17429 this.show_local_selections = false;
17430 let rename_editor = cx.new(|cx| {
17431 let mut editor = Editor::single_line(window, cx);
17432 editor.buffer.update(cx, |buffer, cx| {
17433 buffer.edit([(0..0, old_name.clone())], None, cx)
17434 });
17435 let rename_selection_range = match cursor_offset_in_rename_range
17436 .cmp(&cursor_offset_in_rename_range_end)
17437 {
17438 Ordering::Equal => {
17439 editor.select_all(&SelectAll, window, cx);
17440 return editor;
17441 }
17442 Ordering::Less => {
17443 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17444 }
17445 Ordering::Greater => {
17446 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17447 }
17448 };
17449 if rename_selection_range.end > old_name.len() {
17450 editor.select_all(&SelectAll, window, cx);
17451 } else {
17452 editor.change_selections(Default::default(), window, cx, |s| {
17453 s.select_ranges([rename_selection_range]);
17454 });
17455 }
17456 editor
17457 });
17458 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17459 if e == &EditorEvent::Focused {
17460 cx.emit(EditorEvent::FocusedIn)
17461 }
17462 })
17463 .detach();
17464
17465 let write_highlights =
17466 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17467 let read_highlights =
17468 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17469 let ranges = write_highlights
17470 .iter()
17471 .flat_map(|(_, ranges)| ranges.iter())
17472 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17473 .cloned()
17474 .collect();
17475
17476 this.highlight_text::<Rename>(
17477 ranges,
17478 HighlightStyle {
17479 fade_out: Some(0.6),
17480 ..Default::default()
17481 },
17482 cx,
17483 );
17484 let rename_focus_handle = rename_editor.focus_handle(cx);
17485 window.focus(&rename_focus_handle);
17486 let block_id = this.insert_blocks(
17487 [BlockProperties {
17488 style: BlockStyle::Flex,
17489 placement: BlockPlacement::Below(range.start),
17490 height: Some(1),
17491 render: Arc::new({
17492 let rename_editor = rename_editor.clone();
17493 move |cx: &mut BlockContext| {
17494 let mut text_style = cx.editor_style.text.clone();
17495 if let Some(highlight_style) = old_highlight_id
17496 .and_then(|h| h.style(&cx.editor_style.syntax))
17497 {
17498 text_style = text_style.highlight(highlight_style);
17499 }
17500 div()
17501 .block_mouse_except_scroll()
17502 .pl(cx.anchor_x)
17503 .child(EditorElement::new(
17504 &rename_editor,
17505 EditorStyle {
17506 background: cx.theme().system().transparent,
17507 local_player: cx.editor_style.local_player,
17508 text: text_style,
17509 scrollbar_width: cx.editor_style.scrollbar_width,
17510 syntax: cx.editor_style.syntax.clone(),
17511 status: cx.editor_style.status.clone(),
17512 inlay_hints_style: HighlightStyle {
17513 font_weight: Some(FontWeight::BOLD),
17514 ..make_inlay_hints_style(cx.app)
17515 },
17516 edit_prediction_styles: make_suggestion_styles(
17517 cx.app,
17518 ),
17519 ..EditorStyle::default()
17520 },
17521 ))
17522 .into_any_element()
17523 }
17524 }),
17525 priority: 0,
17526 }],
17527 Some(Autoscroll::fit()),
17528 cx,
17529 )[0];
17530 this.pending_rename = Some(RenameState {
17531 range,
17532 old_name,
17533 editor: rename_editor,
17534 block_id,
17535 });
17536 })?;
17537 }
17538
17539 Ok(())
17540 }))
17541 }
17542
17543 pub fn confirm_rename(
17544 &mut self,
17545 _: &ConfirmRename,
17546 window: &mut Window,
17547 cx: &mut Context<Self>,
17548 ) -> Option<Task<Result<()>>> {
17549 let rename = self.take_rename(false, window, cx)?;
17550 let workspace = self.workspace()?.downgrade();
17551 let (buffer, start) = self
17552 .buffer
17553 .read(cx)
17554 .text_anchor_for_position(rename.range.start, cx)?;
17555 let (end_buffer, _) = self
17556 .buffer
17557 .read(cx)
17558 .text_anchor_for_position(rename.range.end, cx)?;
17559 if buffer != end_buffer {
17560 return None;
17561 }
17562
17563 let old_name = rename.old_name;
17564 let new_name = rename.editor.read(cx).text(cx);
17565
17566 let rename = self.semantics_provider.as_ref()?.perform_rename(
17567 &buffer,
17568 start,
17569 new_name.clone(),
17570 cx,
17571 )?;
17572
17573 Some(cx.spawn_in(window, async move |editor, cx| {
17574 let project_transaction = rename.await?;
17575 Self::open_project_transaction(
17576 &editor,
17577 workspace,
17578 project_transaction,
17579 format!("Rename: {} → {}", old_name, new_name),
17580 cx,
17581 )
17582 .await?;
17583
17584 editor.update(cx, |editor, cx| {
17585 editor.refresh_document_highlights(cx);
17586 })?;
17587 Ok(())
17588 }))
17589 }
17590
17591 fn take_rename(
17592 &mut self,
17593 moving_cursor: bool,
17594 window: &mut Window,
17595 cx: &mut Context<Self>,
17596 ) -> Option<RenameState> {
17597 let rename = self.pending_rename.take()?;
17598 if rename.editor.focus_handle(cx).is_focused(window) {
17599 window.focus(&self.focus_handle);
17600 }
17601
17602 self.remove_blocks(
17603 [rename.block_id].into_iter().collect(),
17604 Some(Autoscroll::fit()),
17605 cx,
17606 );
17607 self.clear_highlights::<Rename>(cx);
17608 self.show_local_selections = true;
17609
17610 if moving_cursor {
17611 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17612 editor
17613 .selections
17614 .newest::<usize>(&editor.display_snapshot(cx))
17615 .head()
17616 });
17617
17618 // Update the selection to match the position of the selection inside
17619 // the rename editor.
17620 let snapshot = self.buffer.read(cx).read(cx);
17621 let rename_range = rename.range.to_offset(&snapshot);
17622 let cursor_in_editor = snapshot
17623 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17624 .min(rename_range.end);
17625 drop(snapshot);
17626
17627 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17628 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17629 });
17630 } else {
17631 self.refresh_document_highlights(cx);
17632 }
17633
17634 Some(rename)
17635 }
17636
17637 pub fn pending_rename(&self) -> Option<&RenameState> {
17638 self.pending_rename.as_ref()
17639 }
17640
17641 fn format(
17642 &mut self,
17643 _: &Format,
17644 window: &mut Window,
17645 cx: &mut Context<Self>,
17646 ) -> Option<Task<Result<()>>> {
17647 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17648
17649 let project = match &self.project {
17650 Some(project) => project.clone(),
17651 None => return None,
17652 };
17653
17654 Some(self.perform_format(
17655 project,
17656 FormatTrigger::Manual,
17657 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17658 window,
17659 cx,
17660 ))
17661 }
17662
17663 fn format_selections(
17664 &mut self,
17665 _: &FormatSelections,
17666 window: &mut Window,
17667 cx: &mut Context<Self>,
17668 ) -> Option<Task<Result<()>>> {
17669 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17670
17671 let project = match &self.project {
17672 Some(project) => project.clone(),
17673 None => return None,
17674 };
17675
17676 let ranges = self
17677 .selections
17678 .all_adjusted(&self.display_snapshot(cx))
17679 .into_iter()
17680 .map(|selection| selection.range())
17681 .collect_vec();
17682
17683 Some(self.perform_format(
17684 project,
17685 FormatTrigger::Manual,
17686 FormatTarget::Ranges(ranges),
17687 window,
17688 cx,
17689 ))
17690 }
17691
17692 fn perform_format(
17693 &mut self,
17694 project: Entity<Project>,
17695 trigger: FormatTrigger,
17696 target: FormatTarget,
17697 window: &mut Window,
17698 cx: &mut Context<Self>,
17699 ) -> Task<Result<()>> {
17700 let buffer = self.buffer.clone();
17701 let (buffers, target) = match target {
17702 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17703 FormatTarget::Ranges(selection_ranges) => {
17704 let multi_buffer = buffer.read(cx);
17705 let snapshot = multi_buffer.read(cx);
17706 let mut buffers = HashSet::default();
17707 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17708 BTreeMap::new();
17709 for selection_range in selection_ranges {
17710 for (buffer, buffer_range, _) in
17711 snapshot.range_to_buffer_ranges(selection_range)
17712 {
17713 let buffer_id = buffer.remote_id();
17714 let start = buffer.anchor_before(buffer_range.start);
17715 let end = buffer.anchor_after(buffer_range.end);
17716 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17717 buffer_id_to_ranges
17718 .entry(buffer_id)
17719 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17720 .or_insert_with(|| vec![start..end]);
17721 }
17722 }
17723 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17724 }
17725 };
17726
17727 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17728 let selections_prev = transaction_id_prev
17729 .and_then(|transaction_id_prev| {
17730 // default to selections as they were after the last edit, if we have them,
17731 // instead of how they are now.
17732 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17733 // will take you back to where you made the last edit, instead of staying where you scrolled
17734 self.selection_history
17735 .transaction(transaction_id_prev)
17736 .map(|t| t.0.clone())
17737 })
17738 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17739
17740 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17741 let format = project.update(cx, |project, cx| {
17742 project.format(buffers, target, true, trigger, cx)
17743 });
17744
17745 cx.spawn_in(window, async move |editor, cx| {
17746 let transaction = futures::select_biased! {
17747 transaction = format.log_err().fuse() => transaction,
17748 () = timeout => {
17749 log::warn!("timed out waiting for formatting");
17750 None
17751 }
17752 };
17753
17754 buffer
17755 .update(cx, |buffer, cx| {
17756 if let Some(transaction) = transaction
17757 && !buffer.is_singleton()
17758 {
17759 buffer.push_transaction(&transaction.0, cx);
17760 }
17761 cx.notify();
17762 })
17763 .ok();
17764
17765 if let Some(transaction_id_now) =
17766 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17767 {
17768 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17769 if has_new_transaction {
17770 _ = editor.update(cx, |editor, _| {
17771 editor
17772 .selection_history
17773 .insert_transaction(transaction_id_now, selections_prev);
17774 });
17775 }
17776 }
17777
17778 Ok(())
17779 })
17780 }
17781
17782 fn organize_imports(
17783 &mut self,
17784 _: &OrganizeImports,
17785 window: &mut Window,
17786 cx: &mut Context<Self>,
17787 ) -> Option<Task<Result<()>>> {
17788 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17789 let project = match &self.project {
17790 Some(project) => project.clone(),
17791 None => return None,
17792 };
17793 Some(self.perform_code_action_kind(
17794 project,
17795 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17796 window,
17797 cx,
17798 ))
17799 }
17800
17801 fn perform_code_action_kind(
17802 &mut self,
17803 project: Entity<Project>,
17804 kind: CodeActionKind,
17805 window: &mut Window,
17806 cx: &mut Context<Self>,
17807 ) -> Task<Result<()>> {
17808 let buffer = self.buffer.clone();
17809 let buffers = buffer.read(cx).all_buffers();
17810 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17811 let apply_action = project.update(cx, |project, cx| {
17812 project.apply_code_action_kind(buffers, kind, true, cx)
17813 });
17814 cx.spawn_in(window, async move |_, cx| {
17815 let transaction = futures::select_biased! {
17816 () = timeout => {
17817 log::warn!("timed out waiting for executing code action");
17818 None
17819 }
17820 transaction = apply_action.log_err().fuse() => transaction,
17821 };
17822 buffer
17823 .update(cx, |buffer, cx| {
17824 // check if we need this
17825 if let Some(transaction) = transaction
17826 && !buffer.is_singleton()
17827 {
17828 buffer.push_transaction(&transaction.0, cx);
17829 }
17830 cx.notify();
17831 })
17832 .ok();
17833 Ok(())
17834 })
17835 }
17836
17837 pub fn restart_language_server(
17838 &mut self,
17839 _: &RestartLanguageServer,
17840 _: &mut Window,
17841 cx: &mut Context<Self>,
17842 ) {
17843 if let Some(project) = self.project.clone() {
17844 self.buffer.update(cx, |multi_buffer, cx| {
17845 project.update(cx, |project, cx| {
17846 project.restart_language_servers_for_buffers(
17847 multi_buffer.all_buffers().into_iter().collect(),
17848 HashSet::default(),
17849 cx,
17850 );
17851 });
17852 })
17853 }
17854 }
17855
17856 pub fn stop_language_server(
17857 &mut self,
17858 _: &StopLanguageServer,
17859 _: &mut Window,
17860 cx: &mut Context<Self>,
17861 ) {
17862 if let Some(project) = self.project.clone() {
17863 self.buffer.update(cx, |multi_buffer, cx| {
17864 project.update(cx, |project, cx| {
17865 project.stop_language_servers_for_buffers(
17866 multi_buffer.all_buffers().into_iter().collect(),
17867 HashSet::default(),
17868 cx,
17869 );
17870 });
17871 });
17872 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17873 }
17874 }
17875
17876 fn cancel_language_server_work(
17877 workspace: &mut Workspace,
17878 _: &actions::CancelLanguageServerWork,
17879 _: &mut Window,
17880 cx: &mut Context<Workspace>,
17881 ) {
17882 let project = workspace.project();
17883 let buffers = workspace
17884 .active_item(cx)
17885 .and_then(|item| item.act_as::<Editor>(cx))
17886 .map_or(HashSet::default(), |editor| {
17887 editor.read(cx).buffer.read(cx).all_buffers()
17888 });
17889 project.update(cx, |project, cx| {
17890 project.cancel_language_server_work_for_buffers(buffers, cx);
17891 });
17892 }
17893
17894 fn show_character_palette(
17895 &mut self,
17896 _: &ShowCharacterPalette,
17897 window: &mut Window,
17898 _: &mut Context<Self>,
17899 ) {
17900 window.show_character_palette();
17901 }
17902
17903 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17904 if !self.diagnostics_enabled() {
17905 return;
17906 }
17907
17908 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17909 let buffer = self.buffer.read(cx).snapshot(cx);
17910 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17911 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17912 let is_valid = buffer
17913 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17914 .any(|entry| {
17915 entry.diagnostic.is_primary
17916 && !entry.range.is_empty()
17917 && entry.range.start == primary_range_start
17918 && entry.diagnostic.message == active_diagnostics.active_message
17919 });
17920
17921 if !is_valid {
17922 self.dismiss_diagnostics(cx);
17923 }
17924 }
17925 }
17926
17927 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17928 match &self.active_diagnostics {
17929 ActiveDiagnostic::Group(group) => Some(group),
17930 _ => None,
17931 }
17932 }
17933
17934 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17935 if !self.diagnostics_enabled() {
17936 return;
17937 }
17938 self.dismiss_diagnostics(cx);
17939 self.active_diagnostics = ActiveDiagnostic::All;
17940 }
17941
17942 fn activate_diagnostics(
17943 &mut self,
17944 buffer_id: BufferId,
17945 diagnostic: DiagnosticEntryRef<'_, usize>,
17946 window: &mut Window,
17947 cx: &mut Context<Self>,
17948 ) {
17949 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17950 return;
17951 }
17952 self.dismiss_diagnostics(cx);
17953 let snapshot = self.snapshot(window, cx);
17954 let buffer = self.buffer.read(cx).snapshot(cx);
17955 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17956 return;
17957 };
17958
17959 let diagnostic_group = buffer
17960 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17961 .collect::<Vec<_>>();
17962
17963 let language_registry = self
17964 .project()
17965 .map(|project| project.read(cx).languages().clone());
17966
17967 let blocks = renderer.render_group(
17968 diagnostic_group,
17969 buffer_id,
17970 snapshot,
17971 cx.weak_entity(),
17972 language_registry,
17973 cx,
17974 );
17975
17976 let blocks = self.display_map.update(cx, |display_map, cx| {
17977 display_map.insert_blocks(blocks, cx).into_iter().collect()
17978 });
17979 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17980 active_range: buffer.anchor_before(diagnostic.range.start)
17981 ..buffer.anchor_after(diagnostic.range.end),
17982 active_message: diagnostic.diagnostic.message.clone(),
17983 group_id: diagnostic.diagnostic.group_id,
17984 blocks,
17985 });
17986 cx.notify();
17987 }
17988
17989 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17990 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17991 return;
17992 };
17993
17994 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17995 if let ActiveDiagnostic::Group(group) = prev {
17996 self.display_map.update(cx, |display_map, cx| {
17997 display_map.remove_blocks(group.blocks, cx);
17998 });
17999 cx.notify();
18000 }
18001 }
18002
18003 /// Disable inline diagnostics rendering for this editor.
18004 pub fn disable_inline_diagnostics(&mut self) {
18005 self.inline_diagnostics_enabled = false;
18006 self.inline_diagnostics_update = Task::ready(());
18007 self.inline_diagnostics.clear();
18008 }
18009
18010 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18011 self.diagnostics_enabled = false;
18012 self.dismiss_diagnostics(cx);
18013 self.inline_diagnostics_update = Task::ready(());
18014 self.inline_diagnostics.clear();
18015 }
18016
18017 pub fn disable_word_completions(&mut self) {
18018 self.word_completions_enabled = false;
18019 }
18020
18021 pub fn diagnostics_enabled(&self) -> bool {
18022 self.diagnostics_enabled && self.mode.is_full()
18023 }
18024
18025 pub fn inline_diagnostics_enabled(&self) -> bool {
18026 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18027 }
18028
18029 pub fn show_inline_diagnostics(&self) -> bool {
18030 self.show_inline_diagnostics
18031 }
18032
18033 pub fn toggle_inline_diagnostics(
18034 &mut self,
18035 _: &ToggleInlineDiagnostics,
18036 window: &mut Window,
18037 cx: &mut Context<Editor>,
18038 ) {
18039 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18040 self.refresh_inline_diagnostics(false, window, cx);
18041 }
18042
18043 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18044 self.diagnostics_max_severity = severity;
18045 self.display_map.update(cx, |display_map, _| {
18046 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18047 });
18048 }
18049
18050 pub fn toggle_diagnostics(
18051 &mut self,
18052 _: &ToggleDiagnostics,
18053 window: &mut Window,
18054 cx: &mut Context<Editor>,
18055 ) {
18056 if !self.diagnostics_enabled() {
18057 return;
18058 }
18059
18060 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18061 EditorSettings::get_global(cx)
18062 .diagnostics_max_severity
18063 .filter(|severity| severity != &DiagnosticSeverity::Off)
18064 .unwrap_or(DiagnosticSeverity::Hint)
18065 } else {
18066 DiagnosticSeverity::Off
18067 };
18068 self.set_max_diagnostics_severity(new_severity, cx);
18069 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18070 self.active_diagnostics = ActiveDiagnostic::None;
18071 self.inline_diagnostics_update = Task::ready(());
18072 self.inline_diagnostics.clear();
18073 } else {
18074 self.refresh_inline_diagnostics(false, window, cx);
18075 }
18076
18077 cx.notify();
18078 }
18079
18080 pub fn toggle_minimap(
18081 &mut self,
18082 _: &ToggleMinimap,
18083 window: &mut Window,
18084 cx: &mut Context<Editor>,
18085 ) {
18086 if self.supports_minimap(cx) {
18087 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18088 }
18089 }
18090
18091 fn refresh_inline_diagnostics(
18092 &mut self,
18093 debounce: bool,
18094 window: &mut Window,
18095 cx: &mut Context<Self>,
18096 ) {
18097 let max_severity = ProjectSettings::get_global(cx)
18098 .diagnostics
18099 .inline
18100 .max_severity
18101 .unwrap_or(self.diagnostics_max_severity);
18102
18103 if !self.inline_diagnostics_enabled()
18104 || !self.diagnostics_enabled()
18105 || !self.show_inline_diagnostics
18106 || max_severity == DiagnosticSeverity::Off
18107 {
18108 self.inline_diagnostics_update = Task::ready(());
18109 self.inline_diagnostics.clear();
18110 return;
18111 }
18112
18113 let debounce_ms = ProjectSettings::get_global(cx)
18114 .diagnostics
18115 .inline
18116 .update_debounce_ms;
18117 let debounce = if debounce && debounce_ms > 0 {
18118 Some(Duration::from_millis(debounce_ms))
18119 } else {
18120 None
18121 };
18122 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18123 if let Some(debounce) = debounce {
18124 cx.background_executor().timer(debounce).await;
18125 }
18126 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18127 editor
18128 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18129 .ok()
18130 }) else {
18131 return;
18132 };
18133
18134 let new_inline_diagnostics = cx
18135 .background_spawn(async move {
18136 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18137 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
18138 let message = diagnostic_entry
18139 .diagnostic
18140 .message
18141 .split_once('\n')
18142 .map(|(line, _)| line)
18143 .map(SharedString::new)
18144 .unwrap_or_else(|| {
18145 SharedString::new(&*diagnostic_entry.diagnostic.message)
18146 });
18147 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18148 let (Ok(i) | Err(i)) = inline_diagnostics
18149 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18150 inline_diagnostics.insert(
18151 i,
18152 (
18153 start_anchor,
18154 InlineDiagnostic {
18155 message,
18156 group_id: diagnostic_entry.diagnostic.group_id,
18157 start: diagnostic_entry.range.start.to_point(&snapshot),
18158 is_primary: diagnostic_entry.diagnostic.is_primary,
18159 severity: diagnostic_entry.diagnostic.severity,
18160 },
18161 ),
18162 );
18163 }
18164 inline_diagnostics
18165 })
18166 .await;
18167
18168 editor
18169 .update(cx, |editor, cx| {
18170 editor.inline_diagnostics = new_inline_diagnostics;
18171 cx.notify();
18172 })
18173 .ok();
18174 });
18175 }
18176
18177 fn pull_diagnostics(
18178 &mut self,
18179 buffer_id: Option<BufferId>,
18180 window: &Window,
18181 cx: &mut Context<Self>,
18182 ) -> Option<()> {
18183 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18184 return None;
18185 }
18186 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18187 .diagnostics
18188 .lsp_pull_diagnostics;
18189 if !pull_diagnostics_settings.enabled {
18190 return None;
18191 }
18192 let project = self.project()?.downgrade();
18193 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18194 let mut buffers = self.buffer.read(cx).all_buffers();
18195 buffers.retain(|buffer| {
18196 let buffer_id_to_retain = buffer.read(cx).remote_id();
18197 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18198 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18199 });
18200 if buffers.is_empty() {
18201 self.pull_diagnostics_task = Task::ready(());
18202 return None;
18203 }
18204
18205 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18206 cx.background_executor().timer(debounce).await;
18207
18208 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18209 buffers
18210 .into_iter()
18211 .filter_map(|buffer| {
18212 project
18213 .update(cx, |project, cx| {
18214 project.lsp_store().update(cx, |lsp_store, cx| {
18215 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18216 })
18217 })
18218 .ok()
18219 })
18220 .collect::<FuturesUnordered<_>>()
18221 }) else {
18222 return;
18223 };
18224
18225 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18226 match pull_task {
18227 Ok(()) => {
18228 if editor
18229 .update_in(cx, |editor, window, cx| {
18230 editor.update_diagnostics_state(window, cx);
18231 })
18232 .is_err()
18233 {
18234 return;
18235 }
18236 }
18237 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18238 }
18239 }
18240 });
18241
18242 Some(())
18243 }
18244
18245 pub fn set_selections_from_remote(
18246 &mut self,
18247 selections: Vec<Selection<Anchor>>,
18248 pending_selection: Option<Selection<Anchor>>,
18249 window: &mut Window,
18250 cx: &mut Context<Self>,
18251 ) {
18252 let old_cursor_position = self.selections.newest_anchor().head();
18253 self.selections
18254 .change_with(&self.display_snapshot(cx), |s| {
18255 s.select_anchors(selections);
18256 if let Some(pending_selection) = pending_selection {
18257 s.set_pending(pending_selection, SelectMode::Character);
18258 } else {
18259 s.clear_pending();
18260 }
18261 });
18262 self.selections_did_change(
18263 false,
18264 &old_cursor_position,
18265 SelectionEffects::default(),
18266 window,
18267 cx,
18268 );
18269 }
18270
18271 pub fn transact(
18272 &mut self,
18273 window: &mut Window,
18274 cx: &mut Context<Self>,
18275 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18276 ) -> Option<TransactionId> {
18277 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18278 this.start_transaction_at(Instant::now(), window, cx);
18279 update(this, window, cx);
18280 this.end_transaction_at(Instant::now(), cx)
18281 })
18282 }
18283
18284 pub fn start_transaction_at(
18285 &mut self,
18286 now: Instant,
18287 window: &mut Window,
18288 cx: &mut Context<Self>,
18289 ) -> Option<TransactionId> {
18290 self.end_selection(window, cx);
18291 if let Some(tx_id) = self
18292 .buffer
18293 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18294 {
18295 self.selection_history
18296 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18297 cx.emit(EditorEvent::TransactionBegun {
18298 transaction_id: tx_id,
18299 });
18300 Some(tx_id)
18301 } else {
18302 None
18303 }
18304 }
18305
18306 pub fn end_transaction_at(
18307 &mut self,
18308 now: Instant,
18309 cx: &mut Context<Self>,
18310 ) -> Option<TransactionId> {
18311 if let Some(transaction_id) = self
18312 .buffer
18313 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18314 {
18315 if let Some((_, end_selections)) =
18316 self.selection_history.transaction_mut(transaction_id)
18317 {
18318 *end_selections = Some(self.selections.disjoint_anchors_arc());
18319 } else {
18320 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18321 }
18322
18323 cx.emit(EditorEvent::Edited { transaction_id });
18324 Some(transaction_id)
18325 } else {
18326 None
18327 }
18328 }
18329
18330 pub fn modify_transaction_selection_history(
18331 &mut self,
18332 transaction_id: TransactionId,
18333 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18334 ) -> bool {
18335 self.selection_history
18336 .transaction_mut(transaction_id)
18337 .map(modify)
18338 .is_some()
18339 }
18340
18341 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18342 if self.selection_mark_mode {
18343 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18344 s.move_with(|_, sel| {
18345 sel.collapse_to(sel.head(), SelectionGoal::None);
18346 });
18347 })
18348 }
18349 self.selection_mark_mode = true;
18350 cx.notify();
18351 }
18352
18353 pub fn swap_selection_ends(
18354 &mut self,
18355 _: &actions::SwapSelectionEnds,
18356 window: &mut Window,
18357 cx: &mut Context<Self>,
18358 ) {
18359 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18360 s.move_with(|_, sel| {
18361 if sel.start != sel.end {
18362 sel.reversed = !sel.reversed
18363 }
18364 });
18365 });
18366 self.request_autoscroll(Autoscroll::newest(), cx);
18367 cx.notify();
18368 }
18369
18370 pub fn toggle_focus(
18371 workspace: &mut Workspace,
18372 _: &actions::ToggleFocus,
18373 window: &mut Window,
18374 cx: &mut Context<Workspace>,
18375 ) {
18376 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18377 return;
18378 };
18379 workspace.activate_item(&item, true, true, window, cx);
18380 }
18381
18382 pub fn toggle_fold(
18383 &mut self,
18384 _: &actions::ToggleFold,
18385 window: &mut Window,
18386 cx: &mut Context<Self>,
18387 ) {
18388 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18389 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18390 let selection = self.selections.newest::<Point>(&display_map);
18391
18392 let range = if selection.is_empty() {
18393 let point = selection.head().to_display_point(&display_map);
18394 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18395 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18396 .to_point(&display_map);
18397 start..end
18398 } else {
18399 selection.range()
18400 };
18401 if display_map.folds_in_range(range).next().is_some() {
18402 self.unfold_lines(&Default::default(), window, cx)
18403 } else {
18404 self.fold(&Default::default(), window, cx)
18405 }
18406 } else {
18407 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18408 let buffer_ids: HashSet<_> = self
18409 .selections
18410 .disjoint_anchor_ranges()
18411 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18412 .collect();
18413
18414 let should_unfold = buffer_ids
18415 .iter()
18416 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18417
18418 for buffer_id in buffer_ids {
18419 if should_unfold {
18420 self.unfold_buffer(buffer_id, cx);
18421 } else {
18422 self.fold_buffer(buffer_id, cx);
18423 }
18424 }
18425 }
18426 }
18427
18428 pub fn toggle_fold_recursive(
18429 &mut self,
18430 _: &actions::ToggleFoldRecursive,
18431 window: &mut Window,
18432 cx: &mut Context<Self>,
18433 ) {
18434 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18435
18436 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18437 let range = if selection.is_empty() {
18438 let point = selection.head().to_display_point(&display_map);
18439 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18440 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18441 .to_point(&display_map);
18442 start..end
18443 } else {
18444 selection.range()
18445 };
18446 if display_map.folds_in_range(range).next().is_some() {
18447 self.unfold_recursive(&Default::default(), window, cx)
18448 } else {
18449 self.fold_recursive(&Default::default(), window, cx)
18450 }
18451 }
18452
18453 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18454 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18455 let mut to_fold = Vec::new();
18456 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18457 let selections = self.selections.all_adjusted(&display_map);
18458
18459 for selection in selections {
18460 let range = selection.range().sorted();
18461 let buffer_start_row = range.start.row;
18462
18463 if range.start.row != range.end.row {
18464 let mut found = false;
18465 let mut row = range.start.row;
18466 while row <= range.end.row {
18467 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18468 {
18469 found = true;
18470 row = crease.range().end.row + 1;
18471 to_fold.push(crease);
18472 } else {
18473 row += 1
18474 }
18475 }
18476 if found {
18477 continue;
18478 }
18479 }
18480
18481 for row in (0..=range.start.row).rev() {
18482 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18483 && crease.range().end.row >= buffer_start_row
18484 {
18485 to_fold.push(crease);
18486 if row <= range.start.row {
18487 break;
18488 }
18489 }
18490 }
18491 }
18492
18493 self.fold_creases(to_fold, true, window, cx);
18494 } else {
18495 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18496 let buffer_ids = self
18497 .selections
18498 .disjoint_anchor_ranges()
18499 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18500 .collect::<HashSet<_>>();
18501 for buffer_id in buffer_ids {
18502 self.fold_buffer(buffer_id, cx);
18503 }
18504 }
18505 }
18506
18507 pub fn toggle_fold_all(
18508 &mut self,
18509 _: &actions::ToggleFoldAll,
18510 window: &mut Window,
18511 cx: &mut Context<Self>,
18512 ) {
18513 if self.buffer.read(cx).is_singleton() {
18514 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18515 let has_folds = display_map
18516 .folds_in_range(0..display_map.buffer_snapshot().len())
18517 .next()
18518 .is_some();
18519
18520 if has_folds {
18521 self.unfold_all(&actions::UnfoldAll, window, cx);
18522 } else {
18523 self.fold_all(&actions::FoldAll, window, cx);
18524 }
18525 } else {
18526 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18527 let should_unfold = buffer_ids
18528 .iter()
18529 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18530
18531 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18532 editor
18533 .update_in(cx, |editor, _, cx| {
18534 for buffer_id in buffer_ids {
18535 if should_unfold {
18536 editor.unfold_buffer(buffer_id, cx);
18537 } else {
18538 editor.fold_buffer(buffer_id, cx);
18539 }
18540 }
18541 })
18542 .ok();
18543 });
18544 }
18545 }
18546
18547 fn fold_at_level(
18548 &mut self,
18549 fold_at: &FoldAtLevel,
18550 window: &mut Window,
18551 cx: &mut Context<Self>,
18552 ) {
18553 if !self.buffer.read(cx).is_singleton() {
18554 return;
18555 }
18556
18557 let fold_at_level = fold_at.0;
18558 let snapshot = self.buffer.read(cx).snapshot(cx);
18559 let mut to_fold = Vec::new();
18560 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18561
18562 let row_ranges_to_keep: Vec<Range<u32>> = self
18563 .selections
18564 .all::<Point>(&self.display_snapshot(cx))
18565 .into_iter()
18566 .map(|sel| sel.start.row..sel.end.row)
18567 .collect();
18568
18569 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18570 while start_row < end_row {
18571 match self
18572 .snapshot(window, cx)
18573 .crease_for_buffer_row(MultiBufferRow(start_row))
18574 {
18575 Some(crease) => {
18576 let nested_start_row = crease.range().start.row + 1;
18577 let nested_end_row = crease.range().end.row;
18578
18579 if current_level < fold_at_level {
18580 stack.push((nested_start_row, nested_end_row, current_level + 1));
18581 } else if current_level == fold_at_level {
18582 // Fold iff there is no selection completely contained within the fold region
18583 if !row_ranges_to_keep.iter().any(|selection| {
18584 selection.end >= nested_start_row
18585 && selection.start <= nested_end_row
18586 }) {
18587 to_fold.push(crease);
18588 }
18589 }
18590
18591 start_row = nested_end_row + 1;
18592 }
18593 None => start_row += 1,
18594 }
18595 }
18596 }
18597
18598 self.fold_creases(to_fold, true, window, cx);
18599 }
18600
18601 pub fn fold_at_level_1(
18602 &mut self,
18603 _: &actions::FoldAtLevel1,
18604 window: &mut Window,
18605 cx: &mut Context<Self>,
18606 ) {
18607 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18608 }
18609
18610 pub fn fold_at_level_2(
18611 &mut self,
18612 _: &actions::FoldAtLevel2,
18613 window: &mut Window,
18614 cx: &mut Context<Self>,
18615 ) {
18616 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18617 }
18618
18619 pub fn fold_at_level_3(
18620 &mut self,
18621 _: &actions::FoldAtLevel3,
18622 window: &mut Window,
18623 cx: &mut Context<Self>,
18624 ) {
18625 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18626 }
18627
18628 pub fn fold_at_level_4(
18629 &mut self,
18630 _: &actions::FoldAtLevel4,
18631 window: &mut Window,
18632 cx: &mut Context<Self>,
18633 ) {
18634 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18635 }
18636
18637 pub fn fold_at_level_5(
18638 &mut self,
18639 _: &actions::FoldAtLevel5,
18640 window: &mut Window,
18641 cx: &mut Context<Self>,
18642 ) {
18643 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18644 }
18645
18646 pub fn fold_at_level_6(
18647 &mut self,
18648 _: &actions::FoldAtLevel6,
18649 window: &mut Window,
18650 cx: &mut Context<Self>,
18651 ) {
18652 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18653 }
18654
18655 pub fn fold_at_level_7(
18656 &mut self,
18657 _: &actions::FoldAtLevel7,
18658 window: &mut Window,
18659 cx: &mut Context<Self>,
18660 ) {
18661 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18662 }
18663
18664 pub fn fold_at_level_8(
18665 &mut self,
18666 _: &actions::FoldAtLevel8,
18667 window: &mut Window,
18668 cx: &mut Context<Self>,
18669 ) {
18670 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18671 }
18672
18673 pub fn fold_at_level_9(
18674 &mut self,
18675 _: &actions::FoldAtLevel9,
18676 window: &mut Window,
18677 cx: &mut Context<Self>,
18678 ) {
18679 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18680 }
18681
18682 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18683 if self.buffer.read(cx).is_singleton() {
18684 let mut fold_ranges = Vec::new();
18685 let snapshot = self.buffer.read(cx).snapshot(cx);
18686
18687 for row in 0..snapshot.max_row().0 {
18688 if let Some(foldable_range) = self
18689 .snapshot(window, cx)
18690 .crease_for_buffer_row(MultiBufferRow(row))
18691 {
18692 fold_ranges.push(foldable_range);
18693 }
18694 }
18695
18696 self.fold_creases(fold_ranges, true, window, cx);
18697 } else {
18698 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18699 editor
18700 .update_in(cx, |editor, _, cx| {
18701 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18702 editor.fold_buffer(buffer_id, cx);
18703 }
18704 })
18705 .ok();
18706 });
18707 }
18708 }
18709
18710 pub fn fold_function_bodies(
18711 &mut self,
18712 _: &actions::FoldFunctionBodies,
18713 window: &mut Window,
18714 cx: &mut Context<Self>,
18715 ) {
18716 let snapshot = self.buffer.read(cx).snapshot(cx);
18717
18718 let ranges = snapshot
18719 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18720 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18721 .collect::<Vec<_>>();
18722
18723 let creases = ranges
18724 .into_iter()
18725 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18726 .collect();
18727
18728 self.fold_creases(creases, true, window, cx);
18729 }
18730
18731 pub fn fold_recursive(
18732 &mut self,
18733 _: &actions::FoldRecursive,
18734 window: &mut Window,
18735 cx: &mut Context<Self>,
18736 ) {
18737 let mut to_fold = Vec::new();
18738 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18739 let selections = self.selections.all_adjusted(&display_map);
18740
18741 for selection in selections {
18742 let range = selection.range().sorted();
18743 let buffer_start_row = range.start.row;
18744
18745 if range.start.row != range.end.row {
18746 let mut found = false;
18747 for row in range.start.row..=range.end.row {
18748 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18749 found = true;
18750 to_fold.push(crease);
18751 }
18752 }
18753 if found {
18754 continue;
18755 }
18756 }
18757
18758 for row in (0..=range.start.row).rev() {
18759 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18760 if crease.range().end.row >= buffer_start_row {
18761 to_fold.push(crease);
18762 } else {
18763 break;
18764 }
18765 }
18766 }
18767 }
18768
18769 self.fold_creases(to_fold, true, window, cx);
18770 }
18771
18772 pub fn fold_at(
18773 &mut self,
18774 buffer_row: MultiBufferRow,
18775 window: &mut Window,
18776 cx: &mut Context<Self>,
18777 ) {
18778 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18779
18780 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18781 let autoscroll = self
18782 .selections
18783 .all::<Point>(&display_map)
18784 .iter()
18785 .any(|selection| crease.range().overlaps(&selection.range()));
18786
18787 self.fold_creases(vec![crease], autoscroll, window, cx);
18788 }
18789 }
18790
18791 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18792 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18793 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18794 let buffer = display_map.buffer_snapshot();
18795 let selections = self.selections.all::<Point>(&display_map);
18796 let ranges = selections
18797 .iter()
18798 .map(|s| {
18799 let range = s.display_range(&display_map).sorted();
18800 let mut start = range.start.to_point(&display_map);
18801 let mut end = range.end.to_point(&display_map);
18802 start.column = 0;
18803 end.column = buffer.line_len(MultiBufferRow(end.row));
18804 start..end
18805 })
18806 .collect::<Vec<_>>();
18807
18808 self.unfold_ranges(&ranges, true, true, cx);
18809 } else {
18810 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18811 let buffer_ids = self
18812 .selections
18813 .disjoint_anchor_ranges()
18814 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18815 .collect::<HashSet<_>>();
18816 for buffer_id in buffer_ids {
18817 self.unfold_buffer(buffer_id, cx);
18818 }
18819 }
18820 }
18821
18822 pub fn unfold_recursive(
18823 &mut self,
18824 _: &UnfoldRecursive,
18825 _window: &mut Window,
18826 cx: &mut Context<Self>,
18827 ) {
18828 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18829 let selections = self.selections.all::<Point>(&display_map);
18830 let ranges = selections
18831 .iter()
18832 .map(|s| {
18833 let mut range = s.display_range(&display_map).sorted();
18834 *range.start.column_mut() = 0;
18835 *range.end.column_mut() = display_map.line_len(range.end.row());
18836 let start = range.start.to_point(&display_map);
18837 let end = range.end.to_point(&display_map);
18838 start..end
18839 })
18840 .collect::<Vec<_>>();
18841
18842 self.unfold_ranges(&ranges, true, true, cx);
18843 }
18844
18845 pub fn unfold_at(
18846 &mut self,
18847 buffer_row: MultiBufferRow,
18848 _window: &mut Window,
18849 cx: &mut Context<Self>,
18850 ) {
18851 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18852
18853 let intersection_range = Point::new(buffer_row.0, 0)
18854 ..Point::new(
18855 buffer_row.0,
18856 display_map.buffer_snapshot().line_len(buffer_row),
18857 );
18858
18859 let autoscroll = self
18860 .selections
18861 .all::<Point>(&display_map)
18862 .iter()
18863 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18864
18865 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18866 }
18867
18868 pub fn unfold_all(
18869 &mut self,
18870 _: &actions::UnfoldAll,
18871 _window: &mut Window,
18872 cx: &mut Context<Self>,
18873 ) {
18874 if self.buffer.read(cx).is_singleton() {
18875 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18876 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18877 } else {
18878 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18879 editor
18880 .update(cx, |editor, cx| {
18881 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18882 editor.unfold_buffer(buffer_id, cx);
18883 }
18884 })
18885 .ok();
18886 });
18887 }
18888 }
18889
18890 pub fn fold_selected_ranges(
18891 &mut self,
18892 _: &FoldSelectedRanges,
18893 window: &mut Window,
18894 cx: &mut Context<Self>,
18895 ) {
18896 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18897 let selections = self.selections.all_adjusted(&display_map);
18898 let ranges = selections
18899 .into_iter()
18900 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18901 .collect::<Vec<_>>();
18902 self.fold_creases(ranges, true, window, cx);
18903 }
18904
18905 pub fn fold_ranges<T: ToOffset + Clone>(
18906 &mut self,
18907 ranges: Vec<Range<T>>,
18908 auto_scroll: bool,
18909 window: &mut Window,
18910 cx: &mut Context<Self>,
18911 ) {
18912 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18913 let ranges = ranges
18914 .into_iter()
18915 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18916 .collect::<Vec<_>>();
18917 self.fold_creases(ranges, auto_scroll, window, cx);
18918 }
18919
18920 pub fn fold_creases<T: ToOffset + Clone>(
18921 &mut self,
18922 creases: Vec<Crease<T>>,
18923 auto_scroll: bool,
18924 _window: &mut Window,
18925 cx: &mut Context<Self>,
18926 ) {
18927 if creases.is_empty() {
18928 return;
18929 }
18930
18931 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18932
18933 if auto_scroll {
18934 self.request_autoscroll(Autoscroll::fit(), cx);
18935 }
18936
18937 cx.notify();
18938
18939 self.scrollbar_marker_state.dirty = true;
18940 self.folds_did_change(cx);
18941 }
18942
18943 /// Removes any folds whose ranges intersect any of the given ranges.
18944 pub fn unfold_ranges<T: ToOffset + Clone>(
18945 &mut self,
18946 ranges: &[Range<T>],
18947 inclusive: bool,
18948 auto_scroll: bool,
18949 cx: &mut Context<Self>,
18950 ) {
18951 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18952 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18953 });
18954 self.folds_did_change(cx);
18955 }
18956
18957 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18958 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18959 return;
18960 }
18961
18962 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18963 self.display_map.update(cx, |display_map, cx| {
18964 display_map.fold_buffers([buffer_id], cx)
18965 });
18966
18967 let snapshot = self.display_snapshot(cx);
18968 self.selections.change_with(&snapshot, |selections| {
18969 selections.remove_selections_from_buffer(buffer_id);
18970 });
18971
18972 cx.emit(EditorEvent::BufferFoldToggled {
18973 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18974 folded: true,
18975 });
18976 cx.notify();
18977 }
18978
18979 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18980 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18981 return;
18982 }
18983 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18984 self.display_map.update(cx, |display_map, cx| {
18985 display_map.unfold_buffers([buffer_id], cx);
18986 });
18987 cx.emit(EditorEvent::BufferFoldToggled {
18988 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18989 folded: false,
18990 });
18991 cx.notify();
18992 }
18993
18994 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18995 self.display_map.read(cx).is_buffer_folded(buffer)
18996 }
18997
18998 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18999 self.display_map.read(cx).folded_buffers()
19000 }
19001
19002 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19003 self.display_map.update(cx, |display_map, cx| {
19004 display_map.disable_header_for_buffer(buffer_id, cx);
19005 });
19006 cx.notify();
19007 }
19008
19009 /// Removes any folds with the given ranges.
19010 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19011 &mut self,
19012 ranges: &[Range<T>],
19013 type_id: TypeId,
19014 auto_scroll: bool,
19015 cx: &mut Context<Self>,
19016 ) {
19017 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19018 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19019 });
19020 self.folds_did_change(cx);
19021 }
19022
19023 fn remove_folds_with<T: ToOffset + Clone>(
19024 &mut self,
19025 ranges: &[Range<T>],
19026 auto_scroll: bool,
19027 cx: &mut Context<Self>,
19028 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19029 ) {
19030 if ranges.is_empty() {
19031 return;
19032 }
19033
19034 let mut buffers_affected = HashSet::default();
19035 let multi_buffer = self.buffer().read(cx);
19036 for range in ranges {
19037 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19038 buffers_affected.insert(buffer.read(cx).remote_id());
19039 };
19040 }
19041
19042 self.display_map.update(cx, update);
19043
19044 if auto_scroll {
19045 self.request_autoscroll(Autoscroll::fit(), cx);
19046 }
19047
19048 cx.notify();
19049 self.scrollbar_marker_state.dirty = true;
19050 self.active_indent_guides_state.dirty = true;
19051 }
19052
19053 pub fn update_renderer_widths(
19054 &mut self,
19055 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19056 cx: &mut Context<Self>,
19057 ) -> bool {
19058 self.display_map
19059 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19060 }
19061
19062 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19063 self.display_map.read(cx).fold_placeholder.clone()
19064 }
19065
19066 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19067 self.buffer.update(cx, |buffer, cx| {
19068 buffer.set_all_diff_hunks_expanded(cx);
19069 });
19070 }
19071
19072 pub fn expand_all_diff_hunks(
19073 &mut self,
19074 _: &ExpandAllDiffHunks,
19075 _window: &mut Window,
19076 cx: &mut Context<Self>,
19077 ) {
19078 self.buffer.update(cx, |buffer, cx| {
19079 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19080 });
19081 }
19082
19083 pub fn collapse_all_diff_hunks(
19084 &mut self,
19085 _: &CollapseAllDiffHunks,
19086 _window: &mut Window,
19087 cx: &mut Context<Self>,
19088 ) {
19089 self.buffer.update(cx, |buffer, cx| {
19090 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19091 });
19092 }
19093
19094 pub fn toggle_selected_diff_hunks(
19095 &mut self,
19096 _: &ToggleSelectedDiffHunks,
19097 _window: &mut Window,
19098 cx: &mut Context<Self>,
19099 ) {
19100 let ranges: Vec<_> = self
19101 .selections
19102 .disjoint_anchors()
19103 .iter()
19104 .map(|s| s.range())
19105 .collect();
19106 self.toggle_diff_hunks_in_ranges(ranges, cx);
19107 }
19108
19109 pub fn diff_hunks_in_ranges<'a>(
19110 &'a self,
19111 ranges: &'a [Range<Anchor>],
19112 buffer: &'a MultiBufferSnapshot,
19113 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19114 ranges.iter().flat_map(move |range| {
19115 let end_excerpt_id = range.end.excerpt_id;
19116 let range = range.to_point(buffer);
19117 let mut peek_end = range.end;
19118 if range.end.row < buffer.max_row().0 {
19119 peek_end = Point::new(range.end.row + 1, 0);
19120 }
19121 buffer
19122 .diff_hunks_in_range(range.start..peek_end)
19123 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19124 })
19125 }
19126
19127 pub fn has_stageable_diff_hunks_in_ranges(
19128 &self,
19129 ranges: &[Range<Anchor>],
19130 snapshot: &MultiBufferSnapshot,
19131 ) -> bool {
19132 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19133 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19134 }
19135
19136 pub fn toggle_staged_selected_diff_hunks(
19137 &mut self,
19138 _: &::git::ToggleStaged,
19139 _: &mut Window,
19140 cx: &mut Context<Self>,
19141 ) {
19142 let snapshot = self.buffer.read(cx).snapshot(cx);
19143 let ranges: Vec<_> = self
19144 .selections
19145 .disjoint_anchors()
19146 .iter()
19147 .map(|s| s.range())
19148 .collect();
19149 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19150 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19151 }
19152
19153 pub fn set_render_diff_hunk_controls(
19154 &mut self,
19155 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19156 cx: &mut Context<Self>,
19157 ) {
19158 self.render_diff_hunk_controls = render_diff_hunk_controls;
19159 cx.notify();
19160 }
19161
19162 pub fn stage_and_next(
19163 &mut self,
19164 _: &::git::StageAndNext,
19165 window: &mut Window,
19166 cx: &mut Context<Self>,
19167 ) {
19168 self.do_stage_or_unstage_and_next(true, window, cx);
19169 }
19170
19171 pub fn unstage_and_next(
19172 &mut self,
19173 _: &::git::UnstageAndNext,
19174 window: &mut Window,
19175 cx: &mut Context<Self>,
19176 ) {
19177 self.do_stage_or_unstage_and_next(false, window, cx);
19178 }
19179
19180 pub fn stage_or_unstage_diff_hunks(
19181 &mut self,
19182 stage: bool,
19183 ranges: Vec<Range<Anchor>>,
19184 cx: &mut Context<Self>,
19185 ) {
19186 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19187 cx.spawn(async move |this, cx| {
19188 task.await?;
19189 this.update(cx, |this, cx| {
19190 let snapshot = this.buffer.read(cx).snapshot(cx);
19191 let chunk_by = this
19192 .diff_hunks_in_ranges(&ranges, &snapshot)
19193 .chunk_by(|hunk| hunk.buffer_id);
19194 for (buffer_id, hunks) in &chunk_by {
19195 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19196 }
19197 })
19198 })
19199 .detach_and_log_err(cx);
19200 }
19201
19202 fn save_buffers_for_ranges_if_needed(
19203 &mut self,
19204 ranges: &[Range<Anchor>],
19205 cx: &mut Context<Editor>,
19206 ) -> Task<Result<()>> {
19207 let multibuffer = self.buffer.read(cx);
19208 let snapshot = multibuffer.read(cx);
19209 let buffer_ids: HashSet<_> = ranges
19210 .iter()
19211 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19212 .collect();
19213 drop(snapshot);
19214
19215 let mut buffers = HashSet::default();
19216 for buffer_id in buffer_ids {
19217 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19218 let buffer = buffer_entity.read(cx);
19219 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19220 {
19221 buffers.insert(buffer_entity);
19222 }
19223 }
19224 }
19225
19226 if let Some(project) = &self.project {
19227 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19228 } else {
19229 Task::ready(Ok(()))
19230 }
19231 }
19232
19233 fn do_stage_or_unstage_and_next(
19234 &mut self,
19235 stage: bool,
19236 window: &mut Window,
19237 cx: &mut Context<Self>,
19238 ) {
19239 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19240
19241 if ranges.iter().any(|range| range.start != range.end) {
19242 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19243 return;
19244 }
19245
19246 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19247 let snapshot = self.snapshot(window, cx);
19248 let position = self
19249 .selections
19250 .newest::<Point>(&snapshot.display_snapshot)
19251 .head();
19252 let mut row = snapshot
19253 .buffer_snapshot()
19254 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19255 .find(|hunk| hunk.row_range.start.0 > position.row)
19256 .map(|hunk| hunk.row_range.start);
19257
19258 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19259 // Outside of the project diff editor, wrap around to the beginning.
19260 if !all_diff_hunks_expanded {
19261 row = row.or_else(|| {
19262 snapshot
19263 .buffer_snapshot()
19264 .diff_hunks_in_range(Point::zero()..position)
19265 .find(|hunk| hunk.row_range.end.0 < position.row)
19266 .map(|hunk| hunk.row_range.start)
19267 });
19268 }
19269
19270 if let Some(row) = row {
19271 let destination = Point::new(row.0, 0);
19272 let autoscroll = Autoscroll::center();
19273
19274 self.unfold_ranges(&[destination..destination], false, false, cx);
19275 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19276 s.select_ranges([destination..destination]);
19277 });
19278 }
19279 }
19280
19281 fn do_stage_or_unstage(
19282 &self,
19283 stage: bool,
19284 buffer_id: BufferId,
19285 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19286 cx: &mut App,
19287 ) -> Option<()> {
19288 let project = self.project()?;
19289 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19290 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19291 let buffer_snapshot = buffer.read(cx).snapshot();
19292 let file_exists = buffer_snapshot
19293 .file()
19294 .is_some_and(|file| file.disk_state().exists());
19295 diff.update(cx, |diff, cx| {
19296 diff.stage_or_unstage_hunks(
19297 stage,
19298 &hunks
19299 .map(|hunk| buffer_diff::DiffHunk {
19300 buffer_range: hunk.buffer_range,
19301 diff_base_byte_range: hunk.diff_base_byte_range,
19302 secondary_status: hunk.secondary_status,
19303 range: Point::zero()..Point::zero(), // unused
19304 })
19305 .collect::<Vec<_>>(),
19306 &buffer_snapshot,
19307 file_exists,
19308 cx,
19309 )
19310 });
19311 None
19312 }
19313
19314 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19315 let ranges: Vec<_> = self
19316 .selections
19317 .disjoint_anchors()
19318 .iter()
19319 .map(|s| s.range())
19320 .collect();
19321 self.buffer
19322 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19323 }
19324
19325 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19326 self.buffer.update(cx, |buffer, cx| {
19327 let ranges = vec![Anchor::min()..Anchor::max()];
19328 if !buffer.all_diff_hunks_expanded()
19329 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19330 {
19331 buffer.collapse_diff_hunks(ranges, cx);
19332 true
19333 } else {
19334 false
19335 }
19336 })
19337 }
19338
19339 fn toggle_diff_hunks_in_ranges(
19340 &mut self,
19341 ranges: Vec<Range<Anchor>>,
19342 cx: &mut Context<Editor>,
19343 ) {
19344 self.buffer.update(cx, |buffer, cx| {
19345 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19346 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19347 })
19348 }
19349
19350 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19351 self.buffer.update(cx, |buffer, cx| {
19352 let snapshot = buffer.snapshot(cx);
19353 let excerpt_id = range.end.excerpt_id;
19354 let point_range = range.to_point(&snapshot);
19355 let expand = !buffer.single_hunk_is_expanded(range, cx);
19356 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19357 })
19358 }
19359
19360 pub(crate) fn apply_all_diff_hunks(
19361 &mut self,
19362 _: &ApplyAllDiffHunks,
19363 window: &mut Window,
19364 cx: &mut Context<Self>,
19365 ) {
19366 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19367
19368 let buffers = self.buffer.read(cx).all_buffers();
19369 for branch_buffer in buffers {
19370 branch_buffer.update(cx, |branch_buffer, cx| {
19371 branch_buffer.merge_into_base(Vec::new(), cx);
19372 });
19373 }
19374
19375 if let Some(project) = self.project.clone() {
19376 self.save(
19377 SaveOptions {
19378 format: true,
19379 autosave: false,
19380 },
19381 project,
19382 window,
19383 cx,
19384 )
19385 .detach_and_log_err(cx);
19386 }
19387 }
19388
19389 pub(crate) fn apply_selected_diff_hunks(
19390 &mut self,
19391 _: &ApplyDiffHunk,
19392 window: &mut Window,
19393 cx: &mut Context<Self>,
19394 ) {
19395 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19396 let snapshot = self.snapshot(window, cx);
19397 let hunks = snapshot.hunks_for_ranges(
19398 self.selections
19399 .all(&snapshot.display_snapshot)
19400 .into_iter()
19401 .map(|selection| selection.range()),
19402 );
19403 let mut ranges_by_buffer = HashMap::default();
19404 self.transact(window, cx, |editor, _window, cx| {
19405 for hunk in hunks {
19406 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19407 ranges_by_buffer
19408 .entry(buffer.clone())
19409 .or_insert_with(Vec::new)
19410 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19411 }
19412 }
19413
19414 for (buffer, ranges) in ranges_by_buffer {
19415 buffer.update(cx, |buffer, cx| {
19416 buffer.merge_into_base(ranges, cx);
19417 });
19418 }
19419 });
19420
19421 if let Some(project) = self.project.clone() {
19422 self.save(
19423 SaveOptions {
19424 format: true,
19425 autosave: false,
19426 },
19427 project,
19428 window,
19429 cx,
19430 )
19431 .detach_and_log_err(cx);
19432 }
19433 }
19434
19435 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19436 if hovered != self.gutter_hovered {
19437 self.gutter_hovered = hovered;
19438 cx.notify();
19439 }
19440 }
19441
19442 pub fn insert_blocks(
19443 &mut self,
19444 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19445 autoscroll: Option<Autoscroll>,
19446 cx: &mut Context<Self>,
19447 ) -> Vec<CustomBlockId> {
19448 let blocks = self
19449 .display_map
19450 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19451 if let Some(autoscroll) = autoscroll {
19452 self.request_autoscroll(autoscroll, cx);
19453 }
19454 cx.notify();
19455 blocks
19456 }
19457
19458 pub fn resize_blocks(
19459 &mut self,
19460 heights: HashMap<CustomBlockId, u32>,
19461 autoscroll: Option<Autoscroll>,
19462 cx: &mut Context<Self>,
19463 ) {
19464 self.display_map
19465 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19466 if let Some(autoscroll) = autoscroll {
19467 self.request_autoscroll(autoscroll, cx);
19468 }
19469 cx.notify();
19470 }
19471
19472 pub fn replace_blocks(
19473 &mut self,
19474 renderers: HashMap<CustomBlockId, RenderBlock>,
19475 autoscroll: Option<Autoscroll>,
19476 cx: &mut Context<Self>,
19477 ) {
19478 self.display_map
19479 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19480 if let Some(autoscroll) = autoscroll {
19481 self.request_autoscroll(autoscroll, cx);
19482 }
19483 cx.notify();
19484 }
19485
19486 pub fn remove_blocks(
19487 &mut self,
19488 block_ids: HashSet<CustomBlockId>,
19489 autoscroll: Option<Autoscroll>,
19490 cx: &mut Context<Self>,
19491 ) {
19492 self.display_map.update(cx, |display_map, cx| {
19493 display_map.remove_blocks(block_ids, cx)
19494 });
19495 if let Some(autoscroll) = autoscroll {
19496 self.request_autoscroll(autoscroll, cx);
19497 }
19498 cx.notify();
19499 }
19500
19501 pub fn row_for_block(
19502 &self,
19503 block_id: CustomBlockId,
19504 cx: &mut Context<Self>,
19505 ) -> Option<DisplayRow> {
19506 self.display_map
19507 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19508 }
19509
19510 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19511 self.focused_block = Some(focused_block);
19512 }
19513
19514 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19515 self.focused_block.take()
19516 }
19517
19518 pub fn insert_creases(
19519 &mut self,
19520 creases: impl IntoIterator<Item = Crease<Anchor>>,
19521 cx: &mut Context<Self>,
19522 ) -> Vec<CreaseId> {
19523 self.display_map
19524 .update(cx, |map, cx| map.insert_creases(creases, cx))
19525 }
19526
19527 pub fn remove_creases(
19528 &mut self,
19529 ids: impl IntoIterator<Item = CreaseId>,
19530 cx: &mut Context<Self>,
19531 ) -> Vec<(CreaseId, Range<Anchor>)> {
19532 self.display_map
19533 .update(cx, |map, cx| map.remove_creases(ids, cx))
19534 }
19535
19536 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19537 self.display_map
19538 .update(cx, |map, cx| map.snapshot(cx))
19539 .longest_row()
19540 }
19541
19542 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19543 self.display_map
19544 .update(cx, |map, cx| map.snapshot(cx))
19545 .max_point()
19546 }
19547
19548 pub fn text(&self, cx: &App) -> String {
19549 self.buffer.read(cx).read(cx).text()
19550 }
19551
19552 pub fn is_empty(&self, cx: &App) -> bool {
19553 self.buffer.read(cx).read(cx).is_empty()
19554 }
19555
19556 pub fn text_option(&self, cx: &App) -> Option<String> {
19557 let text = self.text(cx);
19558 let text = text.trim();
19559
19560 if text.is_empty() {
19561 return None;
19562 }
19563
19564 Some(text.to_string())
19565 }
19566
19567 pub fn set_text(
19568 &mut self,
19569 text: impl Into<Arc<str>>,
19570 window: &mut Window,
19571 cx: &mut Context<Self>,
19572 ) {
19573 self.transact(window, cx, |this, _, cx| {
19574 this.buffer
19575 .read(cx)
19576 .as_singleton()
19577 .expect("you can only call set_text on editors for singleton buffers")
19578 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19579 });
19580 }
19581
19582 pub fn display_text(&self, cx: &mut App) -> String {
19583 self.display_map
19584 .update(cx, |map, cx| map.snapshot(cx))
19585 .text()
19586 }
19587
19588 fn create_minimap(
19589 &self,
19590 minimap_settings: MinimapSettings,
19591 window: &mut Window,
19592 cx: &mut Context<Self>,
19593 ) -> Option<Entity<Self>> {
19594 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19595 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19596 }
19597
19598 fn initialize_new_minimap(
19599 &self,
19600 minimap_settings: MinimapSettings,
19601 window: &mut Window,
19602 cx: &mut Context<Self>,
19603 ) -> Entity<Self> {
19604 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19605
19606 let mut minimap = Editor::new_internal(
19607 EditorMode::Minimap {
19608 parent: cx.weak_entity(),
19609 },
19610 self.buffer.clone(),
19611 None,
19612 Some(self.display_map.clone()),
19613 window,
19614 cx,
19615 );
19616 minimap.scroll_manager.clone_state(&self.scroll_manager);
19617 minimap.set_text_style_refinement(TextStyleRefinement {
19618 font_size: Some(MINIMAP_FONT_SIZE),
19619 font_weight: Some(MINIMAP_FONT_WEIGHT),
19620 ..Default::default()
19621 });
19622 minimap.update_minimap_configuration(minimap_settings, cx);
19623 cx.new(|_| minimap)
19624 }
19625
19626 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19627 let current_line_highlight = minimap_settings
19628 .current_line_highlight
19629 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19630 self.set_current_line_highlight(Some(current_line_highlight));
19631 }
19632
19633 pub fn minimap(&self) -> Option<&Entity<Self>> {
19634 self.minimap
19635 .as_ref()
19636 .filter(|_| self.minimap_visibility.visible())
19637 }
19638
19639 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19640 let mut wrap_guides = smallvec![];
19641
19642 if self.show_wrap_guides == Some(false) {
19643 return wrap_guides;
19644 }
19645
19646 let settings = self.buffer.read(cx).language_settings(cx);
19647 if settings.show_wrap_guides {
19648 match self.soft_wrap_mode(cx) {
19649 SoftWrap::Column(soft_wrap) => {
19650 wrap_guides.push((soft_wrap as usize, true));
19651 }
19652 SoftWrap::Bounded(soft_wrap) => {
19653 wrap_guides.push((soft_wrap as usize, true));
19654 }
19655 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19656 }
19657 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19658 }
19659
19660 wrap_guides
19661 }
19662
19663 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19664 let settings = self.buffer.read(cx).language_settings(cx);
19665 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19666 match mode {
19667 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19668 SoftWrap::None
19669 }
19670 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19671 language_settings::SoftWrap::PreferredLineLength => {
19672 SoftWrap::Column(settings.preferred_line_length)
19673 }
19674 language_settings::SoftWrap::Bounded => {
19675 SoftWrap::Bounded(settings.preferred_line_length)
19676 }
19677 }
19678 }
19679
19680 pub fn set_soft_wrap_mode(
19681 &mut self,
19682 mode: language_settings::SoftWrap,
19683
19684 cx: &mut Context<Self>,
19685 ) {
19686 self.soft_wrap_mode_override = Some(mode);
19687 cx.notify();
19688 }
19689
19690 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19691 self.hard_wrap = hard_wrap;
19692 cx.notify();
19693 }
19694
19695 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19696 self.text_style_refinement = Some(style);
19697 }
19698
19699 /// called by the Element so we know what style we were most recently rendered with.
19700 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19701 // We intentionally do not inform the display map about the minimap style
19702 // so that wrapping is not recalculated and stays consistent for the editor
19703 // and its linked minimap.
19704 if !self.mode.is_minimap() {
19705 let font = style.text.font();
19706 let font_size = style.text.font_size.to_pixels(window.rem_size());
19707 let display_map = self
19708 .placeholder_display_map
19709 .as_ref()
19710 .filter(|_| self.is_empty(cx))
19711 .unwrap_or(&self.display_map);
19712
19713 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19714 }
19715 self.style = Some(style);
19716 }
19717
19718 pub fn style(&self) -> Option<&EditorStyle> {
19719 self.style.as_ref()
19720 }
19721
19722 // Called by the element. This method is not designed to be called outside of the editor
19723 // element's layout code because it does not notify when rewrapping is computed synchronously.
19724 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19725 if self.is_empty(cx) {
19726 self.placeholder_display_map
19727 .as_ref()
19728 .map_or(false, |display_map| {
19729 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19730 })
19731 } else {
19732 self.display_map
19733 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19734 }
19735 }
19736
19737 pub fn set_soft_wrap(&mut self) {
19738 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19739 }
19740
19741 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19742 if self.soft_wrap_mode_override.is_some() {
19743 self.soft_wrap_mode_override.take();
19744 } else {
19745 let soft_wrap = match self.soft_wrap_mode(cx) {
19746 SoftWrap::GitDiff => return,
19747 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19748 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19749 language_settings::SoftWrap::None
19750 }
19751 };
19752 self.soft_wrap_mode_override = Some(soft_wrap);
19753 }
19754 cx.notify();
19755 }
19756
19757 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19758 let Some(workspace) = self.workspace() else {
19759 return;
19760 };
19761 let fs = workspace.read(cx).app_state().fs.clone();
19762 let current_show = TabBarSettings::get_global(cx).show;
19763 update_settings_file(fs, cx, move |setting, _| {
19764 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19765 });
19766 }
19767
19768 pub fn toggle_indent_guides(
19769 &mut self,
19770 _: &ToggleIndentGuides,
19771 _: &mut Window,
19772 cx: &mut Context<Self>,
19773 ) {
19774 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19775 self.buffer
19776 .read(cx)
19777 .language_settings(cx)
19778 .indent_guides
19779 .enabled
19780 });
19781 self.show_indent_guides = Some(!currently_enabled);
19782 cx.notify();
19783 }
19784
19785 fn should_show_indent_guides(&self) -> Option<bool> {
19786 self.show_indent_guides
19787 }
19788
19789 pub fn toggle_line_numbers(
19790 &mut self,
19791 _: &ToggleLineNumbers,
19792 _: &mut Window,
19793 cx: &mut Context<Self>,
19794 ) {
19795 let mut editor_settings = EditorSettings::get_global(cx).clone();
19796 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19797 EditorSettings::override_global(editor_settings, cx);
19798 }
19799
19800 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19801 if let Some(show_line_numbers) = self.show_line_numbers {
19802 return show_line_numbers;
19803 }
19804 EditorSettings::get_global(cx).gutter.line_numbers
19805 }
19806
19807 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19808 match (
19809 self.use_relative_line_numbers,
19810 EditorSettings::get_global(cx).relative_line_numbers,
19811 ) {
19812 (None, setting) => setting,
19813 (Some(false), _) => RelativeLineNumbers::Disabled,
19814 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19815 (Some(true), _) => RelativeLineNumbers::Enabled,
19816 }
19817 }
19818
19819 pub fn toggle_relative_line_numbers(
19820 &mut self,
19821 _: &ToggleRelativeLineNumbers,
19822 _: &mut Window,
19823 cx: &mut Context<Self>,
19824 ) {
19825 let is_relative = self.relative_line_numbers(cx);
19826 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19827 }
19828
19829 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19830 self.use_relative_line_numbers = is_relative;
19831 cx.notify();
19832 }
19833
19834 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19835 self.show_gutter = show_gutter;
19836 cx.notify();
19837 }
19838
19839 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19840 self.show_scrollbars = ScrollbarAxes {
19841 horizontal: show,
19842 vertical: show,
19843 };
19844 cx.notify();
19845 }
19846
19847 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19848 self.show_scrollbars.vertical = show;
19849 cx.notify();
19850 }
19851
19852 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19853 self.show_scrollbars.horizontal = show;
19854 cx.notify();
19855 }
19856
19857 pub fn set_minimap_visibility(
19858 &mut self,
19859 minimap_visibility: MinimapVisibility,
19860 window: &mut Window,
19861 cx: &mut Context<Self>,
19862 ) {
19863 if self.minimap_visibility != minimap_visibility {
19864 if minimap_visibility.visible() && self.minimap.is_none() {
19865 let minimap_settings = EditorSettings::get_global(cx).minimap;
19866 self.minimap =
19867 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19868 }
19869 self.minimap_visibility = minimap_visibility;
19870 cx.notify();
19871 }
19872 }
19873
19874 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19875 self.set_show_scrollbars(false, cx);
19876 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19877 }
19878
19879 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19880 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19881 }
19882
19883 /// Normally the text in full mode and auto height editors is padded on the
19884 /// left side by roughly half a character width for improved hit testing.
19885 ///
19886 /// Use this method to disable this for cases where this is not wanted (e.g.
19887 /// if you want to align the editor text with some other text above or below)
19888 /// or if you want to add this padding to single-line editors.
19889 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19890 self.offset_content = offset_content;
19891 cx.notify();
19892 }
19893
19894 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19895 self.show_line_numbers = Some(show_line_numbers);
19896 cx.notify();
19897 }
19898
19899 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19900 self.disable_expand_excerpt_buttons = true;
19901 cx.notify();
19902 }
19903
19904 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19905 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19906 cx.notify();
19907 }
19908
19909 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19910 self.show_code_actions = Some(show_code_actions);
19911 cx.notify();
19912 }
19913
19914 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19915 self.show_runnables = Some(show_runnables);
19916 cx.notify();
19917 }
19918
19919 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19920 self.show_breakpoints = Some(show_breakpoints);
19921 cx.notify();
19922 }
19923
19924 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19925 if self.display_map.read(cx).masked != masked {
19926 self.display_map.update(cx, |map, _| map.masked = masked);
19927 }
19928 cx.notify()
19929 }
19930
19931 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19932 self.show_wrap_guides = Some(show_wrap_guides);
19933 cx.notify();
19934 }
19935
19936 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19937 self.show_indent_guides = Some(show_indent_guides);
19938 cx.notify();
19939 }
19940
19941 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19942 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19943 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19944 && let Some(dir) = file.abs_path(cx).parent()
19945 {
19946 return Some(dir.to_owned());
19947 }
19948 }
19949
19950 None
19951 }
19952
19953 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19954 self.active_excerpt(cx)?
19955 .1
19956 .read(cx)
19957 .file()
19958 .and_then(|f| f.as_local())
19959 }
19960
19961 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19962 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19963 let buffer = buffer.read(cx);
19964 if let Some(project_path) = buffer.project_path(cx) {
19965 let project = self.project()?.read(cx);
19966 project.absolute_path(&project_path, cx)
19967 } else {
19968 buffer
19969 .file()
19970 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19971 }
19972 })
19973 }
19974
19975 pub fn reveal_in_finder(
19976 &mut self,
19977 _: &RevealInFileManager,
19978 _window: &mut Window,
19979 cx: &mut Context<Self>,
19980 ) {
19981 if let Some(target) = self.target_file(cx) {
19982 cx.reveal_path(&target.abs_path(cx));
19983 }
19984 }
19985
19986 pub fn copy_path(
19987 &mut self,
19988 _: &zed_actions::workspace::CopyPath,
19989 _window: &mut Window,
19990 cx: &mut Context<Self>,
19991 ) {
19992 if let Some(path) = self.target_file_abs_path(cx)
19993 && let Some(path) = path.to_str()
19994 {
19995 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19996 } else {
19997 cx.propagate();
19998 }
19999 }
20000
20001 pub fn copy_relative_path(
20002 &mut self,
20003 _: &zed_actions::workspace::CopyRelativePath,
20004 _window: &mut Window,
20005 cx: &mut Context<Self>,
20006 ) {
20007 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20008 let project = self.project()?.read(cx);
20009 let path = buffer.read(cx).file()?.path();
20010 let path = path.display(project.path_style(cx));
20011 Some(path)
20012 }) {
20013 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20014 } else {
20015 cx.propagate();
20016 }
20017 }
20018
20019 /// Returns the project path for the editor's buffer, if any buffer is
20020 /// opened in the editor.
20021 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20022 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20023 buffer.read(cx).project_path(cx)
20024 } else {
20025 None
20026 }
20027 }
20028
20029 // Returns true if the editor handled a go-to-line request
20030 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20031 maybe!({
20032 let breakpoint_store = self.breakpoint_store.as_ref()?;
20033
20034 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20035 else {
20036 self.clear_row_highlights::<ActiveDebugLine>();
20037 return None;
20038 };
20039
20040 let position = active_stack_frame.position;
20041 let buffer_id = position.buffer_id?;
20042 let snapshot = self
20043 .project
20044 .as_ref()?
20045 .read(cx)
20046 .buffer_for_id(buffer_id, cx)?
20047 .read(cx)
20048 .snapshot();
20049
20050 let mut handled = false;
20051 for (id, ExcerptRange { context, .. }) in
20052 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20053 {
20054 if context.start.cmp(&position, &snapshot).is_ge()
20055 || context.end.cmp(&position, &snapshot).is_lt()
20056 {
20057 continue;
20058 }
20059 let snapshot = self.buffer.read(cx).snapshot(cx);
20060 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20061
20062 handled = true;
20063 self.clear_row_highlights::<ActiveDebugLine>();
20064
20065 self.go_to_line::<ActiveDebugLine>(
20066 multibuffer_anchor,
20067 Some(cx.theme().colors().editor_debugger_active_line_background),
20068 window,
20069 cx,
20070 );
20071
20072 cx.notify();
20073 }
20074
20075 handled.then_some(())
20076 })
20077 .is_some()
20078 }
20079
20080 pub fn copy_file_name_without_extension(
20081 &mut self,
20082 _: &CopyFileNameWithoutExtension,
20083 _: &mut Window,
20084 cx: &mut Context<Self>,
20085 ) {
20086 if let Some(file) = self.target_file(cx)
20087 && let Some(file_stem) = file.path().file_stem()
20088 {
20089 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20090 }
20091 }
20092
20093 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20094 if let Some(file) = self.target_file(cx)
20095 && let Some(name) = file.path().file_name()
20096 {
20097 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
20098 }
20099 }
20100
20101 pub fn toggle_git_blame(
20102 &mut self,
20103 _: &::git::Blame,
20104 window: &mut Window,
20105 cx: &mut Context<Self>,
20106 ) {
20107 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20108
20109 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20110 self.start_git_blame(true, window, cx);
20111 }
20112
20113 cx.notify();
20114 }
20115
20116 pub fn toggle_git_blame_inline(
20117 &mut self,
20118 _: &ToggleGitBlameInline,
20119 window: &mut Window,
20120 cx: &mut Context<Self>,
20121 ) {
20122 self.toggle_git_blame_inline_internal(true, window, cx);
20123 cx.notify();
20124 }
20125
20126 pub fn open_git_blame_commit(
20127 &mut self,
20128 _: &OpenGitBlameCommit,
20129 window: &mut Window,
20130 cx: &mut Context<Self>,
20131 ) {
20132 self.open_git_blame_commit_internal(window, cx);
20133 }
20134
20135 fn open_git_blame_commit_internal(
20136 &mut self,
20137 window: &mut Window,
20138 cx: &mut Context<Self>,
20139 ) -> Option<()> {
20140 let blame = self.blame.as_ref()?;
20141 let snapshot = self.snapshot(window, cx);
20142 let cursor = self
20143 .selections
20144 .newest::<Point>(&snapshot.display_snapshot)
20145 .head();
20146 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20147 let (_, blame_entry) = blame
20148 .update(cx, |blame, cx| {
20149 blame
20150 .blame_for_rows(
20151 &[RowInfo {
20152 buffer_id: Some(buffer.remote_id()),
20153 buffer_row: Some(point.row),
20154 ..Default::default()
20155 }],
20156 cx,
20157 )
20158 .next()
20159 })
20160 .flatten()?;
20161 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20162 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20163 let workspace = self.workspace()?.downgrade();
20164 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20165 None
20166 }
20167
20168 pub fn git_blame_inline_enabled(&self) -> bool {
20169 self.git_blame_inline_enabled
20170 }
20171
20172 pub fn toggle_selection_menu(
20173 &mut self,
20174 _: &ToggleSelectionMenu,
20175 _: &mut Window,
20176 cx: &mut Context<Self>,
20177 ) {
20178 self.show_selection_menu = self
20179 .show_selection_menu
20180 .map(|show_selections_menu| !show_selections_menu)
20181 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20182
20183 cx.notify();
20184 }
20185
20186 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20187 self.show_selection_menu
20188 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20189 }
20190
20191 fn start_git_blame(
20192 &mut self,
20193 user_triggered: bool,
20194 window: &mut Window,
20195 cx: &mut Context<Self>,
20196 ) {
20197 if let Some(project) = self.project() {
20198 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20199 && buffer.read(cx).file().is_none()
20200 {
20201 return;
20202 }
20203
20204 let focused = self.focus_handle(cx).contains_focused(window, cx);
20205
20206 let project = project.clone();
20207 let blame = cx
20208 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20209 self.blame_subscription =
20210 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20211 self.blame = Some(blame);
20212 }
20213 }
20214
20215 fn toggle_git_blame_inline_internal(
20216 &mut self,
20217 user_triggered: bool,
20218 window: &mut Window,
20219 cx: &mut Context<Self>,
20220 ) {
20221 if self.git_blame_inline_enabled {
20222 self.git_blame_inline_enabled = false;
20223 self.show_git_blame_inline = false;
20224 self.show_git_blame_inline_delay_task.take();
20225 } else {
20226 self.git_blame_inline_enabled = true;
20227 self.start_git_blame_inline(user_triggered, window, cx);
20228 }
20229
20230 cx.notify();
20231 }
20232
20233 fn start_git_blame_inline(
20234 &mut self,
20235 user_triggered: bool,
20236 window: &mut Window,
20237 cx: &mut Context<Self>,
20238 ) {
20239 self.start_git_blame(user_triggered, window, cx);
20240
20241 if ProjectSettings::get_global(cx)
20242 .git
20243 .inline_blame_delay()
20244 .is_some()
20245 {
20246 self.start_inline_blame_timer(window, cx);
20247 } else {
20248 self.show_git_blame_inline = true
20249 }
20250 }
20251
20252 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20253 self.blame.as_ref()
20254 }
20255
20256 pub fn show_git_blame_gutter(&self) -> bool {
20257 self.show_git_blame_gutter
20258 }
20259
20260 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20261 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20262 }
20263
20264 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20265 self.show_git_blame_inline
20266 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20267 && !self.newest_selection_head_on_empty_line(cx)
20268 && self.has_blame_entries(cx)
20269 }
20270
20271 fn has_blame_entries(&self, cx: &App) -> bool {
20272 self.blame()
20273 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20274 }
20275
20276 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20277 let cursor_anchor = self.selections.newest_anchor().head();
20278
20279 let snapshot = self.buffer.read(cx).snapshot(cx);
20280 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20281
20282 snapshot.line_len(buffer_row) == 0
20283 }
20284
20285 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20286 let buffer_and_selection = maybe!({
20287 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20288 let selection_range = selection.range();
20289
20290 let multi_buffer = self.buffer().read(cx);
20291 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20292 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20293
20294 let (buffer, range, _) = if selection.reversed {
20295 buffer_ranges.first()
20296 } else {
20297 buffer_ranges.last()
20298 }?;
20299
20300 let selection = text::ToPoint::to_point(&range.start, buffer).row
20301 ..text::ToPoint::to_point(&range.end, buffer).row;
20302 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20303 });
20304
20305 let Some((buffer, selection)) = buffer_and_selection else {
20306 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20307 };
20308
20309 let Some(project) = self.project() else {
20310 return Task::ready(Err(anyhow!("editor does not have project")));
20311 };
20312
20313 project.update(cx, |project, cx| {
20314 project.get_permalink_to_line(&buffer, selection, cx)
20315 })
20316 }
20317
20318 pub fn copy_permalink_to_line(
20319 &mut self,
20320 _: &CopyPermalinkToLine,
20321 window: &mut Window,
20322 cx: &mut Context<Self>,
20323 ) {
20324 let permalink_task = self.get_permalink_to_line(cx);
20325 let workspace = self.workspace();
20326
20327 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20328 Ok(permalink) => {
20329 cx.update(|_, cx| {
20330 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20331 })
20332 .ok();
20333 }
20334 Err(err) => {
20335 let message = format!("Failed to copy permalink: {err}");
20336
20337 anyhow::Result::<()>::Err(err).log_err();
20338
20339 if let Some(workspace) = workspace {
20340 workspace
20341 .update_in(cx, |workspace, _, cx| {
20342 struct CopyPermalinkToLine;
20343
20344 workspace.show_toast(
20345 Toast::new(
20346 NotificationId::unique::<CopyPermalinkToLine>(),
20347 message,
20348 ),
20349 cx,
20350 )
20351 })
20352 .ok();
20353 }
20354 }
20355 })
20356 .detach();
20357 }
20358
20359 pub fn copy_file_location(
20360 &mut self,
20361 _: &CopyFileLocation,
20362 _: &mut Window,
20363 cx: &mut Context<Self>,
20364 ) {
20365 let selection = self
20366 .selections
20367 .newest::<Point>(&self.display_snapshot(cx))
20368 .start
20369 .row
20370 + 1;
20371 if let Some(file) = self.target_file(cx) {
20372 let path = file.path().display(file.path_style(cx));
20373 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20374 }
20375 }
20376
20377 pub fn open_permalink_to_line(
20378 &mut self,
20379 _: &OpenPermalinkToLine,
20380 window: &mut Window,
20381 cx: &mut Context<Self>,
20382 ) {
20383 let permalink_task = self.get_permalink_to_line(cx);
20384 let workspace = self.workspace();
20385
20386 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20387 Ok(permalink) => {
20388 cx.update(|_, cx| {
20389 cx.open_url(permalink.as_ref());
20390 })
20391 .ok();
20392 }
20393 Err(err) => {
20394 let message = format!("Failed to open permalink: {err}");
20395
20396 anyhow::Result::<()>::Err(err).log_err();
20397
20398 if let Some(workspace) = workspace {
20399 workspace
20400 .update(cx, |workspace, cx| {
20401 struct OpenPermalinkToLine;
20402
20403 workspace.show_toast(
20404 Toast::new(
20405 NotificationId::unique::<OpenPermalinkToLine>(),
20406 message,
20407 ),
20408 cx,
20409 )
20410 })
20411 .ok();
20412 }
20413 }
20414 })
20415 .detach();
20416 }
20417
20418 pub fn insert_uuid_v4(
20419 &mut self,
20420 _: &InsertUuidV4,
20421 window: &mut Window,
20422 cx: &mut Context<Self>,
20423 ) {
20424 self.insert_uuid(UuidVersion::V4, window, cx);
20425 }
20426
20427 pub fn insert_uuid_v7(
20428 &mut self,
20429 _: &InsertUuidV7,
20430 window: &mut Window,
20431 cx: &mut Context<Self>,
20432 ) {
20433 self.insert_uuid(UuidVersion::V7, window, cx);
20434 }
20435
20436 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20437 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20438 self.transact(window, cx, |this, window, cx| {
20439 let edits = this
20440 .selections
20441 .all::<Point>(&this.display_snapshot(cx))
20442 .into_iter()
20443 .map(|selection| {
20444 let uuid = match version {
20445 UuidVersion::V4 => uuid::Uuid::new_v4(),
20446 UuidVersion::V7 => uuid::Uuid::now_v7(),
20447 };
20448
20449 (selection.range(), uuid.to_string())
20450 });
20451 this.edit(edits, cx);
20452 this.refresh_edit_prediction(true, false, window, cx);
20453 });
20454 }
20455
20456 pub fn open_selections_in_multibuffer(
20457 &mut self,
20458 _: &OpenSelectionsInMultibuffer,
20459 window: &mut Window,
20460 cx: &mut Context<Self>,
20461 ) {
20462 let multibuffer = self.buffer.read(cx);
20463
20464 let Some(buffer) = multibuffer.as_singleton() else {
20465 return;
20466 };
20467
20468 let Some(workspace) = self.workspace() else {
20469 return;
20470 };
20471
20472 let title = multibuffer.title(cx).to_string();
20473
20474 let locations = self
20475 .selections
20476 .all_anchors(&self.display_snapshot(cx))
20477 .iter()
20478 .map(|selection| {
20479 (
20480 buffer.clone(),
20481 (selection.start.text_anchor..selection.end.text_anchor)
20482 .to_point(buffer.read(cx)),
20483 )
20484 })
20485 .into_group_map();
20486
20487 cx.spawn_in(window, async move |_, cx| {
20488 workspace.update_in(cx, |workspace, window, cx| {
20489 Self::open_locations_in_multibuffer(
20490 workspace,
20491 locations,
20492 format!("Selections for '{title}'"),
20493 false,
20494 MultibufferSelectionMode::All,
20495 window,
20496 cx,
20497 );
20498 })
20499 })
20500 .detach();
20501 }
20502
20503 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20504 /// last highlight added will be used.
20505 ///
20506 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20507 pub fn highlight_rows<T: 'static>(
20508 &mut self,
20509 range: Range<Anchor>,
20510 color: Hsla,
20511 options: RowHighlightOptions,
20512 cx: &mut Context<Self>,
20513 ) {
20514 let snapshot = self.buffer().read(cx).snapshot(cx);
20515 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20516 let ix = row_highlights.binary_search_by(|highlight| {
20517 Ordering::Equal
20518 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20519 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20520 });
20521
20522 if let Err(mut ix) = ix {
20523 let index = post_inc(&mut self.highlight_order);
20524
20525 // If this range intersects with the preceding highlight, then merge it with
20526 // the preceding highlight. Otherwise insert a new highlight.
20527 let mut merged = false;
20528 if ix > 0 {
20529 let prev_highlight = &mut row_highlights[ix - 1];
20530 if prev_highlight
20531 .range
20532 .end
20533 .cmp(&range.start, &snapshot)
20534 .is_ge()
20535 {
20536 ix -= 1;
20537 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20538 prev_highlight.range.end = range.end;
20539 }
20540 merged = true;
20541 prev_highlight.index = index;
20542 prev_highlight.color = color;
20543 prev_highlight.options = options;
20544 }
20545 }
20546
20547 if !merged {
20548 row_highlights.insert(
20549 ix,
20550 RowHighlight {
20551 range,
20552 index,
20553 color,
20554 options,
20555 type_id: TypeId::of::<T>(),
20556 },
20557 );
20558 }
20559
20560 // If any of the following highlights intersect with this one, merge them.
20561 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20562 let highlight = &row_highlights[ix];
20563 if next_highlight
20564 .range
20565 .start
20566 .cmp(&highlight.range.end, &snapshot)
20567 .is_le()
20568 {
20569 if next_highlight
20570 .range
20571 .end
20572 .cmp(&highlight.range.end, &snapshot)
20573 .is_gt()
20574 {
20575 row_highlights[ix].range.end = next_highlight.range.end;
20576 }
20577 row_highlights.remove(ix + 1);
20578 } else {
20579 break;
20580 }
20581 }
20582 }
20583 }
20584
20585 /// Remove any highlighted row ranges of the given type that intersect the
20586 /// given ranges.
20587 pub fn remove_highlighted_rows<T: 'static>(
20588 &mut self,
20589 ranges_to_remove: Vec<Range<Anchor>>,
20590 cx: &mut Context<Self>,
20591 ) {
20592 let snapshot = self.buffer().read(cx).snapshot(cx);
20593 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20594 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20595 row_highlights.retain(|highlight| {
20596 while let Some(range_to_remove) = ranges_to_remove.peek() {
20597 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20598 Ordering::Less | Ordering::Equal => {
20599 ranges_to_remove.next();
20600 }
20601 Ordering::Greater => {
20602 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20603 Ordering::Less | Ordering::Equal => {
20604 return false;
20605 }
20606 Ordering::Greater => break,
20607 }
20608 }
20609 }
20610 }
20611
20612 true
20613 })
20614 }
20615
20616 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20617 pub fn clear_row_highlights<T: 'static>(&mut self) {
20618 self.highlighted_rows.remove(&TypeId::of::<T>());
20619 }
20620
20621 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20622 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20623 self.highlighted_rows
20624 .get(&TypeId::of::<T>())
20625 .map_or(&[] as &[_], |vec| vec.as_slice())
20626 .iter()
20627 .map(|highlight| (highlight.range.clone(), highlight.color))
20628 }
20629
20630 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20631 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20632 /// Allows to ignore certain kinds of highlights.
20633 pub fn highlighted_display_rows(
20634 &self,
20635 window: &mut Window,
20636 cx: &mut App,
20637 ) -> BTreeMap<DisplayRow, LineHighlight> {
20638 let snapshot = self.snapshot(window, cx);
20639 let mut used_highlight_orders = HashMap::default();
20640 self.highlighted_rows
20641 .iter()
20642 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20643 .fold(
20644 BTreeMap::<DisplayRow, LineHighlight>::new(),
20645 |mut unique_rows, highlight| {
20646 let start = highlight.range.start.to_display_point(&snapshot);
20647 let end = highlight.range.end.to_display_point(&snapshot);
20648 let start_row = start.row().0;
20649 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20650 && end.column() == 0
20651 {
20652 end.row().0.saturating_sub(1)
20653 } else {
20654 end.row().0
20655 };
20656 for row in start_row..=end_row {
20657 let used_index =
20658 used_highlight_orders.entry(row).or_insert(highlight.index);
20659 if highlight.index >= *used_index {
20660 *used_index = highlight.index;
20661 unique_rows.insert(
20662 DisplayRow(row),
20663 LineHighlight {
20664 include_gutter: highlight.options.include_gutter,
20665 border: None,
20666 background: highlight.color.into(),
20667 type_id: Some(highlight.type_id),
20668 },
20669 );
20670 }
20671 }
20672 unique_rows
20673 },
20674 )
20675 }
20676
20677 pub fn highlighted_display_row_for_autoscroll(
20678 &self,
20679 snapshot: &DisplaySnapshot,
20680 ) -> Option<DisplayRow> {
20681 self.highlighted_rows
20682 .values()
20683 .flat_map(|highlighted_rows| highlighted_rows.iter())
20684 .filter_map(|highlight| {
20685 if highlight.options.autoscroll {
20686 Some(highlight.range.start.to_display_point(snapshot).row())
20687 } else {
20688 None
20689 }
20690 })
20691 .min()
20692 }
20693
20694 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20695 self.highlight_background::<SearchWithinRange>(
20696 ranges,
20697 |colors| colors.colors().editor_document_highlight_read_background,
20698 cx,
20699 )
20700 }
20701
20702 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20703 self.breadcrumb_header = Some(new_header);
20704 }
20705
20706 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20707 self.clear_background_highlights::<SearchWithinRange>(cx);
20708 }
20709
20710 pub fn highlight_background<T: 'static>(
20711 &mut self,
20712 ranges: &[Range<Anchor>],
20713 color_fetcher: fn(&Theme) -> Hsla,
20714 cx: &mut Context<Self>,
20715 ) {
20716 self.background_highlights.insert(
20717 HighlightKey::Type(TypeId::of::<T>()),
20718 (color_fetcher, Arc::from(ranges)),
20719 );
20720 self.scrollbar_marker_state.dirty = true;
20721 cx.notify();
20722 }
20723
20724 pub fn highlight_background_key<T: 'static>(
20725 &mut self,
20726 key: usize,
20727 ranges: &[Range<Anchor>],
20728 color_fetcher: fn(&Theme) -> Hsla,
20729 cx: &mut Context<Self>,
20730 ) {
20731 self.background_highlights.insert(
20732 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20733 (color_fetcher, Arc::from(ranges)),
20734 );
20735 self.scrollbar_marker_state.dirty = true;
20736 cx.notify();
20737 }
20738
20739 pub fn clear_background_highlights<T: 'static>(
20740 &mut self,
20741 cx: &mut Context<Self>,
20742 ) -> Option<BackgroundHighlight> {
20743 let text_highlights = self
20744 .background_highlights
20745 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20746 if !text_highlights.1.is_empty() {
20747 self.scrollbar_marker_state.dirty = true;
20748 cx.notify();
20749 }
20750 Some(text_highlights)
20751 }
20752
20753 pub fn highlight_gutter<T: 'static>(
20754 &mut self,
20755 ranges: impl Into<Vec<Range<Anchor>>>,
20756 color_fetcher: fn(&App) -> Hsla,
20757 cx: &mut Context<Self>,
20758 ) {
20759 self.gutter_highlights
20760 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20761 cx.notify();
20762 }
20763
20764 pub fn clear_gutter_highlights<T: 'static>(
20765 &mut self,
20766 cx: &mut Context<Self>,
20767 ) -> Option<GutterHighlight> {
20768 cx.notify();
20769 self.gutter_highlights.remove(&TypeId::of::<T>())
20770 }
20771
20772 pub fn insert_gutter_highlight<T: 'static>(
20773 &mut self,
20774 range: Range<Anchor>,
20775 color_fetcher: fn(&App) -> Hsla,
20776 cx: &mut Context<Self>,
20777 ) {
20778 let snapshot = self.buffer().read(cx).snapshot(cx);
20779 let mut highlights = self
20780 .gutter_highlights
20781 .remove(&TypeId::of::<T>())
20782 .map(|(_, highlights)| highlights)
20783 .unwrap_or_default();
20784 let ix = highlights.binary_search_by(|highlight| {
20785 Ordering::Equal
20786 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20787 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20788 });
20789 if let Err(ix) = ix {
20790 highlights.insert(ix, range);
20791 }
20792 self.gutter_highlights
20793 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20794 }
20795
20796 pub fn remove_gutter_highlights<T: 'static>(
20797 &mut self,
20798 ranges_to_remove: Vec<Range<Anchor>>,
20799 cx: &mut Context<Self>,
20800 ) {
20801 let snapshot = self.buffer().read(cx).snapshot(cx);
20802 let Some((color_fetcher, mut gutter_highlights)) =
20803 self.gutter_highlights.remove(&TypeId::of::<T>())
20804 else {
20805 return;
20806 };
20807 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20808 gutter_highlights.retain(|highlight| {
20809 while let Some(range_to_remove) = ranges_to_remove.peek() {
20810 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20811 Ordering::Less | Ordering::Equal => {
20812 ranges_to_remove.next();
20813 }
20814 Ordering::Greater => {
20815 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20816 Ordering::Less | Ordering::Equal => {
20817 return false;
20818 }
20819 Ordering::Greater => break,
20820 }
20821 }
20822 }
20823 }
20824
20825 true
20826 });
20827 self.gutter_highlights
20828 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20829 }
20830
20831 #[cfg(feature = "test-support")]
20832 pub fn all_text_highlights(
20833 &self,
20834 window: &mut Window,
20835 cx: &mut Context<Self>,
20836 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20837 let snapshot = self.snapshot(window, cx);
20838 self.display_map.update(cx, |display_map, _| {
20839 display_map
20840 .all_text_highlights()
20841 .map(|highlight| {
20842 let (style, ranges) = highlight.as_ref();
20843 (
20844 *style,
20845 ranges
20846 .iter()
20847 .map(|range| range.clone().to_display_points(&snapshot))
20848 .collect(),
20849 )
20850 })
20851 .collect()
20852 })
20853 }
20854
20855 #[cfg(feature = "test-support")]
20856 pub fn all_text_background_highlights(
20857 &self,
20858 window: &mut Window,
20859 cx: &mut Context<Self>,
20860 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20861 let snapshot = self.snapshot(window, cx);
20862 let buffer = &snapshot.buffer_snapshot();
20863 let start = buffer.anchor_before(0);
20864 let end = buffer.anchor_after(buffer.len());
20865 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20866 }
20867
20868 #[cfg(any(test, feature = "test-support"))]
20869 pub fn sorted_background_highlights_in_range(
20870 &self,
20871 search_range: Range<Anchor>,
20872 display_snapshot: &DisplaySnapshot,
20873 theme: &Theme,
20874 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20875 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20876 res.sort_by(|a, b| {
20877 a.0.start
20878 .cmp(&b.0.start)
20879 .then_with(|| a.0.end.cmp(&b.0.end))
20880 .then_with(|| a.1.cmp(&b.1))
20881 });
20882 res
20883 }
20884
20885 #[cfg(feature = "test-support")]
20886 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20887 let snapshot = self.buffer().read(cx).snapshot(cx);
20888
20889 let highlights = self
20890 .background_highlights
20891 .get(&HighlightKey::Type(TypeId::of::<
20892 items::BufferSearchHighlights,
20893 >()));
20894
20895 if let Some((_color, ranges)) = highlights {
20896 ranges
20897 .iter()
20898 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20899 .collect_vec()
20900 } else {
20901 vec![]
20902 }
20903 }
20904
20905 fn document_highlights_for_position<'a>(
20906 &'a self,
20907 position: Anchor,
20908 buffer: &'a MultiBufferSnapshot,
20909 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20910 let read_highlights = self
20911 .background_highlights
20912 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20913 .map(|h| &h.1);
20914 let write_highlights = self
20915 .background_highlights
20916 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20917 .map(|h| &h.1);
20918 let left_position = position.bias_left(buffer);
20919 let right_position = position.bias_right(buffer);
20920 read_highlights
20921 .into_iter()
20922 .chain(write_highlights)
20923 .flat_map(move |ranges| {
20924 let start_ix = match ranges.binary_search_by(|probe| {
20925 let cmp = probe.end.cmp(&left_position, buffer);
20926 if cmp.is_ge() {
20927 Ordering::Greater
20928 } else {
20929 Ordering::Less
20930 }
20931 }) {
20932 Ok(i) | Err(i) => i,
20933 };
20934
20935 ranges[start_ix..]
20936 .iter()
20937 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20938 })
20939 }
20940
20941 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20942 self.background_highlights
20943 .get(&HighlightKey::Type(TypeId::of::<T>()))
20944 .is_some_and(|(_, highlights)| !highlights.is_empty())
20945 }
20946
20947 /// Returns all background highlights for a given range.
20948 ///
20949 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20950 pub fn background_highlights_in_range(
20951 &self,
20952 search_range: Range<Anchor>,
20953 display_snapshot: &DisplaySnapshot,
20954 theme: &Theme,
20955 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20956 let mut results = Vec::new();
20957 for (color_fetcher, ranges) in self.background_highlights.values() {
20958 let color = color_fetcher(theme);
20959 let start_ix = match ranges.binary_search_by(|probe| {
20960 let cmp = probe
20961 .end
20962 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20963 if cmp.is_gt() {
20964 Ordering::Greater
20965 } else {
20966 Ordering::Less
20967 }
20968 }) {
20969 Ok(i) | Err(i) => i,
20970 };
20971 for range in &ranges[start_ix..] {
20972 if range
20973 .start
20974 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20975 .is_ge()
20976 {
20977 break;
20978 }
20979
20980 let start = range.start.to_display_point(display_snapshot);
20981 let end = range.end.to_display_point(display_snapshot);
20982 results.push((start..end, color))
20983 }
20984 }
20985 results
20986 }
20987
20988 pub fn gutter_highlights_in_range(
20989 &self,
20990 search_range: Range<Anchor>,
20991 display_snapshot: &DisplaySnapshot,
20992 cx: &App,
20993 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20994 let mut results = Vec::new();
20995 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20996 let color = color_fetcher(cx);
20997 let start_ix = match ranges.binary_search_by(|probe| {
20998 let cmp = probe
20999 .end
21000 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21001 if cmp.is_gt() {
21002 Ordering::Greater
21003 } else {
21004 Ordering::Less
21005 }
21006 }) {
21007 Ok(i) | Err(i) => i,
21008 };
21009 for range in &ranges[start_ix..] {
21010 if range
21011 .start
21012 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21013 .is_ge()
21014 {
21015 break;
21016 }
21017
21018 let start = range.start.to_display_point(display_snapshot);
21019 let end = range.end.to_display_point(display_snapshot);
21020 results.push((start..end, color))
21021 }
21022 }
21023 results
21024 }
21025
21026 /// Get the text ranges corresponding to the redaction query
21027 pub fn redacted_ranges(
21028 &self,
21029 search_range: Range<Anchor>,
21030 display_snapshot: &DisplaySnapshot,
21031 cx: &App,
21032 ) -> Vec<Range<DisplayPoint>> {
21033 display_snapshot
21034 .buffer_snapshot()
21035 .redacted_ranges(search_range, |file| {
21036 if let Some(file) = file {
21037 file.is_private()
21038 && EditorSettings::get(
21039 Some(SettingsLocation {
21040 worktree_id: file.worktree_id(cx),
21041 path: file.path().as_ref(),
21042 }),
21043 cx,
21044 )
21045 .redact_private_values
21046 } else {
21047 false
21048 }
21049 })
21050 .map(|range| {
21051 range.start.to_display_point(display_snapshot)
21052 ..range.end.to_display_point(display_snapshot)
21053 })
21054 .collect()
21055 }
21056
21057 pub fn highlight_text_key<T: 'static>(
21058 &mut self,
21059 key: usize,
21060 ranges: Vec<Range<Anchor>>,
21061 style: HighlightStyle,
21062 cx: &mut Context<Self>,
21063 ) {
21064 self.display_map.update(cx, |map, _| {
21065 map.highlight_text(
21066 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21067 ranges,
21068 style,
21069 );
21070 });
21071 cx.notify();
21072 }
21073
21074 pub fn highlight_text<T: 'static>(
21075 &mut self,
21076 ranges: Vec<Range<Anchor>>,
21077 style: HighlightStyle,
21078 cx: &mut Context<Self>,
21079 ) {
21080 self.display_map.update(cx, |map, _| {
21081 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
21082 });
21083 cx.notify();
21084 }
21085
21086 pub fn text_highlights<'a, T: 'static>(
21087 &'a self,
21088 cx: &'a App,
21089 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21090 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21091 }
21092
21093 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21094 let cleared = self
21095 .display_map
21096 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21097 if cleared {
21098 cx.notify();
21099 }
21100 }
21101
21102 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21103 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21104 && self.focus_handle.is_focused(window)
21105 }
21106
21107 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21108 self.show_cursor_when_unfocused = is_enabled;
21109 cx.notify();
21110 }
21111
21112 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21113 cx.notify();
21114 }
21115
21116 fn on_debug_session_event(
21117 &mut self,
21118 _session: Entity<Session>,
21119 event: &SessionEvent,
21120 cx: &mut Context<Self>,
21121 ) {
21122 if let SessionEvent::InvalidateInlineValue = event {
21123 self.refresh_inline_values(cx);
21124 }
21125 }
21126
21127 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21128 let Some(project) = self.project.clone() else {
21129 return;
21130 };
21131
21132 if !self.inline_value_cache.enabled {
21133 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21134 self.splice_inlays(&inlays, Vec::new(), cx);
21135 return;
21136 }
21137
21138 let current_execution_position = self
21139 .highlighted_rows
21140 .get(&TypeId::of::<ActiveDebugLine>())
21141 .and_then(|lines| lines.last().map(|line| line.range.end));
21142
21143 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21144 let inline_values = editor
21145 .update(cx, |editor, cx| {
21146 let Some(current_execution_position) = current_execution_position else {
21147 return Some(Task::ready(Ok(Vec::new())));
21148 };
21149
21150 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21151 let snapshot = buffer.snapshot(cx);
21152
21153 let excerpt = snapshot.excerpt_containing(
21154 current_execution_position..current_execution_position,
21155 )?;
21156
21157 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21158 })?;
21159
21160 let range =
21161 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21162
21163 project.inline_values(buffer, range, cx)
21164 })
21165 .ok()
21166 .flatten()?
21167 .await
21168 .context("refreshing debugger inlays")
21169 .log_err()?;
21170
21171 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21172
21173 for (buffer_id, inline_value) in inline_values
21174 .into_iter()
21175 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21176 {
21177 buffer_inline_values
21178 .entry(buffer_id)
21179 .or_default()
21180 .push(inline_value);
21181 }
21182
21183 editor
21184 .update(cx, |editor, cx| {
21185 let snapshot = editor.buffer.read(cx).snapshot(cx);
21186 let mut new_inlays = Vec::default();
21187
21188 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21189 let buffer_id = buffer_snapshot.remote_id();
21190 buffer_inline_values
21191 .get(&buffer_id)
21192 .into_iter()
21193 .flatten()
21194 .for_each(|hint| {
21195 let inlay = Inlay::debugger(
21196 post_inc(&mut editor.next_inlay_id),
21197 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
21198 hint.text(),
21199 );
21200 if !inlay.text().chars().contains(&'\n') {
21201 new_inlays.push(inlay);
21202 }
21203 });
21204 }
21205
21206 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21207 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21208
21209 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21210 })
21211 .ok()?;
21212 Some(())
21213 });
21214 }
21215
21216 fn on_buffer_event(
21217 &mut self,
21218 multibuffer: &Entity<MultiBuffer>,
21219 event: &multi_buffer::Event,
21220 window: &mut Window,
21221 cx: &mut Context<Self>,
21222 ) {
21223 match event {
21224 multi_buffer::Event::Edited { edited_buffer } => {
21225 self.scrollbar_marker_state.dirty = true;
21226 self.active_indent_guides_state.dirty = true;
21227 self.refresh_active_diagnostics(cx);
21228 self.refresh_code_actions(window, cx);
21229 self.refresh_selected_text_highlights(true, window, cx);
21230 self.refresh_single_line_folds(window, cx);
21231 self.refresh_matching_bracket_highlights(window, cx);
21232 if self.has_active_edit_prediction() {
21233 self.update_visible_edit_prediction(window, cx);
21234 }
21235
21236 if let Some(buffer) = edited_buffer {
21237 if buffer.read(cx).file().is_none() {
21238 cx.emit(EditorEvent::TitleChanged);
21239 }
21240
21241 if self.project.is_some() {
21242 let buffer_id = buffer.read(cx).remote_id();
21243 self.register_buffer(buffer_id, cx);
21244 self.update_lsp_data(Some(buffer_id), window, cx);
21245 self.refresh_inlay_hints(
21246 InlayHintRefreshReason::BufferEdited(buffer_id),
21247 cx,
21248 );
21249 }
21250 }
21251
21252 cx.emit(EditorEvent::BufferEdited);
21253 cx.emit(SearchEvent::MatchesInvalidated);
21254
21255 let Some(project) = &self.project else { return };
21256 let (telemetry, is_via_ssh) = {
21257 let project = project.read(cx);
21258 let telemetry = project.client().telemetry().clone();
21259 let is_via_ssh = project.is_via_remote_server();
21260 (telemetry, is_via_ssh)
21261 };
21262 telemetry.log_edit_event("editor", is_via_ssh);
21263 }
21264 multi_buffer::Event::ExcerptsAdded {
21265 buffer,
21266 predecessor,
21267 excerpts,
21268 } => {
21269 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21270 let buffer_id = buffer.read(cx).remote_id();
21271 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21272 && let Some(project) = &self.project
21273 {
21274 update_uncommitted_diff_for_buffer(
21275 cx.entity(),
21276 project,
21277 [buffer.clone()],
21278 self.buffer.clone(),
21279 cx,
21280 )
21281 .detach();
21282 }
21283 self.update_lsp_data(Some(buffer_id), window, cx);
21284 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21285 cx.emit(EditorEvent::ExcerptsAdded {
21286 buffer: buffer.clone(),
21287 predecessor: *predecessor,
21288 excerpts: excerpts.clone(),
21289 });
21290 }
21291 multi_buffer::Event::ExcerptsRemoved {
21292 ids,
21293 removed_buffer_ids,
21294 } => {
21295 if let Some(inlay_hints) = &mut self.inlay_hints {
21296 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21297 }
21298 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21299 for buffer_id in removed_buffer_ids {
21300 self.registered_buffers.remove(buffer_id);
21301 }
21302 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21303 cx.emit(EditorEvent::ExcerptsRemoved {
21304 ids: ids.clone(),
21305 removed_buffer_ids: removed_buffer_ids.clone(),
21306 });
21307 }
21308 multi_buffer::Event::ExcerptsEdited {
21309 excerpt_ids,
21310 buffer_ids,
21311 } => {
21312 self.display_map.update(cx, |map, cx| {
21313 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21314 });
21315 cx.emit(EditorEvent::ExcerptsEdited {
21316 ids: excerpt_ids.clone(),
21317 });
21318 }
21319 multi_buffer::Event::ExcerptsExpanded { ids } => {
21320 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21321 self.refresh_document_highlights(cx);
21322 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21323 }
21324 multi_buffer::Event::Reparsed(buffer_id) => {
21325 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21326 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21327
21328 cx.emit(EditorEvent::Reparsed(*buffer_id));
21329 }
21330 multi_buffer::Event::DiffHunksToggled => {
21331 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21332 }
21333 multi_buffer::Event::LanguageChanged(buffer_id) => {
21334 self.registered_buffers.remove(&buffer_id);
21335 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21336 cx.emit(EditorEvent::Reparsed(*buffer_id));
21337 cx.notify();
21338 }
21339 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21340 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21341 multi_buffer::Event::FileHandleChanged
21342 | multi_buffer::Event::Reloaded
21343 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21344 multi_buffer::Event::DiagnosticsUpdated => {
21345 self.update_diagnostics_state(window, cx);
21346 }
21347 _ => {}
21348 };
21349 }
21350
21351 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21352 if !self.diagnostics_enabled() {
21353 return;
21354 }
21355 self.refresh_active_diagnostics(cx);
21356 self.refresh_inline_diagnostics(true, window, cx);
21357 self.scrollbar_marker_state.dirty = true;
21358 cx.notify();
21359 }
21360
21361 pub fn start_temporary_diff_override(&mut self) {
21362 self.load_diff_task.take();
21363 self.temporary_diff_override = true;
21364 }
21365
21366 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21367 self.temporary_diff_override = false;
21368 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21369 self.buffer.update(cx, |buffer, cx| {
21370 buffer.set_all_diff_hunks_collapsed(cx);
21371 });
21372
21373 if let Some(project) = self.project.clone() {
21374 self.load_diff_task = Some(
21375 update_uncommitted_diff_for_buffer(
21376 cx.entity(),
21377 &project,
21378 self.buffer.read(cx).all_buffers(),
21379 self.buffer.clone(),
21380 cx,
21381 )
21382 .shared(),
21383 );
21384 }
21385 }
21386
21387 fn on_display_map_changed(
21388 &mut self,
21389 _: Entity<DisplayMap>,
21390 _: &mut Window,
21391 cx: &mut Context<Self>,
21392 ) {
21393 cx.notify();
21394 }
21395
21396 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21397 if self.diagnostics_enabled() {
21398 let new_severity = EditorSettings::get_global(cx)
21399 .diagnostics_max_severity
21400 .unwrap_or(DiagnosticSeverity::Hint);
21401 self.set_max_diagnostics_severity(new_severity, cx);
21402 }
21403 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21404 self.update_edit_prediction_settings(cx);
21405 self.refresh_edit_prediction(true, false, window, cx);
21406 self.refresh_inline_values(cx);
21407 self.refresh_inlay_hints(
21408 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21409 self.selections.newest_anchor().head(),
21410 &self.buffer.read(cx).snapshot(cx),
21411 cx,
21412 )),
21413 cx,
21414 );
21415
21416 let old_cursor_shape = self.cursor_shape;
21417 let old_show_breadcrumbs = self.show_breadcrumbs;
21418
21419 {
21420 let editor_settings = EditorSettings::get_global(cx);
21421 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21422 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21423 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21424 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21425 }
21426
21427 if old_cursor_shape != self.cursor_shape {
21428 cx.emit(EditorEvent::CursorShapeChanged);
21429 }
21430
21431 if old_show_breadcrumbs != self.show_breadcrumbs {
21432 cx.emit(EditorEvent::BreadcrumbsChanged);
21433 }
21434
21435 let project_settings = ProjectSettings::get_global(cx);
21436 self.buffer_serialization = self
21437 .should_serialize_buffer()
21438 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21439
21440 if self.mode.is_full() {
21441 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21442 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21443 if self.show_inline_diagnostics != show_inline_diagnostics {
21444 self.show_inline_diagnostics = show_inline_diagnostics;
21445 self.refresh_inline_diagnostics(false, window, cx);
21446 }
21447
21448 if self.git_blame_inline_enabled != inline_blame_enabled {
21449 self.toggle_git_blame_inline_internal(false, window, cx);
21450 }
21451
21452 let minimap_settings = EditorSettings::get_global(cx).minimap;
21453 if self.minimap_visibility != MinimapVisibility::Disabled {
21454 if self.minimap_visibility.settings_visibility()
21455 != minimap_settings.minimap_enabled()
21456 {
21457 self.set_minimap_visibility(
21458 MinimapVisibility::for_mode(self.mode(), cx),
21459 window,
21460 cx,
21461 );
21462 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21463 minimap_entity.update(cx, |minimap_editor, cx| {
21464 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21465 })
21466 }
21467 }
21468 }
21469
21470 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21471 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21472 }) {
21473 if !inlay_splice.is_empty() {
21474 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21475 }
21476 self.refresh_colors_for_visible_range(None, window, cx);
21477 }
21478
21479 cx.notify();
21480 }
21481
21482 pub fn set_searchable(&mut self, searchable: bool) {
21483 self.searchable = searchable;
21484 }
21485
21486 pub fn searchable(&self) -> bool {
21487 self.searchable
21488 }
21489
21490 pub fn open_excerpts_in_split(
21491 &mut self,
21492 _: &OpenExcerptsSplit,
21493 window: &mut Window,
21494 cx: &mut Context<Self>,
21495 ) {
21496 self.open_excerpts_common(None, true, window, cx)
21497 }
21498
21499 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21500 self.open_excerpts_common(None, false, window, cx)
21501 }
21502
21503 fn open_excerpts_common(
21504 &mut self,
21505 jump_data: Option<JumpData>,
21506 split: bool,
21507 window: &mut Window,
21508 cx: &mut Context<Self>,
21509 ) {
21510 let Some(workspace) = self.workspace() else {
21511 cx.propagate();
21512 return;
21513 };
21514
21515 if self.buffer.read(cx).is_singleton() {
21516 cx.propagate();
21517 return;
21518 }
21519
21520 let mut new_selections_by_buffer = HashMap::default();
21521 match &jump_data {
21522 Some(JumpData::MultiBufferPoint {
21523 excerpt_id,
21524 position,
21525 anchor,
21526 line_offset_from_top,
21527 }) => {
21528 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21529 if let Some(buffer) = multi_buffer_snapshot
21530 .buffer_id_for_excerpt(*excerpt_id)
21531 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21532 {
21533 let buffer_snapshot = buffer.read(cx).snapshot();
21534 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21535 language::ToPoint::to_point(anchor, &buffer_snapshot)
21536 } else {
21537 buffer_snapshot.clip_point(*position, Bias::Left)
21538 };
21539 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21540 new_selections_by_buffer.insert(
21541 buffer,
21542 (
21543 vec![jump_to_offset..jump_to_offset],
21544 Some(*line_offset_from_top),
21545 ),
21546 );
21547 }
21548 }
21549 Some(JumpData::MultiBufferRow {
21550 row,
21551 line_offset_from_top,
21552 }) => {
21553 let point = MultiBufferPoint::new(row.0, 0);
21554 if let Some((buffer, buffer_point, _)) =
21555 self.buffer.read(cx).point_to_buffer_point(point, cx)
21556 {
21557 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21558 new_selections_by_buffer
21559 .entry(buffer)
21560 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21561 .0
21562 .push(buffer_offset..buffer_offset)
21563 }
21564 }
21565 None => {
21566 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21567 let multi_buffer = self.buffer.read(cx);
21568 for selection in selections {
21569 for (snapshot, range, _, anchor) in multi_buffer
21570 .snapshot(cx)
21571 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21572 {
21573 if let Some(anchor) = anchor {
21574 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21575 else {
21576 continue;
21577 };
21578 let offset = text::ToOffset::to_offset(
21579 &anchor.text_anchor,
21580 &buffer_handle.read(cx).snapshot(),
21581 );
21582 let range = offset..offset;
21583 new_selections_by_buffer
21584 .entry(buffer_handle)
21585 .or_insert((Vec::new(), None))
21586 .0
21587 .push(range)
21588 } else {
21589 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21590 else {
21591 continue;
21592 };
21593 new_selections_by_buffer
21594 .entry(buffer_handle)
21595 .or_insert((Vec::new(), None))
21596 .0
21597 .push(range)
21598 }
21599 }
21600 }
21601 }
21602 }
21603
21604 new_selections_by_buffer
21605 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21606
21607 if new_selections_by_buffer.is_empty() {
21608 return;
21609 }
21610
21611 // We defer the pane interaction because we ourselves are a workspace item
21612 // and activating a new item causes the pane to call a method on us reentrantly,
21613 // which panics if we're on the stack.
21614 window.defer(cx, move |window, cx| {
21615 workspace.update(cx, |workspace, cx| {
21616 let pane = if split {
21617 workspace.adjacent_pane(window, cx)
21618 } else {
21619 workspace.active_pane().clone()
21620 };
21621
21622 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21623 let editor = buffer
21624 .read(cx)
21625 .file()
21626 .is_none()
21627 .then(|| {
21628 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21629 // so `workspace.open_project_item` will never find them, always opening a new editor.
21630 // Instead, we try to activate the existing editor in the pane first.
21631 let (editor, pane_item_index) =
21632 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21633 let editor = item.downcast::<Editor>()?;
21634 let singleton_buffer =
21635 editor.read(cx).buffer().read(cx).as_singleton()?;
21636 if singleton_buffer == buffer {
21637 Some((editor, i))
21638 } else {
21639 None
21640 }
21641 })?;
21642 pane.update(cx, |pane, cx| {
21643 pane.activate_item(pane_item_index, true, true, window, cx)
21644 });
21645 Some(editor)
21646 })
21647 .flatten()
21648 .unwrap_or_else(|| {
21649 workspace.open_project_item::<Self>(
21650 pane.clone(),
21651 buffer,
21652 true,
21653 true,
21654 window,
21655 cx,
21656 )
21657 });
21658
21659 editor.update(cx, |editor, cx| {
21660 let autoscroll = match scroll_offset {
21661 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21662 None => Autoscroll::newest(),
21663 };
21664 let nav_history = editor.nav_history.take();
21665 editor.change_selections(
21666 SelectionEffects::scroll(autoscroll),
21667 window,
21668 cx,
21669 |s| {
21670 s.select_ranges(ranges);
21671 },
21672 );
21673 editor.nav_history = nav_history;
21674 });
21675 }
21676 })
21677 });
21678 }
21679
21680 // For now, don't allow opening excerpts in buffers that aren't backed by
21681 // regular project files.
21682 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21683 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21684 }
21685
21686 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21687 let snapshot = self.buffer.read(cx).read(cx);
21688 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21689 Some(
21690 ranges
21691 .iter()
21692 .map(move |range| {
21693 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21694 })
21695 .collect(),
21696 )
21697 }
21698
21699 fn selection_replacement_ranges(
21700 &self,
21701 range: Range<OffsetUtf16>,
21702 cx: &mut App,
21703 ) -> Vec<Range<OffsetUtf16>> {
21704 let selections = self
21705 .selections
21706 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21707 let newest_selection = selections
21708 .iter()
21709 .max_by_key(|selection| selection.id)
21710 .unwrap();
21711 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21712 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21713 let snapshot = self.buffer.read(cx).read(cx);
21714 selections
21715 .into_iter()
21716 .map(|mut selection| {
21717 selection.start.0 =
21718 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21719 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21720 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21721 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21722 })
21723 .collect()
21724 }
21725
21726 fn report_editor_event(
21727 &self,
21728 reported_event: ReportEditorEvent,
21729 file_extension: Option<String>,
21730 cx: &App,
21731 ) {
21732 if cfg!(any(test, feature = "test-support")) {
21733 return;
21734 }
21735
21736 let Some(project) = &self.project else { return };
21737
21738 // If None, we are in a file without an extension
21739 let file = self
21740 .buffer
21741 .read(cx)
21742 .as_singleton()
21743 .and_then(|b| b.read(cx).file());
21744 let file_extension = file_extension.or(file
21745 .as_ref()
21746 .and_then(|file| Path::new(file.file_name(cx)).extension())
21747 .and_then(|e| e.to_str())
21748 .map(|a| a.to_string()));
21749
21750 let vim_mode = vim_flavor(cx).is_some();
21751
21752 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21753 let copilot_enabled = edit_predictions_provider
21754 == language::language_settings::EditPredictionProvider::Copilot;
21755 let copilot_enabled_for_language = self
21756 .buffer
21757 .read(cx)
21758 .language_settings(cx)
21759 .show_edit_predictions;
21760
21761 let project = project.read(cx);
21762 let event_type = reported_event.event_type();
21763
21764 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21765 telemetry::event!(
21766 event_type,
21767 type = if auto_saved {"autosave"} else {"manual"},
21768 file_extension,
21769 vim_mode,
21770 copilot_enabled,
21771 copilot_enabled_for_language,
21772 edit_predictions_provider,
21773 is_via_ssh = project.is_via_remote_server(),
21774 );
21775 } else {
21776 telemetry::event!(
21777 event_type,
21778 file_extension,
21779 vim_mode,
21780 copilot_enabled,
21781 copilot_enabled_for_language,
21782 edit_predictions_provider,
21783 is_via_ssh = project.is_via_remote_server(),
21784 );
21785 };
21786 }
21787
21788 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21789 /// with each line being an array of {text, highlight} objects.
21790 fn copy_highlight_json(
21791 &mut self,
21792 _: &CopyHighlightJson,
21793 window: &mut Window,
21794 cx: &mut Context<Self>,
21795 ) {
21796 #[derive(Serialize)]
21797 struct Chunk<'a> {
21798 text: String,
21799 highlight: Option<&'a str>,
21800 }
21801
21802 let snapshot = self.buffer.read(cx).snapshot(cx);
21803 let range = self
21804 .selected_text_range(false, window, cx)
21805 .and_then(|selection| {
21806 if selection.range.is_empty() {
21807 None
21808 } else {
21809 Some(
21810 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21811 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21812 )
21813 }
21814 })
21815 .unwrap_or_else(|| 0..snapshot.len());
21816
21817 let chunks = snapshot.chunks(range, true);
21818 let mut lines = Vec::new();
21819 let mut line: VecDeque<Chunk> = VecDeque::new();
21820
21821 let Some(style) = self.style.as_ref() else {
21822 return;
21823 };
21824
21825 for chunk in chunks {
21826 let highlight = chunk
21827 .syntax_highlight_id
21828 .and_then(|id| id.name(&style.syntax));
21829 let mut chunk_lines = chunk.text.split('\n').peekable();
21830 while let Some(text) = chunk_lines.next() {
21831 let mut merged_with_last_token = false;
21832 if let Some(last_token) = line.back_mut()
21833 && last_token.highlight == highlight
21834 {
21835 last_token.text.push_str(text);
21836 merged_with_last_token = true;
21837 }
21838
21839 if !merged_with_last_token {
21840 line.push_back(Chunk {
21841 text: text.into(),
21842 highlight,
21843 });
21844 }
21845
21846 if chunk_lines.peek().is_some() {
21847 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21848 line.pop_front();
21849 }
21850 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21851 line.pop_back();
21852 }
21853
21854 lines.push(mem::take(&mut line));
21855 }
21856 }
21857 }
21858
21859 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21860 return;
21861 };
21862 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21863 }
21864
21865 pub fn open_context_menu(
21866 &mut self,
21867 _: &OpenContextMenu,
21868 window: &mut Window,
21869 cx: &mut Context<Self>,
21870 ) {
21871 self.request_autoscroll(Autoscroll::newest(), cx);
21872 let position = self
21873 .selections
21874 .newest_display(&self.display_snapshot(cx))
21875 .start;
21876 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21877 }
21878
21879 pub fn replay_insert_event(
21880 &mut self,
21881 text: &str,
21882 relative_utf16_range: Option<Range<isize>>,
21883 window: &mut Window,
21884 cx: &mut Context<Self>,
21885 ) {
21886 if !self.input_enabled {
21887 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21888 return;
21889 }
21890 if let Some(relative_utf16_range) = relative_utf16_range {
21891 let selections = self
21892 .selections
21893 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21894 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21895 let new_ranges = selections.into_iter().map(|range| {
21896 let start = OffsetUtf16(
21897 range
21898 .head()
21899 .0
21900 .saturating_add_signed(relative_utf16_range.start),
21901 );
21902 let end = OffsetUtf16(
21903 range
21904 .head()
21905 .0
21906 .saturating_add_signed(relative_utf16_range.end),
21907 );
21908 start..end
21909 });
21910 s.select_ranges(new_ranges);
21911 });
21912 }
21913
21914 self.handle_input(text, window, cx);
21915 }
21916
21917 pub fn is_focused(&self, window: &Window) -> bool {
21918 self.focus_handle.is_focused(window)
21919 }
21920
21921 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21922 cx.emit(EditorEvent::Focused);
21923
21924 if let Some(descendant) = self
21925 .last_focused_descendant
21926 .take()
21927 .and_then(|descendant| descendant.upgrade())
21928 {
21929 window.focus(&descendant);
21930 } else {
21931 if let Some(blame) = self.blame.as_ref() {
21932 blame.update(cx, GitBlame::focus)
21933 }
21934
21935 self.blink_manager.update(cx, BlinkManager::enable);
21936 self.show_cursor_names(window, cx);
21937 self.buffer.update(cx, |buffer, cx| {
21938 buffer.finalize_last_transaction(cx);
21939 if self.leader_id.is_none() {
21940 buffer.set_active_selections(
21941 &self.selections.disjoint_anchors_arc(),
21942 self.selections.line_mode(),
21943 self.cursor_shape,
21944 cx,
21945 );
21946 }
21947 });
21948 }
21949 }
21950
21951 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21952 cx.emit(EditorEvent::FocusedIn)
21953 }
21954
21955 fn handle_focus_out(
21956 &mut self,
21957 event: FocusOutEvent,
21958 _window: &mut Window,
21959 cx: &mut Context<Self>,
21960 ) {
21961 if event.blurred != self.focus_handle {
21962 self.last_focused_descendant = Some(event.blurred);
21963 }
21964 self.selection_drag_state = SelectionDragState::None;
21965 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21966 }
21967
21968 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21969 self.blink_manager.update(cx, BlinkManager::disable);
21970 self.buffer
21971 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21972
21973 if let Some(blame) = self.blame.as_ref() {
21974 blame.update(cx, GitBlame::blur)
21975 }
21976 if !self.hover_state.focused(window, cx) {
21977 hide_hover(self, cx);
21978 }
21979 if !self
21980 .context_menu
21981 .borrow()
21982 .as_ref()
21983 .is_some_and(|context_menu| context_menu.focused(window, cx))
21984 {
21985 self.hide_context_menu(window, cx);
21986 }
21987 self.take_active_edit_prediction(cx);
21988 cx.emit(EditorEvent::Blurred);
21989 cx.notify();
21990 }
21991
21992 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21993 let mut pending: String = window
21994 .pending_input_keystrokes()
21995 .into_iter()
21996 .flatten()
21997 .filter_map(|keystroke| {
21998 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21999 keystroke.key_char.clone()
22000 } else {
22001 None
22002 }
22003 })
22004 .collect();
22005
22006 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22007 pending = "".to_string();
22008 }
22009
22010 let existing_pending = self
22011 .text_highlights::<PendingInput>(cx)
22012 .map(|(_, ranges)| ranges.to_vec());
22013 if existing_pending.is_none() && pending.is_empty() {
22014 return;
22015 }
22016 let transaction =
22017 self.transact(window, cx, |this, window, cx| {
22018 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
22019 let edits = selections
22020 .iter()
22021 .map(|selection| (selection.end..selection.end, pending.clone()));
22022 this.edit(edits, cx);
22023 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22024 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22025 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22026 }));
22027 });
22028 if let Some(existing_ranges) = existing_pending {
22029 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22030 this.edit(edits, cx);
22031 }
22032 });
22033
22034 let snapshot = self.snapshot(window, cx);
22035 let ranges = self
22036 .selections
22037 .all::<usize>(&snapshot.display_snapshot)
22038 .into_iter()
22039 .map(|selection| {
22040 snapshot.buffer_snapshot().anchor_after(selection.end)
22041 ..snapshot
22042 .buffer_snapshot()
22043 .anchor_before(selection.end + pending.len())
22044 })
22045 .collect();
22046
22047 if pending.is_empty() {
22048 self.clear_highlights::<PendingInput>(cx);
22049 } else {
22050 self.highlight_text::<PendingInput>(
22051 ranges,
22052 HighlightStyle {
22053 underline: Some(UnderlineStyle {
22054 thickness: px(1.),
22055 color: None,
22056 wavy: false,
22057 }),
22058 ..Default::default()
22059 },
22060 cx,
22061 );
22062 }
22063
22064 self.ime_transaction = self.ime_transaction.or(transaction);
22065 if let Some(transaction) = self.ime_transaction {
22066 self.buffer.update(cx, |buffer, cx| {
22067 buffer.group_until_transaction(transaction, cx);
22068 });
22069 }
22070
22071 if self.text_highlights::<PendingInput>(cx).is_none() {
22072 self.ime_transaction.take();
22073 }
22074 }
22075
22076 pub fn register_action_renderer(
22077 &mut self,
22078 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22079 ) -> Subscription {
22080 let id = self.next_editor_action_id.post_inc();
22081 self.editor_actions
22082 .borrow_mut()
22083 .insert(id, Box::new(listener));
22084
22085 let editor_actions = self.editor_actions.clone();
22086 Subscription::new(move || {
22087 editor_actions.borrow_mut().remove(&id);
22088 })
22089 }
22090
22091 pub fn register_action<A: Action>(
22092 &mut self,
22093 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22094 ) -> Subscription {
22095 let id = self.next_editor_action_id.post_inc();
22096 let listener = Arc::new(listener);
22097 self.editor_actions.borrow_mut().insert(
22098 id,
22099 Box::new(move |_, window, _| {
22100 let listener = listener.clone();
22101 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22102 let action = action.downcast_ref().unwrap();
22103 if phase == DispatchPhase::Bubble {
22104 listener(action, window, cx)
22105 }
22106 })
22107 }),
22108 );
22109
22110 let editor_actions = self.editor_actions.clone();
22111 Subscription::new(move || {
22112 editor_actions.borrow_mut().remove(&id);
22113 })
22114 }
22115
22116 pub fn file_header_size(&self) -> u32 {
22117 FILE_HEADER_HEIGHT
22118 }
22119
22120 pub fn restore(
22121 &mut self,
22122 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22123 window: &mut Window,
22124 cx: &mut Context<Self>,
22125 ) {
22126 let workspace = self.workspace();
22127 let project = self.project();
22128 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22129 let mut tasks = Vec::new();
22130 for (buffer_id, changes) in revert_changes {
22131 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22132 buffer.update(cx, |buffer, cx| {
22133 buffer.edit(
22134 changes
22135 .into_iter()
22136 .map(|(range, text)| (range, text.to_string())),
22137 None,
22138 cx,
22139 );
22140 });
22141
22142 if let Some(project) =
22143 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22144 {
22145 project.update(cx, |project, cx| {
22146 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22147 })
22148 }
22149 }
22150 }
22151 tasks
22152 });
22153 cx.spawn_in(window, async move |_, cx| {
22154 for (buffer, task) in save_tasks {
22155 let result = task.await;
22156 if result.is_err() {
22157 let Some(path) = buffer
22158 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22159 .ok()
22160 else {
22161 continue;
22162 };
22163 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22164 let Some(task) = cx
22165 .update_window_entity(workspace, |workspace, window, cx| {
22166 workspace
22167 .open_path_preview(path, None, false, false, false, window, cx)
22168 })
22169 .ok()
22170 else {
22171 continue;
22172 };
22173 task.await.log_err();
22174 }
22175 }
22176 }
22177 })
22178 .detach();
22179 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22180 selections.refresh()
22181 });
22182 }
22183
22184 pub fn to_pixel_point(
22185 &self,
22186 source: multi_buffer::Anchor,
22187 editor_snapshot: &EditorSnapshot,
22188 window: &mut Window,
22189 ) -> Option<gpui::Point<Pixels>> {
22190 let source_point = source.to_display_point(editor_snapshot);
22191 self.display_to_pixel_point(source_point, editor_snapshot, window)
22192 }
22193
22194 pub fn display_to_pixel_point(
22195 &self,
22196 source: DisplayPoint,
22197 editor_snapshot: &EditorSnapshot,
22198 window: &mut Window,
22199 ) -> Option<gpui::Point<Pixels>> {
22200 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22201 let text_layout_details = self.text_layout_details(window);
22202 let scroll_top = text_layout_details
22203 .scroll_anchor
22204 .scroll_position(editor_snapshot)
22205 .y;
22206
22207 if source.row().as_f64() < scroll_top.floor() {
22208 return None;
22209 }
22210 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22211 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22212 Some(gpui::Point::new(source_x, source_y))
22213 }
22214
22215 pub fn has_visible_completions_menu(&self) -> bool {
22216 !self.edit_prediction_preview_is_active()
22217 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22218 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22219 })
22220 }
22221
22222 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22223 if self.mode.is_minimap() {
22224 return;
22225 }
22226 self.addons
22227 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22228 }
22229
22230 pub fn unregister_addon<T: Addon>(&mut self) {
22231 self.addons.remove(&std::any::TypeId::of::<T>());
22232 }
22233
22234 pub fn addon<T: Addon>(&self) -> Option<&T> {
22235 let type_id = std::any::TypeId::of::<T>();
22236 self.addons
22237 .get(&type_id)
22238 .and_then(|item| item.to_any().downcast_ref::<T>())
22239 }
22240
22241 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22242 let type_id = std::any::TypeId::of::<T>();
22243 self.addons
22244 .get_mut(&type_id)
22245 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22246 }
22247
22248 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22249 let text_layout_details = self.text_layout_details(window);
22250 let style = &text_layout_details.editor_style;
22251 let font_id = window.text_system().resolve_font(&style.text.font());
22252 let font_size = style.text.font_size.to_pixels(window.rem_size());
22253 let line_height = style.text.line_height_in_pixels(window.rem_size());
22254 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22255 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22256
22257 CharacterDimensions {
22258 em_width,
22259 em_advance,
22260 line_height,
22261 }
22262 }
22263
22264 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22265 self.load_diff_task.clone()
22266 }
22267
22268 fn read_metadata_from_db(
22269 &mut self,
22270 item_id: u64,
22271 workspace_id: WorkspaceId,
22272 window: &mut Window,
22273 cx: &mut Context<Editor>,
22274 ) {
22275 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22276 && !self.mode.is_minimap()
22277 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22278 {
22279 let buffer_snapshot = OnceCell::new();
22280
22281 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22282 && !folds.is_empty()
22283 {
22284 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22285 self.fold_ranges(
22286 folds
22287 .into_iter()
22288 .map(|(start, end)| {
22289 snapshot.clip_offset(start, Bias::Left)
22290 ..snapshot.clip_offset(end, Bias::Right)
22291 })
22292 .collect(),
22293 false,
22294 window,
22295 cx,
22296 );
22297 }
22298
22299 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22300 && !selections.is_empty()
22301 {
22302 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22303 // skip adding the initial selection to selection history
22304 self.selection_history.mode = SelectionHistoryMode::Skipping;
22305 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22306 s.select_ranges(selections.into_iter().map(|(start, end)| {
22307 snapshot.clip_offset(start, Bias::Left)
22308 ..snapshot.clip_offset(end, Bias::Right)
22309 }));
22310 });
22311 self.selection_history.mode = SelectionHistoryMode::Normal;
22312 };
22313 }
22314
22315 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22316 }
22317
22318 fn update_lsp_data(
22319 &mut self,
22320 for_buffer: Option<BufferId>,
22321 window: &mut Window,
22322 cx: &mut Context<'_, Self>,
22323 ) {
22324 self.pull_diagnostics(for_buffer, window, cx);
22325 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22326 }
22327
22328 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22329 if self.ignore_lsp_data() {
22330 return;
22331 }
22332 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22333 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22334 }
22335 }
22336
22337 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22338 if self.ignore_lsp_data() {
22339 return;
22340 }
22341
22342 if !self.registered_buffers.contains_key(&buffer_id)
22343 && let Some(project) = self.project.as_ref()
22344 {
22345 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22346 project.update(cx, |project, cx| {
22347 self.registered_buffers.insert(
22348 buffer_id,
22349 project.register_buffer_with_language_servers(&buffer, cx),
22350 );
22351 });
22352 } else {
22353 self.registered_buffers.remove(&buffer_id);
22354 }
22355 }
22356 }
22357
22358 fn ignore_lsp_data(&self) -> bool {
22359 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22360 // skip any LSP updates for it.
22361 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22362 }
22363}
22364
22365fn edit_for_markdown_paste<'a>(
22366 buffer: &MultiBufferSnapshot,
22367 range: Range<usize>,
22368 to_insert: &'a str,
22369 url: Option<url::Url>,
22370) -> (Range<usize>, Cow<'a, str>) {
22371 if url.is_none() {
22372 return (range, Cow::Borrowed(to_insert));
22373 };
22374
22375 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22376
22377 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22378 Cow::Borrowed(to_insert)
22379 } else {
22380 Cow::Owned(format!("[{old_text}]({to_insert})"))
22381 };
22382 (range, new_text)
22383}
22384
22385#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
22386pub enum VimFlavor {
22387 Vim,
22388 Helix,
22389}
22390
22391pub fn vim_flavor(cx: &App) -> Option<VimFlavor> {
22392 if vim_mode_setting::HelixModeSetting::try_get(cx)
22393 .map(|helix_mode| helix_mode.0)
22394 .unwrap_or(false)
22395 {
22396 Some(VimFlavor::Helix)
22397 } else if vim_mode_setting::VimModeSetting::try_get(cx)
22398 .map(|vim_mode| vim_mode.0)
22399 .unwrap_or(false)
22400 {
22401 Some(VimFlavor::Vim)
22402 } else {
22403 None // neither vim nor helix mode
22404 }
22405}
22406
22407fn process_completion_for_edit(
22408 completion: &Completion,
22409 intent: CompletionIntent,
22410 buffer: &Entity<Buffer>,
22411 cursor_position: &text::Anchor,
22412 cx: &mut Context<Editor>,
22413) -> CompletionEdit {
22414 let buffer = buffer.read(cx);
22415 let buffer_snapshot = buffer.snapshot();
22416 let (snippet, new_text) = if completion.is_snippet() {
22417 let mut snippet_source = completion.new_text.clone();
22418 // Workaround for typescript language server issues so that methods don't expand within
22419 // strings and functions with type expressions. The previous point is used because the query
22420 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22421 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22422 let previous_point = if previous_point.column > 0 {
22423 cursor_position.to_previous_offset(&buffer_snapshot)
22424 } else {
22425 cursor_position.to_offset(&buffer_snapshot)
22426 };
22427 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22428 && scope.prefers_label_for_snippet_in_completion()
22429 && let Some(label) = completion.label()
22430 && matches!(
22431 completion.kind(),
22432 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22433 )
22434 {
22435 snippet_source = label;
22436 }
22437 match Snippet::parse(&snippet_source).log_err() {
22438 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22439 None => (None, completion.new_text.clone()),
22440 }
22441 } else {
22442 (None, completion.new_text.clone())
22443 };
22444
22445 let mut range_to_replace = {
22446 let replace_range = &completion.replace_range;
22447 if let CompletionSource::Lsp {
22448 insert_range: Some(insert_range),
22449 ..
22450 } = &completion.source
22451 {
22452 debug_assert_eq!(
22453 insert_range.start, replace_range.start,
22454 "insert_range and replace_range should start at the same position"
22455 );
22456 debug_assert!(
22457 insert_range
22458 .start
22459 .cmp(cursor_position, &buffer_snapshot)
22460 .is_le(),
22461 "insert_range should start before or at cursor position"
22462 );
22463 debug_assert!(
22464 replace_range
22465 .start
22466 .cmp(cursor_position, &buffer_snapshot)
22467 .is_le(),
22468 "replace_range should start before or at cursor position"
22469 );
22470
22471 let should_replace = match intent {
22472 CompletionIntent::CompleteWithInsert => false,
22473 CompletionIntent::CompleteWithReplace => true,
22474 CompletionIntent::Complete | CompletionIntent::Compose => {
22475 let insert_mode =
22476 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22477 .completions
22478 .lsp_insert_mode;
22479 match insert_mode {
22480 LspInsertMode::Insert => false,
22481 LspInsertMode::Replace => true,
22482 LspInsertMode::ReplaceSubsequence => {
22483 let mut text_to_replace = buffer.chars_for_range(
22484 buffer.anchor_before(replace_range.start)
22485 ..buffer.anchor_after(replace_range.end),
22486 );
22487 let mut current_needle = text_to_replace.next();
22488 for haystack_ch in completion.label.text.chars() {
22489 if let Some(needle_ch) = current_needle
22490 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22491 {
22492 current_needle = text_to_replace.next();
22493 }
22494 }
22495 current_needle.is_none()
22496 }
22497 LspInsertMode::ReplaceSuffix => {
22498 if replace_range
22499 .end
22500 .cmp(cursor_position, &buffer_snapshot)
22501 .is_gt()
22502 {
22503 let range_after_cursor = *cursor_position..replace_range.end;
22504 let text_after_cursor = buffer
22505 .text_for_range(
22506 buffer.anchor_before(range_after_cursor.start)
22507 ..buffer.anchor_after(range_after_cursor.end),
22508 )
22509 .collect::<String>()
22510 .to_ascii_lowercase();
22511 completion
22512 .label
22513 .text
22514 .to_ascii_lowercase()
22515 .ends_with(&text_after_cursor)
22516 } else {
22517 true
22518 }
22519 }
22520 }
22521 }
22522 };
22523
22524 if should_replace {
22525 replace_range.clone()
22526 } else {
22527 insert_range.clone()
22528 }
22529 } else {
22530 replace_range.clone()
22531 }
22532 };
22533
22534 if range_to_replace
22535 .end
22536 .cmp(cursor_position, &buffer_snapshot)
22537 .is_lt()
22538 {
22539 range_to_replace.end = *cursor_position;
22540 }
22541
22542 CompletionEdit {
22543 new_text,
22544 replace_range: range_to_replace.to_offset(buffer),
22545 snippet,
22546 }
22547}
22548
22549struct CompletionEdit {
22550 new_text: String,
22551 replace_range: Range<usize>,
22552 snippet: Option<Snippet>,
22553}
22554
22555fn insert_extra_newline_brackets(
22556 buffer: &MultiBufferSnapshot,
22557 range: Range<usize>,
22558 language: &language::LanguageScope,
22559) -> bool {
22560 let leading_whitespace_len = buffer
22561 .reversed_chars_at(range.start)
22562 .take_while(|c| c.is_whitespace() && *c != '\n')
22563 .map(|c| c.len_utf8())
22564 .sum::<usize>();
22565 let trailing_whitespace_len = buffer
22566 .chars_at(range.end)
22567 .take_while(|c| c.is_whitespace() && *c != '\n')
22568 .map(|c| c.len_utf8())
22569 .sum::<usize>();
22570 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22571
22572 language.brackets().any(|(pair, enabled)| {
22573 let pair_start = pair.start.trim_end();
22574 let pair_end = pair.end.trim_start();
22575
22576 enabled
22577 && pair.newline
22578 && buffer.contains_str_at(range.end, pair_end)
22579 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22580 })
22581}
22582
22583fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22584 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22585 [(buffer, range, _)] => (*buffer, range.clone()),
22586 _ => return false,
22587 };
22588 let pair = {
22589 let mut result: Option<BracketMatch> = None;
22590
22591 for pair in buffer
22592 .all_bracket_ranges(range.clone())
22593 .filter(move |pair| {
22594 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22595 })
22596 {
22597 let len = pair.close_range.end - pair.open_range.start;
22598
22599 if let Some(existing) = &result {
22600 let existing_len = existing.close_range.end - existing.open_range.start;
22601 if len > existing_len {
22602 continue;
22603 }
22604 }
22605
22606 result = Some(pair);
22607 }
22608
22609 result
22610 };
22611 let Some(pair) = pair else {
22612 return false;
22613 };
22614 pair.newline_only
22615 && buffer
22616 .chars_for_range(pair.open_range.end..range.start)
22617 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22618 .all(|c| c.is_whitespace() && c != '\n')
22619}
22620
22621fn update_uncommitted_diff_for_buffer(
22622 editor: Entity<Editor>,
22623 project: &Entity<Project>,
22624 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22625 buffer: Entity<MultiBuffer>,
22626 cx: &mut App,
22627) -> Task<()> {
22628 let mut tasks = Vec::new();
22629 project.update(cx, |project, cx| {
22630 for buffer in buffers {
22631 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22632 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22633 }
22634 }
22635 });
22636 cx.spawn(async move |cx| {
22637 let diffs = future::join_all(tasks).await;
22638 if editor
22639 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22640 .unwrap_or(false)
22641 {
22642 return;
22643 }
22644
22645 buffer
22646 .update(cx, |buffer, cx| {
22647 for diff in diffs.into_iter().flatten() {
22648 buffer.add_diff(diff, cx);
22649 }
22650 })
22651 .ok();
22652 })
22653}
22654
22655fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22656 let tab_size = tab_size.get() as usize;
22657 let mut width = offset;
22658
22659 for ch in text.chars() {
22660 width += if ch == '\t' {
22661 tab_size - (width % tab_size)
22662 } else {
22663 1
22664 };
22665 }
22666
22667 width - offset
22668}
22669
22670#[cfg(test)]
22671mod tests {
22672 use super::*;
22673
22674 #[test]
22675 fn test_string_size_with_expanded_tabs() {
22676 let nz = |val| NonZeroU32::new(val).unwrap();
22677 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22678 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22679 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22680 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22681 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22682 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22683 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22684 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22685 }
22686}
22687
22688/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22689struct WordBreakingTokenizer<'a> {
22690 input: &'a str,
22691}
22692
22693impl<'a> WordBreakingTokenizer<'a> {
22694 fn new(input: &'a str) -> Self {
22695 Self { input }
22696 }
22697}
22698
22699fn is_char_ideographic(ch: char) -> bool {
22700 use unicode_script::Script::*;
22701 use unicode_script::UnicodeScript;
22702 matches!(ch.script(), Han | Tangut | Yi)
22703}
22704
22705fn is_grapheme_ideographic(text: &str) -> bool {
22706 text.chars().any(is_char_ideographic)
22707}
22708
22709fn is_grapheme_whitespace(text: &str) -> bool {
22710 text.chars().any(|x| x.is_whitespace())
22711}
22712
22713fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22714 text.chars()
22715 .next()
22716 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22717}
22718
22719#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22720enum WordBreakToken<'a> {
22721 Word { token: &'a str, grapheme_len: usize },
22722 InlineWhitespace { token: &'a str, grapheme_len: usize },
22723 Newline,
22724}
22725
22726impl<'a> Iterator for WordBreakingTokenizer<'a> {
22727 /// Yields a span, the count of graphemes in the token, and whether it was
22728 /// whitespace. Note that it also breaks at word boundaries.
22729 type Item = WordBreakToken<'a>;
22730
22731 fn next(&mut self) -> Option<Self::Item> {
22732 use unicode_segmentation::UnicodeSegmentation;
22733 if self.input.is_empty() {
22734 return None;
22735 }
22736
22737 let mut iter = self.input.graphemes(true).peekable();
22738 let mut offset = 0;
22739 let mut grapheme_len = 0;
22740 if let Some(first_grapheme) = iter.next() {
22741 let is_newline = first_grapheme == "\n";
22742 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22743 offset += first_grapheme.len();
22744 grapheme_len += 1;
22745 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22746 if let Some(grapheme) = iter.peek().copied()
22747 && should_stay_with_preceding_ideograph(grapheme)
22748 {
22749 offset += grapheme.len();
22750 grapheme_len += 1;
22751 }
22752 } else {
22753 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22754 let mut next_word_bound = words.peek().copied();
22755 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22756 next_word_bound = words.next();
22757 }
22758 while let Some(grapheme) = iter.peek().copied() {
22759 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22760 break;
22761 };
22762 if is_grapheme_whitespace(grapheme) != is_whitespace
22763 || (grapheme == "\n") != is_newline
22764 {
22765 break;
22766 };
22767 offset += grapheme.len();
22768 grapheme_len += 1;
22769 iter.next();
22770 }
22771 }
22772 let token = &self.input[..offset];
22773 self.input = &self.input[offset..];
22774 if token == "\n" {
22775 Some(WordBreakToken::Newline)
22776 } else if is_whitespace {
22777 Some(WordBreakToken::InlineWhitespace {
22778 token,
22779 grapheme_len,
22780 })
22781 } else {
22782 Some(WordBreakToken::Word {
22783 token,
22784 grapheme_len,
22785 })
22786 }
22787 } else {
22788 None
22789 }
22790 }
22791}
22792
22793#[test]
22794fn test_word_breaking_tokenizer() {
22795 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22796 ("", &[]),
22797 (" ", &[whitespace(" ", 2)]),
22798 ("Ʒ", &[word("Ʒ", 1)]),
22799 ("Ǽ", &[word("Ǽ", 1)]),
22800 ("⋑", &[word("⋑", 1)]),
22801 ("⋑⋑", &[word("⋑⋑", 2)]),
22802 (
22803 "原理,进而",
22804 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22805 ),
22806 (
22807 "hello world",
22808 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22809 ),
22810 (
22811 "hello, world",
22812 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22813 ),
22814 (
22815 " hello world",
22816 &[
22817 whitespace(" ", 2),
22818 word("hello", 5),
22819 whitespace(" ", 1),
22820 word("world", 5),
22821 ],
22822 ),
22823 (
22824 "这是什么 \n 钢笔",
22825 &[
22826 word("这", 1),
22827 word("是", 1),
22828 word("什", 1),
22829 word("么", 1),
22830 whitespace(" ", 1),
22831 newline(),
22832 whitespace(" ", 1),
22833 word("钢", 1),
22834 word("笔", 1),
22835 ],
22836 ),
22837 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22838 ];
22839
22840 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22841 WordBreakToken::Word {
22842 token,
22843 grapheme_len,
22844 }
22845 }
22846
22847 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22848 WordBreakToken::InlineWhitespace {
22849 token,
22850 grapheme_len,
22851 }
22852 }
22853
22854 fn newline() -> WordBreakToken<'static> {
22855 WordBreakToken::Newline
22856 }
22857
22858 for (input, result) in tests {
22859 assert_eq!(
22860 WordBreakingTokenizer::new(input)
22861 .collect::<Vec<_>>()
22862 .as_slice(),
22863 *result,
22864 );
22865 }
22866}
22867
22868fn wrap_with_prefix(
22869 first_line_prefix: String,
22870 subsequent_lines_prefix: String,
22871 unwrapped_text: String,
22872 wrap_column: usize,
22873 tab_size: NonZeroU32,
22874 preserve_existing_whitespace: bool,
22875) -> String {
22876 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22877 let subsequent_lines_prefix_len =
22878 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22879 let mut wrapped_text = String::new();
22880 let mut current_line = first_line_prefix;
22881 let mut is_first_line = true;
22882
22883 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22884 let mut current_line_len = first_line_prefix_len;
22885 let mut in_whitespace = false;
22886 for token in tokenizer {
22887 let have_preceding_whitespace = in_whitespace;
22888 match token {
22889 WordBreakToken::Word {
22890 token,
22891 grapheme_len,
22892 } => {
22893 in_whitespace = false;
22894 let current_prefix_len = if is_first_line {
22895 first_line_prefix_len
22896 } else {
22897 subsequent_lines_prefix_len
22898 };
22899 if current_line_len + grapheme_len > wrap_column
22900 && current_line_len != current_prefix_len
22901 {
22902 wrapped_text.push_str(current_line.trim_end());
22903 wrapped_text.push('\n');
22904 is_first_line = false;
22905 current_line = subsequent_lines_prefix.clone();
22906 current_line_len = subsequent_lines_prefix_len;
22907 }
22908 current_line.push_str(token);
22909 current_line_len += grapheme_len;
22910 }
22911 WordBreakToken::InlineWhitespace {
22912 mut token,
22913 mut grapheme_len,
22914 } => {
22915 in_whitespace = true;
22916 if have_preceding_whitespace && !preserve_existing_whitespace {
22917 continue;
22918 }
22919 if !preserve_existing_whitespace {
22920 // Keep a single whitespace grapheme as-is
22921 if let Some(first) =
22922 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22923 {
22924 token = first;
22925 } else {
22926 token = " ";
22927 }
22928 grapheme_len = 1;
22929 }
22930 let current_prefix_len = if is_first_line {
22931 first_line_prefix_len
22932 } else {
22933 subsequent_lines_prefix_len
22934 };
22935 if current_line_len + grapheme_len > wrap_column {
22936 wrapped_text.push_str(current_line.trim_end());
22937 wrapped_text.push('\n');
22938 is_first_line = false;
22939 current_line = subsequent_lines_prefix.clone();
22940 current_line_len = subsequent_lines_prefix_len;
22941 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22942 current_line.push_str(token);
22943 current_line_len += grapheme_len;
22944 }
22945 }
22946 WordBreakToken::Newline => {
22947 in_whitespace = true;
22948 let current_prefix_len = if is_first_line {
22949 first_line_prefix_len
22950 } else {
22951 subsequent_lines_prefix_len
22952 };
22953 if preserve_existing_whitespace {
22954 wrapped_text.push_str(current_line.trim_end());
22955 wrapped_text.push('\n');
22956 is_first_line = false;
22957 current_line = subsequent_lines_prefix.clone();
22958 current_line_len = subsequent_lines_prefix_len;
22959 } else if have_preceding_whitespace {
22960 continue;
22961 } else if current_line_len + 1 > wrap_column
22962 && current_line_len != current_prefix_len
22963 {
22964 wrapped_text.push_str(current_line.trim_end());
22965 wrapped_text.push('\n');
22966 is_first_line = false;
22967 current_line = subsequent_lines_prefix.clone();
22968 current_line_len = subsequent_lines_prefix_len;
22969 } else if current_line_len != current_prefix_len {
22970 current_line.push(' ');
22971 current_line_len += 1;
22972 }
22973 }
22974 }
22975 }
22976
22977 if !current_line.is_empty() {
22978 wrapped_text.push_str(¤t_line);
22979 }
22980 wrapped_text
22981}
22982
22983#[test]
22984fn test_wrap_with_prefix() {
22985 assert_eq!(
22986 wrap_with_prefix(
22987 "# ".to_string(),
22988 "# ".to_string(),
22989 "abcdefg".to_string(),
22990 4,
22991 NonZeroU32::new(4).unwrap(),
22992 false,
22993 ),
22994 "# abcdefg"
22995 );
22996 assert_eq!(
22997 wrap_with_prefix(
22998 "".to_string(),
22999 "".to_string(),
23000 "\thello world".to_string(),
23001 8,
23002 NonZeroU32::new(4).unwrap(),
23003 false,
23004 ),
23005 "hello\nworld"
23006 );
23007 assert_eq!(
23008 wrap_with_prefix(
23009 "// ".to_string(),
23010 "// ".to_string(),
23011 "xx \nyy zz aa bb cc".to_string(),
23012 12,
23013 NonZeroU32::new(4).unwrap(),
23014 false,
23015 ),
23016 "// xx yy zz\n// aa bb cc"
23017 );
23018 assert_eq!(
23019 wrap_with_prefix(
23020 String::new(),
23021 String::new(),
23022 "这是什么 \n 钢笔".to_string(),
23023 3,
23024 NonZeroU32::new(4).unwrap(),
23025 false,
23026 ),
23027 "这是什\n么 钢\n笔"
23028 );
23029 assert_eq!(
23030 wrap_with_prefix(
23031 String::new(),
23032 String::new(),
23033 format!("foo{}bar", '\u{2009}'), // thin space
23034 80,
23035 NonZeroU32::new(4).unwrap(),
23036 false,
23037 ),
23038 format!("foo{}bar", '\u{2009}')
23039 );
23040}
23041
23042pub trait CollaborationHub {
23043 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23044 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23045 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23046}
23047
23048impl CollaborationHub for Entity<Project> {
23049 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23050 self.read(cx).collaborators()
23051 }
23052
23053 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23054 self.read(cx).user_store().read(cx).participant_indices()
23055 }
23056
23057 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23058 let this = self.read(cx);
23059 let user_ids = this.collaborators().values().map(|c| c.user_id);
23060 this.user_store().read(cx).participant_names(user_ids, cx)
23061 }
23062}
23063
23064pub trait SemanticsProvider {
23065 fn hover(
23066 &self,
23067 buffer: &Entity<Buffer>,
23068 position: text::Anchor,
23069 cx: &mut App,
23070 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23071
23072 fn inline_values(
23073 &self,
23074 buffer_handle: Entity<Buffer>,
23075 range: Range<text::Anchor>,
23076 cx: &mut App,
23077 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23078
23079 fn applicable_inlay_chunks(
23080 &self,
23081 buffer: &Entity<Buffer>,
23082 ranges: &[Range<text::Anchor>],
23083 cx: &mut App,
23084 ) -> Vec<Range<BufferRow>>;
23085
23086 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23087
23088 fn inlay_hints(
23089 &self,
23090 invalidate: InvalidationStrategy,
23091 buffer: Entity<Buffer>,
23092 ranges: Vec<Range<text::Anchor>>,
23093 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23094 cx: &mut App,
23095 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23096
23097 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23098
23099 fn document_highlights(
23100 &self,
23101 buffer: &Entity<Buffer>,
23102 position: text::Anchor,
23103 cx: &mut App,
23104 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23105
23106 fn definitions(
23107 &self,
23108 buffer: &Entity<Buffer>,
23109 position: text::Anchor,
23110 kind: GotoDefinitionKind,
23111 cx: &mut App,
23112 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23113
23114 fn range_for_rename(
23115 &self,
23116 buffer: &Entity<Buffer>,
23117 position: text::Anchor,
23118 cx: &mut App,
23119 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23120
23121 fn perform_rename(
23122 &self,
23123 buffer: &Entity<Buffer>,
23124 position: text::Anchor,
23125 new_name: String,
23126 cx: &mut App,
23127 ) -> Option<Task<Result<ProjectTransaction>>>;
23128}
23129
23130pub trait CompletionProvider {
23131 fn completions(
23132 &self,
23133 excerpt_id: ExcerptId,
23134 buffer: &Entity<Buffer>,
23135 buffer_position: text::Anchor,
23136 trigger: CompletionContext,
23137 window: &mut Window,
23138 cx: &mut Context<Editor>,
23139 ) -> Task<Result<Vec<CompletionResponse>>>;
23140
23141 fn resolve_completions(
23142 &self,
23143 _buffer: Entity<Buffer>,
23144 _completion_indices: Vec<usize>,
23145 _completions: Rc<RefCell<Box<[Completion]>>>,
23146 _cx: &mut Context<Editor>,
23147 ) -> Task<Result<bool>> {
23148 Task::ready(Ok(false))
23149 }
23150
23151 fn apply_additional_edits_for_completion(
23152 &self,
23153 _buffer: Entity<Buffer>,
23154 _completions: Rc<RefCell<Box<[Completion]>>>,
23155 _completion_index: usize,
23156 _push_to_history: bool,
23157 _cx: &mut Context<Editor>,
23158 ) -> Task<Result<Option<language::Transaction>>> {
23159 Task::ready(Ok(None))
23160 }
23161
23162 fn is_completion_trigger(
23163 &self,
23164 buffer: &Entity<Buffer>,
23165 position: language::Anchor,
23166 text: &str,
23167 trigger_in_words: bool,
23168 menu_is_open: bool,
23169 cx: &mut Context<Editor>,
23170 ) -> bool;
23171
23172 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23173
23174 fn sort_completions(&self) -> bool {
23175 true
23176 }
23177
23178 fn filter_completions(&self) -> bool {
23179 true
23180 }
23181
23182 fn show_snippets(&self) -> bool {
23183 false
23184 }
23185}
23186
23187pub trait CodeActionProvider {
23188 fn id(&self) -> Arc<str>;
23189
23190 fn code_actions(
23191 &self,
23192 buffer: &Entity<Buffer>,
23193 range: Range<text::Anchor>,
23194 window: &mut Window,
23195 cx: &mut App,
23196 ) -> Task<Result<Vec<CodeAction>>>;
23197
23198 fn apply_code_action(
23199 &self,
23200 buffer_handle: Entity<Buffer>,
23201 action: CodeAction,
23202 excerpt_id: ExcerptId,
23203 push_to_history: bool,
23204 window: &mut Window,
23205 cx: &mut App,
23206 ) -> Task<Result<ProjectTransaction>>;
23207}
23208
23209impl CodeActionProvider for Entity<Project> {
23210 fn id(&self) -> Arc<str> {
23211 "project".into()
23212 }
23213
23214 fn code_actions(
23215 &self,
23216 buffer: &Entity<Buffer>,
23217 range: Range<text::Anchor>,
23218 _window: &mut Window,
23219 cx: &mut App,
23220 ) -> Task<Result<Vec<CodeAction>>> {
23221 self.update(cx, |project, cx| {
23222 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23223 let code_actions = project.code_actions(buffer, range, None, cx);
23224 cx.background_spawn(async move {
23225 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23226 Ok(code_lens_actions
23227 .context("code lens fetch")?
23228 .into_iter()
23229 .flatten()
23230 .chain(
23231 code_actions
23232 .context("code action fetch")?
23233 .into_iter()
23234 .flatten(),
23235 )
23236 .collect())
23237 })
23238 })
23239 }
23240
23241 fn apply_code_action(
23242 &self,
23243 buffer_handle: Entity<Buffer>,
23244 action: CodeAction,
23245 _excerpt_id: ExcerptId,
23246 push_to_history: bool,
23247 _window: &mut Window,
23248 cx: &mut App,
23249 ) -> Task<Result<ProjectTransaction>> {
23250 self.update(cx, |project, cx| {
23251 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23252 })
23253 }
23254}
23255
23256fn snippet_completions(
23257 project: &Project,
23258 buffer: &Entity<Buffer>,
23259 buffer_anchor: text::Anchor,
23260 classifier: CharClassifier,
23261 cx: &mut App,
23262) -> Task<Result<CompletionResponse>> {
23263 let languages = buffer.read(cx).languages_at(buffer_anchor);
23264 let snippet_store = project.snippets().read(cx);
23265
23266 let scopes: Vec<_> = languages
23267 .iter()
23268 .filter_map(|language| {
23269 let language_name = language.lsp_id();
23270 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23271
23272 if snippets.is_empty() {
23273 None
23274 } else {
23275 Some((language.default_scope(), snippets))
23276 }
23277 })
23278 .collect();
23279
23280 if scopes.is_empty() {
23281 return Task::ready(Ok(CompletionResponse {
23282 completions: vec![],
23283 display_options: CompletionDisplayOptions::default(),
23284 is_incomplete: false,
23285 }));
23286 }
23287
23288 let snapshot = buffer.read(cx).text_snapshot();
23289 let executor = cx.background_executor().clone();
23290
23291 cx.background_spawn(async move {
23292 let is_word_char = |c| classifier.is_word(c);
23293
23294 let mut is_incomplete = false;
23295 let mut completions: Vec<Completion> = Vec::new();
23296
23297 const MAX_PREFIX_LEN: usize = 128;
23298 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
23299 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
23300 let window_start = snapshot.clip_offset(window_start, Bias::Left);
23301
23302 let max_buffer_window: String = snapshot
23303 .text_for_range(window_start..buffer_offset)
23304 .collect();
23305
23306 if max_buffer_window.is_empty() {
23307 return Ok(CompletionResponse {
23308 completions: vec![],
23309 display_options: CompletionDisplayOptions::default(),
23310 is_incomplete: true,
23311 });
23312 }
23313
23314 for (_scope, snippets) in scopes.into_iter() {
23315 // Sort snippets by word count to match longer snippet prefixes first.
23316 let mut sorted_snippet_candidates = snippets
23317 .iter()
23318 .enumerate()
23319 .flat_map(|(snippet_ix, snippet)| {
23320 snippet
23321 .prefix
23322 .iter()
23323 .enumerate()
23324 .map(move |(prefix_ix, prefix)| {
23325 let word_count =
23326 snippet_candidate_suffixes(prefix, is_word_char).count();
23327 ((snippet_ix, prefix_ix), prefix, word_count)
23328 })
23329 })
23330 .collect_vec();
23331 sorted_snippet_candidates
23332 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
23333
23334 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
23335
23336 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
23337 .take(
23338 sorted_snippet_candidates
23339 .first()
23340 .map(|(_, _, word_count)| *word_count)
23341 .unwrap_or_default(),
23342 )
23343 .collect_vec();
23344
23345 const MAX_RESULTS: usize = 100;
23346 // Each match also remembers how many characters from the buffer it consumed
23347 let mut matches: Vec<(StringMatch, usize)> = vec![];
23348
23349 let mut snippet_list_cutoff_index = 0;
23350 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
23351 let word_count = buffer_index + 1;
23352 // Increase `snippet_list_cutoff_index` until we have all of the
23353 // snippets with sufficiently many words.
23354 while sorted_snippet_candidates
23355 .get(snippet_list_cutoff_index)
23356 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
23357 *snippet_word_count >= word_count
23358 })
23359 {
23360 snippet_list_cutoff_index += 1;
23361 }
23362
23363 // Take only the candidates with at least `word_count` many words
23364 let snippet_candidates_at_word_len =
23365 &sorted_snippet_candidates[..snippet_list_cutoff_index];
23366
23367 let candidates = snippet_candidates_at_word_len
23368 .iter()
23369 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
23370 .enumerate() // index in `sorted_snippet_candidates`
23371 // First char must match
23372 .filter(|(_ix, prefix)| {
23373 itertools::equal(
23374 prefix
23375 .chars()
23376 .next()
23377 .into_iter()
23378 .flat_map(|c| c.to_lowercase()),
23379 buffer_window
23380 .chars()
23381 .next()
23382 .into_iter()
23383 .flat_map(|c| c.to_lowercase()),
23384 )
23385 })
23386 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
23387 .collect::<Vec<StringMatchCandidate>>();
23388
23389 matches.extend(
23390 fuzzy::match_strings(
23391 &candidates,
23392 &buffer_window,
23393 buffer_window.chars().any(|c| c.is_uppercase()),
23394 true,
23395 MAX_RESULTS - matches.len(), // always prioritize longer snippets
23396 &Default::default(),
23397 executor.clone(),
23398 )
23399 .await
23400 .into_iter()
23401 .map(|string_match| (string_match, buffer_window.len())),
23402 );
23403
23404 if matches.len() >= MAX_RESULTS {
23405 break;
23406 }
23407 }
23408
23409 let to_lsp = |point: &text::Anchor| {
23410 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23411 point_to_lsp(end)
23412 };
23413 let lsp_end = to_lsp(&buffer_anchor);
23414
23415 if matches.len() >= MAX_RESULTS {
23416 is_incomplete = true;
23417 }
23418
23419 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
23420 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
23421 sorted_snippet_candidates[string_match.candidate_id];
23422 let snippet = &snippets[snippet_index];
23423 let start = buffer_offset - buffer_window_len;
23424 let start = snapshot.anchor_before(start);
23425 let range = start..buffer_anchor;
23426 let lsp_start = to_lsp(&start);
23427 let lsp_range = lsp::Range {
23428 start: lsp_start,
23429 end: lsp_end,
23430 };
23431 Completion {
23432 replace_range: range,
23433 new_text: snippet.body.clone(),
23434 source: CompletionSource::Lsp {
23435 insert_range: None,
23436 server_id: LanguageServerId(usize::MAX),
23437 resolved: true,
23438 lsp_completion: Box::new(lsp::CompletionItem {
23439 label: snippet.prefix.first().unwrap().clone(),
23440 kind: Some(CompletionItemKind::SNIPPET),
23441 label_details: snippet.description.as_ref().map(|description| {
23442 lsp::CompletionItemLabelDetails {
23443 detail: Some(description.clone()),
23444 description: None,
23445 }
23446 }),
23447 insert_text_format: Some(InsertTextFormat::SNIPPET),
23448 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23449 lsp::InsertReplaceEdit {
23450 new_text: snippet.body.clone(),
23451 insert: lsp_range,
23452 replace: lsp_range,
23453 },
23454 )),
23455 filter_text: Some(snippet.body.clone()),
23456 sort_text: Some(char::MAX.to_string()),
23457 ..lsp::CompletionItem::default()
23458 }),
23459 lsp_defaults: None,
23460 },
23461 label: CodeLabel {
23462 text: matching_prefix.clone(),
23463 runs: Vec::new(),
23464 filter_range: 0..matching_prefix.len(),
23465 },
23466 icon_path: None,
23467 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23468 single_line: snippet.name.clone().into(),
23469 plain_text: snippet
23470 .description
23471 .clone()
23472 .map(|description| description.into()),
23473 }),
23474 insert_text_mode: None,
23475 confirm: None,
23476 match_start: Some(start),
23477 snippet_deduplication_key: Some((snippet_index, prefix_index)),
23478 }
23479 }));
23480 }
23481
23482 Ok(CompletionResponse {
23483 completions,
23484 display_options: CompletionDisplayOptions::default(),
23485 is_incomplete,
23486 })
23487 })
23488}
23489
23490impl CompletionProvider for Entity<Project> {
23491 fn completions(
23492 &self,
23493 _excerpt_id: ExcerptId,
23494 buffer: &Entity<Buffer>,
23495 buffer_position: text::Anchor,
23496 options: CompletionContext,
23497 _window: &mut Window,
23498 cx: &mut Context<Editor>,
23499 ) -> Task<Result<Vec<CompletionResponse>>> {
23500 self.update(cx, |project, cx| {
23501 let task = project.completions(buffer, buffer_position, options, cx);
23502 cx.background_spawn(task)
23503 })
23504 }
23505
23506 fn resolve_completions(
23507 &self,
23508 buffer: Entity<Buffer>,
23509 completion_indices: Vec<usize>,
23510 completions: Rc<RefCell<Box<[Completion]>>>,
23511 cx: &mut Context<Editor>,
23512 ) -> Task<Result<bool>> {
23513 self.update(cx, |project, cx| {
23514 project.lsp_store().update(cx, |lsp_store, cx| {
23515 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23516 })
23517 })
23518 }
23519
23520 fn apply_additional_edits_for_completion(
23521 &self,
23522 buffer: Entity<Buffer>,
23523 completions: Rc<RefCell<Box<[Completion]>>>,
23524 completion_index: usize,
23525 push_to_history: bool,
23526 cx: &mut Context<Editor>,
23527 ) -> Task<Result<Option<language::Transaction>>> {
23528 self.update(cx, |project, cx| {
23529 project.lsp_store().update(cx, |lsp_store, cx| {
23530 lsp_store.apply_additional_edits_for_completion(
23531 buffer,
23532 completions,
23533 completion_index,
23534 push_to_history,
23535 cx,
23536 )
23537 })
23538 })
23539 }
23540
23541 fn is_completion_trigger(
23542 &self,
23543 buffer: &Entity<Buffer>,
23544 position: language::Anchor,
23545 text: &str,
23546 trigger_in_words: bool,
23547 menu_is_open: bool,
23548 cx: &mut Context<Editor>,
23549 ) -> bool {
23550 let mut chars = text.chars();
23551 let char = if let Some(char) = chars.next() {
23552 char
23553 } else {
23554 return false;
23555 };
23556 if chars.next().is_some() {
23557 return false;
23558 }
23559
23560 let buffer = buffer.read(cx);
23561 let snapshot = buffer.snapshot();
23562 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23563 return false;
23564 }
23565 let classifier = snapshot
23566 .char_classifier_at(position)
23567 .scope_context(Some(CharScopeContext::Completion));
23568 if trigger_in_words && classifier.is_word(char) {
23569 return true;
23570 }
23571
23572 buffer.completion_triggers().contains(text)
23573 }
23574
23575 fn show_snippets(&self) -> bool {
23576 true
23577 }
23578}
23579
23580impl SemanticsProvider for Entity<Project> {
23581 fn hover(
23582 &self,
23583 buffer: &Entity<Buffer>,
23584 position: text::Anchor,
23585 cx: &mut App,
23586 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23587 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23588 }
23589
23590 fn document_highlights(
23591 &self,
23592 buffer: &Entity<Buffer>,
23593 position: text::Anchor,
23594 cx: &mut App,
23595 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23596 Some(self.update(cx, |project, cx| {
23597 project.document_highlights(buffer, position, cx)
23598 }))
23599 }
23600
23601 fn definitions(
23602 &self,
23603 buffer: &Entity<Buffer>,
23604 position: text::Anchor,
23605 kind: GotoDefinitionKind,
23606 cx: &mut App,
23607 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23608 Some(self.update(cx, |project, cx| match kind {
23609 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23610 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23611 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23612 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23613 }))
23614 }
23615
23616 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23617 self.update(cx, |project, cx| {
23618 if project
23619 .active_debug_session(cx)
23620 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23621 {
23622 return true;
23623 }
23624
23625 buffer.update(cx, |buffer, cx| {
23626 project.any_language_server_supports_inlay_hints(buffer, cx)
23627 })
23628 })
23629 }
23630
23631 fn inline_values(
23632 &self,
23633 buffer_handle: Entity<Buffer>,
23634 range: Range<text::Anchor>,
23635 cx: &mut App,
23636 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23637 self.update(cx, |project, cx| {
23638 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23639
23640 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23641 })
23642 }
23643
23644 fn applicable_inlay_chunks(
23645 &self,
23646 buffer: &Entity<Buffer>,
23647 ranges: &[Range<text::Anchor>],
23648 cx: &mut App,
23649 ) -> Vec<Range<BufferRow>> {
23650 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23651 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23652 })
23653 }
23654
23655 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23656 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23657 lsp_store.invalidate_inlay_hints(for_buffers)
23658 });
23659 }
23660
23661 fn inlay_hints(
23662 &self,
23663 invalidate: InvalidationStrategy,
23664 buffer: Entity<Buffer>,
23665 ranges: Vec<Range<text::Anchor>>,
23666 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23667 cx: &mut App,
23668 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23669 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23670 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23671 }))
23672 }
23673
23674 fn range_for_rename(
23675 &self,
23676 buffer: &Entity<Buffer>,
23677 position: text::Anchor,
23678 cx: &mut App,
23679 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23680 Some(self.update(cx, |project, cx| {
23681 let buffer = buffer.clone();
23682 let task = project.prepare_rename(buffer.clone(), position, cx);
23683 cx.spawn(async move |_, cx| {
23684 Ok(match task.await? {
23685 PrepareRenameResponse::Success(range) => Some(range),
23686 PrepareRenameResponse::InvalidPosition => None,
23687 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23688 // Fallback on using TreeSitter info to determine identifier range
23689 buffer.read_with(cx, |buffer, _| {
23690 let snapshot = buffer.snapshot();
23691 let (range, kind) = snapshot.surrounding_word(position, None);
23692 if kind != Some(CharKind::Word) {
23693 return None;
23694 }
23695 Some(
23696 snapshot.anchor_before(range.start)
23697 ..snapshot.anchor_after(range.end),
23698 )
23699 })?
23700 }
23701 })
23702 })
23703 }))
23704 }
23705
23706 fn perform_rename(
23707 &self,
23708 buffer: &Entity<Buffer>,
23709 position: text::Anchor,
23710 new_name: String,
23711 cx: &mut App,
23712 ) -> Option<Task<Result<ProjectTransaction>>> {
23713 Some(self.update(cx, |project, cx| {
23714 project.perform_rename(buffer.clone(), position, new_name, cx)
23715 }))
23716 }
23717}
23718
23719fn consume_contiguous_rows(
23720 contiguous_row_selections: &mut Vec<Selection<Point>>,
23721 selection: &Selection<Point>,
23722 display_map: &DisplaySnapshot,
23723 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23724) -> (MultiBufferRow, MultiBufferRow) {
23725 contiguous_row_selections.push(selection.clone());
23726 let start_row = starting_row(selection, display_map);
23727 let mut end_row = ending_row(selection, display_map);
23728
23729 while let Some(next_selection) = selections.peek() {
23730 if next_selection.start.row <= end_row.0 {
23731 end_row = ending_row(next_selection, display_map);
23732 contiguous_row_selections.push(selections.next().unwrap().clone());
23733 } else {
23734 break;
23735 }
23736 }
23737 (start_row, end_row)
23738}
23739
23740fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23741 if selection.start.column > 0 {
23742 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23743 } else {
23744 MultiBufferRow(selection.start.row)
23745 }
23746}
23747
23748fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23749 if next_selection.end.column > 0 || next_selection.is_empty() {
23750 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23751 } else {
23752 MultiBufferRow(next_selection.end.row)
23753 }
23754}
23755
23756impl EditorSnapshot {
23757 pub fn remote_selections_in_range<'a>(
23758 &'a self,
23759 range: &'a Range<Anchor>,
23760 collaboration_hub: &dyn CollaborationHub,
23761 cx: &'a App,
23762 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23763 let participant_names = collaboration_hub.user_names(cx);
23764 let participant_indices = collaboration_hub.user_participant_indices(cx);
23765 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23766 let collaborators_by_replica_id = collaborators_by_peer_id
23767 .values()
23768 .map(|collaborator| (collaborator.replica_id, collaborator))
23769 .collect::<HashMap<_, _>>();
23770 self.buffer_snapshot()
23771 .selections_in_range(range, false)
23772 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23773 if replica_id == ReplicaId::AGENT {
23774 Some(RemoteSelection {
23775 replica_id,
23776 selection,
23777 cursor_shape,
23778 line_mode,
23779 collaborator_id: CollaboratorId::Agent,
23780 user_name: Some("Agent".into()),
23781 color: cx.theme().players().agent(),
23782 })
23783 } else {
23784 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23785 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23786 let user_name = participant_names.get(&collaborator.user_id).cloned();
23787 Some(RemoteSelection {
23788 replica_id,
23789 selection,
23790 cursor_shape,
23791 line_mode,
23792 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23793 user_name,
23794 color: if let Some(index) = participant_index {
23795 cx.theme().players().color_for_participant(index.0)
23796 } else {
23797 cx.theme().players().absent()
23798 },
23799 })
23800 }
23801 })
23802 }
23803
23804 pub fn hunks_for_ranges(
23805 &self,
23806 ranges: impl IntoIterator<Item = Range<Point>>,
23807 ) -> Vec<MultiBufferDiffHunk> {
23808 let mut hunks = Vec::new();
23809 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23810 HashMap::default();
23811 for query_range in ranges {
23812 let query_rows =
23813 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23814 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23815 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23816 ) {
23817 // Include deleted hunks that are adjacent to the query range, because
23818 // otherwise they would be missed.
23819 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23820 if hunk.status().is_deleted() {
23821 intersects_range |= hunk.row_range.start == query_rows.end;
23822 intersects_range |= hunk.row_range.end == query_rows.start;
23823 }
23824 if intersects_range {
23825 if !processed_buffer_rows
23826 .entry(hunk.buffer_id)
23827 .or_default()
23828 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23829 {
23830 continue;
23831 }
23832 hunks.push(hunk);
23833 }
23834 }
23835 }
23836
23837 hunks
23838 }
23839
23840 fn display_diff_hunks_for_rows<'a>(
23841 &'a self,
23842 display_rows: Range<DisplayRow>,
23843 folded_buffers: &'a HashSet<BufferId>,
23844 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23845 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23846 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23847
23848 self.buffer_snapshot()
23849 .diff_hunks_in_range(buffer_start..buffer_end)
23850 .filter_map(|hunk| {
23851 if folded_buffers.contains(&hunk.buffer_id) {
23852 return None;
23853 }
23854
23855 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23856 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23857
23858 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23859 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23860
23861 let display_hunk = if hunk_display_start.column() != 0 {
23862 DisplayDiffHunk::Folded {
23863 display_row: hunk_display_start.row(),
23864 }
23865 } else {
23866 let mut end_row = hunk_display_end.row();
23867 if hunk_display_end.column() > 0 {
23868 end_row.0 += 1;
23869 }
23870 let is_created_file = hunk.is_created_file();
23871 DisplayDiffHunk::Unfolded {
23872 status: hunk.status(),
23873 diff_base_byte_range: hunk.diff_base_byte_range,
23874 display_row_range: hunk_display_start.row()..end_row,
23875 multi_buffer_range: Anchor::range_in_buffer(
23876 hunk.excerpt_id,
23877 hunk.buffer_id,
23878 hunk.buffer_range,
23879 ),
23880 is_created_file,
23881 }
23882 };
23883
23884 Some(display_hunk)
23885 })
23886 }
23887
23888 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23889 self.display_snapshot
23890 .buffer_snapshot()
23891 .language_at(position)
23892 }
23893
23894 pub fn is_focused(&self) -> bool {
23895 self.is_focused
23896 }
23897
23898 pub fn placeholder_text(&self) -> Option<String> {
23899 self.placeholder_display_snapshot
23900 .as_ref()
23901 .map(|display_map| display_map.text())
23902 }
23903
23904 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23905 self.scroll_anchor.scroll_position(&self.display_snapshot)
23906 }
23907
23908 fn gutter_dimensions(
23909 &self,
23910 font_id: FontId,
23911 font_size: Pixels,
23912 max_line_number_width: Pixels,
23913 cx: &App,
23914 ) -> Option<GutterDimensions> {
23915 if !self.show_gutter {
23916 return None;
23917 }
23918
23919 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23920 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23921
23922 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23923 matches!(
23924 ProjectSettings::get_global(cx).git.git_gutter,
23925 GitGutterSetting::TrackedFiles
23926 )
23927 });
23928 let gutter_settings = EditorSettings::get_global(cx).gutter;
23929 let show_line_numbers = self
23930 .show_line_numbers
23931 .unwrap_or(gutter_settings.line_numbers);
23932 let line_gutter_width = if show_line_numbers {
23933 // Avoid flicker-like gutter resizes when the line number gains another digit by
23934 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23935 let min_width_for_number_on_gutter =
23936 ch_advance * gutter_settings.min_line_number_digits as f32;
23937 max_line_number_width.max(min_width_for_number_on_gutter)
23938 } else {
23939 0.0.into()
23940 };
23941
23942 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23943 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23944
23945 let git_blame_entries_width =
23946 self.git_blame_gutter_max_author_length
23947 .map(|max_author_length| {
23948 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23949 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23950
23951 /// The number of characters to dedicate to gaps and margins.
23952 const SPACING_WIDTH: usize = 4;
23953
23954 let max_char_count = max_author_length.min(renderer.max_author_length())
23955 + ::git::SHORT_SHA_LENGTH
23956 + MAX_RELATIVE_TIMESTAMP.len()
23957 + SPACING_WIDTH;
23958
23959 ch_advance * max_char_count
23960 });
23961
23962 let is_singleton = self.buffer_snapshot().is_singleton();
23963
23964 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23965 left_padding += if !is_singleton {
23966 ch_width * 4.0
23967 } else if show_runnables || show_breakpoints {
23968 ch_width * 3.0
23969 } else if show_git_gutter && show_line_numbers {
23970 ch_width * 2.0
23971 } else if show_git_gutter || show_line_numbers {
23972 ch_width
23973 } else {
23974 px(0.)
23975 };
23976
23977 let shows_folds = is_singleton && gutter_settings.folds;
23978
23979 let right_padding = if shows_folds && show_line_numbers {
23980 ch_width * 4.0
23981 } else if shows_folds || (!is_singleton && show_line_numbers) {
23982 ch_width * 3.0
23983 } else if show_line_numbers {
23984 ch_width
23985 } else {
23986 px(0.)
23987 };
23988
23989 Some(GutterDimensions {
23990 left_padding,
23991 right_padding,
23992 width: line_gutter_width + left_padding + right_padding,
23993 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23994 git_blame_entries_width,
23995 })
23996 }
23997
23998 pub fn render_crease_toggle(
23999 &self,
24000 buffer_row: MultiBufferRow,
24001 row_contains_cursor: bool,
24002 editor: Entity<Editor>,
24003 window: &mut Window,
24004 cx: &mut App,
24005 ) -> Option<AnyElement> {
24006 let folded = self.is_line_folded(buffer_row);
24007 let mut is_foldable = false;
24008
24009 if let Some(crease) = self
24010 .crease_snapshot
24011 .query_row(buffer_row, self.buffer_snapshot())
24012 {
24013 is_foldable = true;
24014 match crease {
24015 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24016 if let Some(render_toggle) = render_toggle {
24017 let toggle_callback =
24018 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24019 if folded {
24020 editor.update(cx, |editor, cx| {
24021 editor.fold_at(buffer_row, window, cx)
24022 });
24023 } else {
24024 editor.update(cx, |editor, cx| {
24025 editor.unfold_at(buffer_row, window, cx)
24026 });
24027 }
24028 });
24029 return Some((render_toggle)(
24030 buffer_row,
24031 folded,
24032 toggle_callback,
24033 window,
24034 cx,
24035 ));
24036 }
24037 }
24038 }
24039 }
24040
24041 is_foldable |= self.starts_indent(buffer_row);
24042
24043 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24044 Some(
24045 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24046 .toggle_state(folded)
24047 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24048 if folded {
24049 this.unfold_at(buffer_row, window, cx);
24050 } else {
24051 this.fold_at(buffer_row, window, cx);
24052 }
24053 }))
24054 .into_any_element(),
24055 )
24056 } else {
24057 None
24058 }
24059 }
24060
24061 pub fn render_crease_trailer(
24062 &self,
24063 buffer_row: MultiBufferRow,
24064 window: &mut Window,
24065 cx: &mut App,
24066 ) -> Option<AnyElement> {
24067 let folded = self.is_line_folded(buffer_row);
24068 if let Crease::Inline { render_trailer, .. } = self
24069 .crease_snapshot
24070 .query_row(buffer_row, self.buffer_snapshot())?
24071 {
24072 let render_trailer = render_trailer.as_ref()?;
24073 Some(render_trailer(buffer_row, folded, window, cx))
24074 } else {
24075 None
24076 }
24077 }
24078}
24079
24080impl Deref for EditorSnapshot {
24081 type Target = DisplaySnapshot;
24082
24083 fn deref(&self) -> &Self::Target {
24084 &self.display_snapshot
24085 }
24086}
24087
24088#[derive(Clone, Debug, PartialEq, Eq)]
24089pub enum EditorEvent {
24090 InputIgnored {
24091 text: Arc<str>,
24092 },
24093 InputHandled {
24094 utf16_range_to_replace: Option<Range<isize>>,
24095 text: Arc<str>,
24096 },
24097 ExcerptsAdded {
24098 buffer: Entity<Buffer>,
24099 predecessor: ExcerptId,
24100 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
24101 },
24102 ExcerptsRemoved {
24103 ids: Vec<ExcerptId>,
24104 removed_buffer_ids: Vec<BufferId>,
24105 },
24106 BufferFoldToggled {
24107 ids: Vec<ExcerptId>,
24108 folded: bool,
24109 },
24110 ExcerptsEdited {
24111 ids: Vec<ExcerptId>,
24112 },
24113 ExcerptsExpanded {
24114 ids: Vec<ExcerptId>,
24115 },
24116 BufferEdited,
24117 Edited {
24118 transaction_id: clock::Lamport,
24119 },
24120 Reparsed(BufferId),
24121 Focused,
24122 FocusedIn,
24123 Blurred,
24124 DirtyChanged,
24125 Saved,
24126 TitleChanged,
24127 SelectionsChanged {
24128 local: bool,
24129 },
24130 ScrollPositionChanged {
24131 local: bool,
24132 autoscroll: bool,
24133 },
24134 TransactionUndone {
24135 transaction_id: clock::Lamport,
24136 },
24137 TransactionBegun {
24138 transaction_id: clock::Lamport,
24139 },
24140 CursorShapeChanged,
24141 BreadcrumbsChanged,
24142 PushedToNavHistory {
24143 anchor: Anchor,
24144 is_deactivate: bool,
24145 },
24146}
24147
24148impl EventEmitter<EditorEvent> for Editor {}
24149
24150impl Focusable for Editor {
24151 fn focus_handle(&self, _cx: &App) -> FocusHandle {
24152 self.focus_handle.clone()
24153 }
24154}
24155
24156impl Render for Editor {
24157 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24158 let settings = ThemeSettings::get_global(cx);
24159
24160 let mut text_style = match self.mode {
24161 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24162 color: cx.theme().colors().editor_foreground,
24163 font_family: settings.ui_font.family.clone(),
24164 font_features: settings.ui_font.features.clone(),
24165 font_fallbacks: settings.ui_font.fallbacks.clone(),
24166 font_size: rems(0.875).into(),
24167 font_weight: settings.ui_font.weight,
24168 line_height: relative(settings.buffer_line_height.value()),
24169 ..Default::default()
24170 },
24171 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24172 color: cx.theme().colors().editor_foreground,
24173 font_family: settings.buffer_font.family.clone(),
24174 font_features: settings.buffer_font.features.clone(),
24175 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24176 font_size: settings.buffer_font_size(cx).into(),
24177 font_weight: settings.buffer_font.weight,
24178 line_height: relative(settings.buffer_line_height.value()),
24179 ..Default::default()
24180 },
24181 };
24182 if let Some(text_style_refinement) = &self.text_style_refinement {
24183 text_style.refine(text_style_refinement)
24184 }
24185
24186 let background = match self.mode {
24187 EditorMode::SingleLine => cx.theme().system().transparent,
24188 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24189 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24190 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24191 };
24192
24193 EditorElement::new(
24194 &cx.entity(),
24195 EditorStyle {
24196 background,
24197 border: cx.theme().colors().border,
24198 local_player: cx.theme().players().local(),
24199 text: text_style,
24200 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24201 syntax: cx.theme().syntax().clone(),
24202 status: cx.theme().status().clone(),
24203 inlay_hints_style: make_inlay_hints_style(cx),
24204 edit_prediction_styles: make_suggestion_styles(cx),
24205 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
24206 show_underlines: self.diagnostics_enabled(),
24207 },
24208 )
24209 }
24210}
24211
24212impl EntityInputHandler for Editor {
24213 fn text_for_range(
24214 &mut self,
24215 range_utf16: Range<usize>,
24216 adjusted_range: &mut Option<Range<usize>>,
24217 _: &mut Window,
24218 cx: &mut Context<Self>,
24219 ) -> Option<String> {
24220 let snapshot = self.buffer.read(cx).read(cx);
24221 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
24222 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
24223 if (start.0..end.0) != range_utf16 {
24224 adjusted_range.replace(start.0..end.0);
24225 }
24226 Some(snapshot.text_for_range(start..end).collect())
24227 }
24228
24229 fn selected_text_range(
24230 &mut self,
24231 ignore_disabled_input: bool,
24232 _: &mut Window,
24233 cx: &mut Context<Self>,
24234 ) -> Option<UTF16Selection> {
24235 // Prevent the IME menu from appearing when holding down an alphabetic key
24236 // while input is disabled.
24237 if !ignore_disabled_input && !self.input_enabled {
24238 return None;
24239 }
24240
24241 let selection = self
24242 .selections
24243 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
24244 let range = selection.range();
24245
24246 Some(UTF16Selection {
24247 range: range.start.0..range.end.0,
24248 reversed: selection.reversed,
24249 })
24250 }
24251
24252 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24253 let snapshot = self.buffer.read(cx).read(cx);
24254 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24255 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
24256 }
24257
24258 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24259 self.clear_highlights::<InputComposition>(cx);
24260 self.ime_transaction.take();
24261 }
24262
24263 fn replace_text_in_range(
24264 &mut self,
24265 range_utf16: Option<Range<usize>>,
24266 text: &str,
24267 window: &mut Window,
24268 cx: &mut Context<Self>,
24269 ) {
24270 if !self.input_enabled {
24271 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24272 return;
24273 }
24274
24275 self.transact(window, cx, |this, window, cx| {
24276 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24277 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24278 Some(this.selection_replacement_ranges(range_utf16, cx))
24279 } else {
24280 this.marked_text_ranges(cx)
24281 };
24282
24283 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24284 let newest_selection_id = this.selections.newest_anchor().id;
24285 this.selections
24286 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24287 .iter()
24288 .zip(ranges_to_replace.iter())
24289 .find_map(|(selection, range)| {
24290 if selection.id == newest_selection_id {
24291 Some(
24292 (range.start.0 as isize - selection.head().0 as isize)
24293 ..(range.end.0 as isize - selection.head().0 as isize),
24294 )
24295 } else {
24296 None
24297 }
24298 })
24299 });
24300
24301 cx.emit(EditorEvent::InputHandled {
24302 utf16_range_to_replace: range_to_replace,
24303 text: text.into(),
24304 });
24305
24306 if let Some(new_selected_ranges) = new_selected_ranges {
24307 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24308 selections.select_ranges(new_selected_ranges)
24309 });
24310 this.backspace(&Default::default(), window, cx);
24311 }
24312
24313 this.handle_input(text, window, cx);
24314 });
24315
24316 if let Some(transaction) = self.ime_transaction {
24317 self.buffer.update(cx, |buffer, cx| {
24318 buffer.group_until_transaction(transaction, cx);
24319 });
24320 }
24321
24322 self.unmark_text(window, cx);
24323 }
24324
24325 fn replace_and_mark_text_in_range(
24326 &mut self,
24327 range_utf16: Option<Range<usize>>,
24328 text: &str,
24329 new_selected_range_utf16: Option<Range<usize>>,
24330 window: &mut Window,
24331 cx: &mut Context<Self>,
24332 ) {
24333 if !self.input_enabled {
24334 return;
24335 }
24336
24337 let transaction = self.transact(window, cx, |this, window, cx| {
24338 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24339 let snapshot = this.buffer.read(cx).read(cx);
24340 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24341 for marked_range in &mut marked_ranges {
24342 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24343 marked_range.start.0 += relative_range_utf16.start;
24344 marked_range.start =
24345 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24346 marked_range.end =
24347 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24348 }
24349 }
24350 Some(marked_ranges)
24351 } else if let Some(range_utf16) = range_utf16 {
24352 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24353 Some(this.selection_replacement_ranges(range_utf16, cx))
24354 } else {
24355 None
24356 };
24357
24358 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24359 let newest_selection_id = this.selections.newest_anchor().id;
24360 this.selections
24361 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24362 .iter()
24363 .zip(ranges_to_replace.iter())
24364 .find_map(|(selection, range)| {
24365 if selection.id == newest_selection_id {
24366 Some(
24367 (range.start.0 as isize - selection.head().0 as isize)
24368 ..(range.end.0 as isize - selection.head().0 as isize),
24369 )
24370 } else {
24371 None
24372 }
24373 })
24374 });
24375
24376 cx.emit(EditorEvent::InputHandled {
24377 utf16_range_to_replace: range_to_replace,
24378 text: text.into(),
24379 });
24380
24381 if let Some(ranges) = ranges_to_replace {
24382 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24383 s.select_ranges(ranges)
24384 });
24385 }
24386
24387 let marked_ranges = {
24388 let snapshot = this.buffer.read(cx).read(cx);
24389 this.selections
24390 .disjoint_anchors_arc()
24391 .iter()
24392 .map(|selection| {
24393 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24394 })
24395 .collect::<Vec<_>>()
24396 };
24397
24398 if text.is_empty() {
24399 this.unmark_text(window, cx);
24400 } else {
24401 this.highlight_text::<InputComposition>(
24402 marked_ranges.clone(),
24403 HighlightStyle {
24404 underline: Some(UnderlineStyle {
24405 thickness: px(1.),
24406 color: None,
24407 wavy: false,
24408 }),
24409 ..Default::default()
24410 },
24411 cx,
24412 );
24413 }
24414
24415 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24416 let use_autoclose = this.use_autoclose;
24417 let use_auto_surround = this.use_auto_surround;
24418 this.set_use_autoclose(false);
24419 this.set_use_auto_surround(false);
24420 this.handle_input(text, window, cx);
24421 this.set_use_autoclose(use_autoclose);
24422 this.set_use_auto_surround(use_auto_surround);
24423
24424 if let Some(new_selected_range) = new_selected_range_utf16 {
24425 let snapshot = this.buffer.read(cx).read(cx);
24426 let new_selected_ranges = marked_ranges
24427 .into_iter()
24428 .map(|marked_range| {
24429 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24430 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24431 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24432 snapshot.clip_offset_utf16(new_start, Bias::Left)
24433 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24434 })
24435 .collect::<Vec<_>>();
24436
24437 drop(snapshot);
24438 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24439 selections.select_ranges(new_selected_ranges)
24440 });
24441 }
24442 });
24443
24444 self.ime_transaction = self.ime_transaction.or(transaction);
24445 if let Some(transaction) = self.ime_transaction {
24446 self.buffer.update(cx, |buffer, cx| {
24447 buffer.group_until_transaction(transaction, cx);
24448 });
24449 }
24450
24451 if self.text_highlights::<InputComposition>(cx).is_none() {
24452 self.ime_transaction.take();
24453 }
24454 }
24455
24456 fn bounds_for_range(
24457 &mut self,
24458 range_utf16: Range<usize>,
24459 element_bounds: gpui::Bounds<Pixels>,
24460 window: &mut Window,
24461 cx: &mut Context<Self>,
24462 ) -> Option<gpui::Bounds<Pixels>> {
24463 let text_layout_details = self.text_layout_details(window);
24464 let CharacterDimensions {
24465 em_width,
24466 em_advance,
24467 line_height,
24468 } = self.character_dimensions(window);
24469
24470 let snapshot = self.snapshot(window, cx);
24471 let scroll_position = snapshot.scroll_position();
24472 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24473
24474 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24475 let x = Pixels::from(
24476 ScrollOffset::from(
24477 snapshot.x_for_display_point(start, &text_layout_details)
24478 + self.gutter_dimensions.full_width(),
24479 ) - scroll_left,
24480 );
24481 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24482
24483 Some(Bounds {
24484 origin: element_bounds.origin + point(x, y),
24485 size: size(em_width, line_height),
24486 })
24487 }
24488
24489 fn character_index_for_point(
24490 &mut self,
24491 point: gpui::Point<Pixels>,
24492 _window: &mut Window,
24493 _cx: &mut Context<Self>,
24494 ) -> Option<usize> {
24495 let position_map = self.last_position_map.as_ref()?;
24496 if !position_map.text_hitbox.contains(&point) {
24497 return None;
24498 }
24499 let display_point = position_map.point_for_position(point).previous_valid;
24500 let anchor = position_map
24501 .snapshot
24502 .display_point_to_anchor(display_point, Bias::Left);
24503 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24504 Some(utf16_offset.0)
24505 }
24506
24507 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24508 self.input_enabled
24509 }
24510}
24511
24512trait SelectionExt {
24513 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24514 fn spanned_rows(
24515 &self,
24516 include_end_if_at_line_start: bool,
24517 map: &DisplaySnapshot,
24518 ) -> Range<MultiBufferRow>;
24519}
24520
24521impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24522 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24523 let start = self
24524 .start
24525 .to_point(map.buffer_snapshot())
24526 .to_display_point(map);
24527 let end = self
24528 .end
24529 .to_point(map.buffer_snapshot())
24530 .to_display_point(map);
24531 if self.reversed {
24532 end..start
24533 } else {
24534 start..end
24535 }
24536 }
24537
24538 fn spanned_rows(
24539 &self,
24540 include_end_if_at_line_start: bool,
24541 map: &DisplaySnapshot,
24542 ) -> Range<MultiBufferRow> {
24543 let start = self.start.to_point(map.buffer_snapshot());
24544 let mut end = self.end.to_point(map.buffer_snapshot());
24545 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24546 end.row -= 1;
24547 }
24548
24549 let buffer_start = map.prev_line_boundary(start).0;
24550 let buffer_end = map.next_line_boundary(end).0;
24551 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24552 }
24553}
24554
24555impl<T: InvalidationRegion> InvalidationStack<T> {
24556 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24557 where
24558 S: Clone + ToOffset,
24559 {
24560 while let Some(region) = self.last() {
24561 let all_selections_inside_invalidation_ranges =
24562 if selections.len() == region.ranges().len() {
24563 selections
24564 .iter()
24565 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24566 .all(|(selection, invalidation_range)| {
24567 let head = selection.head().to_offset(buffer);
24568 invalidation_range.start <= head && invalidation_range.end >= head
24569 })
24570 } else {
24571 false
24572 };
24573
24574 if all_selections_inside_invalidation_ranges {
24575 break;
24576 } else {
24577 self.pop();
24578 }
24579 }
24580 }
24581}
24582
24583impl<T> Default for InvalidationStack<T> {
24584 fn default() -> Self {
24585 Self(Default::default())
24586 }
24587}
24588
24589impl<T> Deref for InvalidationStack<T> {
24590 type Target = Vec<T>;
24591
24592 fn deref(&self) -> &Self::Target {
24593 &self.0
24594 }
24595}
24596
24597impl<T> DerefMut for InvalidationStack<T> {
24598 fn deref_mut(&mut self) -> &mut Self::Target {
24599 &mut self.0
24600 }
24601}
24602
24603impl InvalidationRegion for SnippetState {
24604 fn ranges(&self) -> &[Range<Anchor>] {
24605 &self.ranges[self.active_index]
24606 }
24607}
24608
24609fn edit_prediction_edit_text(
24610 current_snapshot: &BufferSnapshot,
24611 edits: &[(Range<Anchor>, impl AsRef<str>)],
24612 edit_preview: &EditPreview,
24613 include_deletions: bool,
24614 cx: &App,
24615) -> HighlightedText {
24616 let edits = edits
24617 .iter()
24618 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24619 .collect::<Vec<_>>();
24620
24621 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24622}
24623
24624fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24625 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24626 // Just show the raw edit text with basic styling
24627 let mut text = String::new();
24628 let mut highlights = Vec::new();
24629
24630 let insertion_highlight_style = HighlightStyle {
24631 color: Some(cx.theme().colors().text),
24632 ..Default::default()
24633 };
24634
24635 for (_, edit_text) in edits {
24636 let start_offset = text.len();
24637 text.push_str(edit_text);
24638 let end_offset = text.len();
24639
24640 if start_offset < end_offset {
24641 highlights.push((start_offset..end_offset, insertion_highlight_style));
24642 }
24643 }
24644
24645 HighlightedText {
24646 text: text.into(),
24647 highlights,
24648 }
24649}
24650
24651pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24652 match severity {
24653 lsp::DiagnosticSeverity::ERROR => colors.error,
24654 lsp::DiagnosticSeverity::WARNING => colors.warning,
24655 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24656 lsp::DiagnosticSeverity::HINT => colors.info,
24657 _ => colors.ignored,
24658 }
24659}
24660
24661pub fn styled_runs_for_code_label<'a>(
24662 label: &'a CodeLabel,
24663 syntax_theme: &'a theme::SyntaxTheme,
24664) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24665 let fade_out = HighlightStyle {
24666 fade_out: Some(0.35),
24667 ..Default::default()
24668 };
24669
24670 let mut prev_end = label.filter_range.end;
24671 label
24672 .runs
24673 .iter()
24674 .enumerate()
24675 .flat_map(move |(ix, (range, highlight_id))| {
24676 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24677 style
24678 } else {
24679 return Default::default();
24680 };
24681 let muted_style = style.highlight(fade_out);
24682
24683 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24684 if range.start >= label.filter_range.end {
24685 if range.start > prev_end {
24686 runs.push((prev_end..range.start, fade_out));
24687 }
24688 runs.push((range.clone(), muted_style));
24689 } else if range.end <= label.filter_range.end {
24690 runs.push((range.clone(), style));
24691 } else {
24692 runs.push((range.start..label.filter_range.end, style));
24693 runs.push((label.filter_range.end..range.end, muted_style));
24694 }
24695 prev_end = cmp::max(prev_end, range.end);
24696
24697 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24698 runs.push((prev_end..label.text.len(), fade_out));
24699 }
24700
24701 runs
24702 })
24703}
24704
24705pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24706 let mut prev_index = 0;
24707 let mut prev_codepoint: Option<char> = None;
24708 text.char_indices()
24709 .chain([(text.len(), '\0')])
24710 .filter_map(move |(index, codepoint)| {
24711 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24712 let is_boundary = index == text.len()
24713 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24714 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24715 if is_boundary {
24716 let chunk = &text[prev_index..index];
24717 prev_index = index;
24718 Some(chunk)
24719 } else {
24720 None
24721 }
24722 })
24723}
24724
24725/// Given a string of text immediately before the cursor, iterates over possible
24726/// strings a snippet could match to. More precisely: returns an iterator over
24727/// suffixes of `text` created by splitting at word boundaries (before & after
24728/// every non-word character).
24729///
24730/// Shorter suffixes are returned first.
24731pub(crate) fn snippet_candidate_suffixes(
24732 text: &str,
24733 is_word_char: impl Fn(char) -> bool,
24734) -> impl std::iter::Iterator<Item = &str> {
24735 let mut prev_index = text.len();
24736 let mut prev_codepoint = None;
24737 text.char_indices()
24738 .rev()
24739 .chain([(0, '\0')])
24740 .filter_map(move |(index, codepoint)| {
24741 let prev_index = std::mem::replace(&mut prev_index, index);
24742 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24743 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
24744 None
24745 } else {
24746 let chunk = &text[prev_index..]; // go to end of string
24747 Some(chunk)
24748 }
24749 })
24750}
24751
24752pub trait RangeToAnchorExt: Sized {
24753 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24754
24755 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24756 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24757 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24758 }
24759}
24760
24761impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24762 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24763 let start_offset = self.start.to_offset(snapshot);
24764 let end_offset = self.end.to_offset(snapshot);
24765 if start_offset == end_offset {
24766 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24767 } else {
24768 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24769 }
24770 }
24771}
24772
24773pub trait RowExt {
24774 fn as_f64(&self) -> f64;
24775
24776 fn next_row(&self) -> Self;
24777
24778 fn previous_row(&self) -> Self;
24779
24780 fn minus(&self, other: Self) -> u32;
24781}
24782
24783impl RowExt for DisplayRow {
24784 fn as_f64(&self) -> f64 {
24785 self.0 as _
24786 }
24787
24788 fn next_row(&self) -> Self {
24789 Self(self.0 + 1)
24790 }
24791
24792 fn previous_row(&self) -> Self {
24793 Self(self.0.saturating_sub(1))
24794 }
24795
24796 fn minus(&self, other: Self) -> u32 {
24797 self.0 - other.0
24798 }
24799}
24800
24801impl RowExt for MultiBufferRow {
24802 fn as_f64(&self) -> f64 {
24803 self.0 as _
24804 }
24805
24806 fn next_row(&self) -> Self {
24807 Self(self.0 + 1)
24808 }
24809
24810 fn previous_row(&self) -> Self {
24811 Self(self.0.saturating_sub(1))
24812 }
24813
24814 fn minus(&self, other: Self) -> u32 {
24815 self.0 - other.0
24816 }
24817}
24818
24819trait RowRangeExt {
24820 type Row;
24821
24822 fn len(&self) -> usize;
24823
24824 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24825}
24826
24827impl RowRangeExt for Range<MultiBufferRow> {
24828 type Row = MultiBufferRow;
24829
24830 fn len(&self) -> usize {
24831 (self.end.0 - self.start.0) as usize
24832 }
24833
24834 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24835 (self.start.0..self.end.0).map(MultiBufferRow)
24836 }
24837}
24838
24839impl RowRangeExt for Range<DisplayRow> {
24840 type Row = DisplayRow;
24841
24842 fn len(&self) -> usize {
24843 (self.end.0 - self.start.0) as usize
24844 }
24845
24846 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24847 (self.start.0..self.end.0).map(DisplayRow)
24848 }
24849}
24850
24851/// If select range has more than one line, we
24852/// just point the cursor to range.start.
24853fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24854 if range.start.row == range.end.row {
24855 range
24856 } else {
24857 range.start..range.start
24858 }
24859}
24860pub struct KillRing(ClipboardItem);
24861impl Global for KillRing {}
24862
24863const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24864
24865enum BreakpointPromptEditAction {
24866 Log,
24867 Condition,
24868 HitCondition,
24869}
24870
24871struct BreakpointPromptEditor {
24872 pub(crate) prompt: Entity<Editor>,
24873 editor: WeakEntity<Editor>,
24874 breakpoint_anchor: Anchor,
24875 breakpoint: Breakpoint,
24876 edit_action: BreakpointPromptEditAction,
24877 block_ids: HashSet<CustomBlockId>,
24878 editor_margins: Arc<Mutex<EditorMargins>>,
24879 _subscriptions: Vec<Subscription>,
24880}
24881
24882impl BreakpointPromptEditor {
24883 const MAX_LINES: u8 = 4;
24884
24885 fn new(
24886 editor: WeakEntity<Editor>,
24887 breakpoint_anchor: Anchor,
24888 breakpoint: Breakpoint,
24889 edit_action: BreakpointPromptEditAction,
24890 window: &mut Window,
24891 cx: &mut Context<Self>,
24892 ) -> Self {
24893 let base_text = match edit_action {
24894 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24895 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24896 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24897 }
24898 .map(|msg| msg.to_string())
24899 .unwrap_or_default();
24900
24901 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24902 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24903
24904 let prompt = cx.new(|cx| {
24905 let mut prompt = Editor::new(
24906 EditorMode::AutoHeight {
24907 min_lines: 1,
24908 max_lines: Some(Self::MAX_LINES as usize),
24909 },
24910 buffer,
24911 None,
24912 window,
24913 cx,
24914 );
24915 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24916 prompt.set_show_cursor_when_unfocused(false, cx);
24917 prompt.set_placeholder_text(
24918 match edit_action {
24919 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24920 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24921 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24922 },
24923 window,
24924 cx,
24925 );
24926
24927 prompt
24928 });
24929
24930 Self {
24931 prompt,
24932 editor,
24933 breakpoint_anchor,
24934 breakpoint,
24935 edit_action,
24936 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24937 block_ids: Default::default(),
24938 _subscriptions: vec![],
24939 }
24940 }
24941
24942 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24943 self.block_ids.extend(block_ids)
24944 }
24945
24946 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24947 if let Some(editor) = self.editor.upgrade() {
24948 let message = self
24949 .prompt
24950 .read(cx)
24951 .buffer
24952 .read(cx)
24953 .as_singleton()
24954 .expect("A multi buffer in breakpoint prompt isn't possible")
24955 .read(cx)
24956 .as_rope()
24957 .to_string();
24958
24959 editor.update(cx, |editor, cx| {
24960 editor.edit_breakpoint_at_anchor(
24961 self.breakpoint_anchor,
24962 self.breakpoint.clone(),
24963 match self.edit_action {
24964 BreakpointPromptEditAction::Log => {
24965 BreakpointEditAction::EditLogMessage(message.into())
24966 }
24967 BreakpointPromptEditAction::Condition => {
24968 BreakpointEditAction::EditCondition(message.into())
24969 }
24970 BreakpointPromptEditAction::HitCondition => {
24971 BreakpointEditAction::EditHitCondition(message.into())
24972 }
24973 },
24974 cx,
24975 );
24976
24977 editor.remove_blocks(self.block_ids.clone(), None, cx);
24978 cx.focus_self(window);
24979 });
24980 }
24981 }
24982
24983 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24984 self.editor
24985 .update(cx, |editor, cx| {
24986 editor.remove_blocks(self.block_ids.clone(), None, cx);
24987 window.focus(&editor.focus_handle);
24988 })
24989 .log_err();
24990 }
24991
24992 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24993 let settings = ThemeSettings::get_global(cx);
24994 let text_style = TextStyle {
24995 color: if self.prompt.read(cx).read_only(cx) {
24996 cx.theme().colors().text_disabled
24997 } else {
24998 cx.theme().colors().text
24999 },
25000 font_family: settings.buffer_font.family.clone(),
25001 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25002 font_size: settings.buffer_font_size(cx).into(),
25003 font_weight: settings.buffer_font.weight,
25004 line_height: relative(settings.buffer_line_height.value()),
25005 ..Default::default()
25006 };
25007 EditorElement::new(
25008 &self.prompt,
25009 EditorStyle {
25010 background: cx.theme().colors().editor_background,
25011 local_player: cx.theme().players().local(),
25012 text: text_style,
25013 ..Default::default()
25014 },
25015 )
25016 }
25017}
25018
25019impl Render for BreakpointPromptEditor {
25020 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25021 let editor_margins = *self.editor_margins.lock();
25022 let gutter_dimensions = editor_margins.gutter;
25023 h_flex()
25024 .key_context("Editor")
25025 .bg(cx.theme().colors().editor_background)
25026 .border_y_1()
25027 .border_color(cx.theme().status().info_border)
25028 .size_full()
25029 .py(window.line_height() / 2.5)
25030 .on_action(cx.listener(Self::confirm))
25031 .on_action(cx.listener(Self::cancel))
25032 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25033 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25034 }
25035}
25036
25037impl Focusable for BreakpointPromptEditor {
25038 fn focus_handle(&self, cx: &App) -> FocusHandle {
25039 self.prompt.focus_handle(cx)
25040 }
25041}
25042
25043fn all_edits_insertions_or_deletions(
25044 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25045 snapshot: &MultiBufferSnapshot,
25046) -> bool {
25047 let mut all_insertions = true;
25048 let mut all_deletions = true;
25049
25050 for (range, new_text) in edits.iter() {
25051 let range_is_empty = range.to_offset(snapshot).is_empty();
25052 let text_is_empty = new_text.is_empty();
25053
25054 if range_is_empty != text_is_empty {
25055 if range_is_empty {
25056 all_deletions = false;
25057 } else {
25058 all_insertions = false;
25059 }
25060 } else {
25061 return false;
25062 }
25063
25064 if !all_insertions && !all_deletions {
25065 return false;
25066 }
25067 }
25068 all_insertions || all_deletions
25069}
25070
25071struct MissingEditPredictionKeybindingTooltip;
25072
25073impl Render for MissingEditPredictionKeybindingTooltip {
25074 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25075 ui::tooltip_container(cx, |container, cx| {
25076 container
25077 .flex_shrink_0()
25078 .max_w_80()
25079 .min_h(rems_from_px(124.))
25080 .justify_between()
25081 .child(
25082 v_flex()
25083 .flex_1()
25084 .text_ui_sm(cx)
25085 .child(Label::new("Conflict with Accept Keybinding"))
25086 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25087 )
25088 .child(
25089 h_flex()
25090 .pb_1()
25091 .gap_1()
25092 .items_end()
25093 .w_full()
25094 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
25095 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
25096 }))
25097 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
25098 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
25099 })),
25100 )
25101 })
25102 }
25103}
25104
25105#[derive(Debug, Clone, Copy, PartialEq)]
25106pub struct LineHighlight {
25107 pub background: Background,
25108 pub border: Option<gpui::Hsla>,
25109 pub include_gutter: bool,
25110 pub type_id: Option<TypeId>,
25111}
25112
25113struct LineManipulationResult {
25114 pub new_text: String,
25115 pub line_count_before: usize,
25116 pub line_count_after: usize,
25117}
25118
25119fn render_diff_hunk_controls(
25120 row: u32,
25121 status: &DiffHunkStatus,
25122 hunk_range: Range<Anchor>,
25123 is_created_file: bool,
25124 line_height: Pixels,
25125 editor: &Entity<Editor>,
25126 _window: &mut Window,
25127 cx: &mut App,
25128) -> AnyElement {
25129 h_flex()
25130 .h(line_height)
25131 .mr_1()
25132 .gap_1()
25133 .px_0p5()
25134 .pb_1()
25135 .border_x_1()
25136 .border_b_1()
25137 .border_color(cx.theme().colors().border_variant)
25138 .rounded_b_lg()
25139 .bg(cx.theme().colors().editor_background)
25140 .gap_1()
25141 .block_mouse_except_scroll()
25142 .shadow_md()
25143 .child(if status.has_secondary_hunk() {
25144 Button::new(("stage", row as u64), "Stage")
25145 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25146 .tooltip({
25147 let focus_handle = editor.focus_handle(cx);
25148 move |_window, cx| {
25149 Tooltip::for_action_in(
25150 "Stage Hunk",
25151 &::git::ToggleStaged,
25152 &focus_handle,
25153 cx,
25154 )
25155 }
25156 })
25157 .on_click({
25158 let editor = editor.clone();
25159 move |_event, _window, cx| {
25160 editor.update(cx, |editor, cx| {
25161 editor.stage_or_unstage_diff_hunks(
25162 true,
25163 vec![hunk_range.start..hunk_range.start],
25164 cx,
25165 );
25166 });
25167 }
25168 })
25169 } else {
25170 Button::new(("unstage", row as u64), "Unstage")
25171 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25172 .tooltip({
25173 let focus_handle = editor.focus_handle(cx);
25174 move |_window, cx| {
25175 Tooltip::for_action_in(
25176 "Unstage Hunk",
25177 &::git::ToggleStaged,
25178 &focus_handle,
25179 cx,
25180 )
25181 }
25182 })
25183 .on_click({
25184 let editor = editor.clone();
25185 move |_event, _window, cx| {
25186 editor.update(cx, |editor, cx| {
25187 editor.stage_or_unstage_diff_hunks(
25188 false,
25189 vec![hunk_range.start..hunk_range.start],
25190 cx,
25191 );
25192 });
25193 }
25194 })
25195 })
25196 .child(
25197 Button::new(("restore", row as u64), "Restore")
25198 .tooltip({
25199 let focus_handle = editor.focus_handle(cx);
25200 move |_window, cx| {
25201 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
25202 }
25203 })
25204 .on_click({
25205 let editor = editor.clone();
25206 move |_event, window, cx| {
25207 editor.update(cx, |editor, cx| {
25208 let snapshot = editor.snapshot(window, cx);
25209 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
25210 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
25211 });
25212 }
25213 })
25214 .disabled(is_created_file),
25215 )
25216 .when(
25217 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
25218 |el| {
25219 el.child(
25220 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
25221 .shape(IconButtonShape::Square)
25222 .icon_size(IconSize::Small)
25223 // .disabled(!has_multiple_hunks)
25224 .tooltip({
25225 let focus_handle = editor.focus_handle(cx);
25226 move |_window, cx| {
25227 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
25228 }
25229 })
25230 .on_click({
25231 let editor = editor.clone();
25232 move |_event, window, cx| {
25233 editor.update(cx, |editor, cx| {
25234 let snapshot = editor.snapshot(window, cx);
25235 let position =
25236 hunk_range.end.to_point(&snapshot.buffer_snapshot());
25237 editor.go_to_hunk_before_or_after_position(
25238 &snapshot,
25239 position,
25240 Direction::Next,
25241 window,
25242 cx,
25243 );
25244 editor.expand_selected_diff_hunks(cx);
25245 });
25246 }
25247 }),
25248 )
25249 .child(
25250 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25251 .shape(IconButtonShape::Square)
25252 .icon_size(IconSize::Small)
25253 // .disabled(!has_multiple_hunks)
25254 .tooltip({
25255 let focus_handle = editor.focus_handle(cx);
25256 move |_window, cx| {
25257 Tooltip::for_action_in(
25258 "Previous Hunk",
25259 &GoToPreviousHunk,
25260 &focus_handle,
25261 cx,
25262 )
25263 }
25264 })
25265 .on_click({
25266 let editor = editor.clone();
25267 move |_event, window, cx| {
25268 editor.update(cx, |editor, cx| {
25269 let snapshot = editor.snapshot(window, cx);
25270 let point =
25271 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25272 editor.go_to_hunk_before_or_after_position(
25273 &snapshot,
25274 point,
25275 Direction::Prev,
25276 window,
25277 cx,
25278 );
25279 editor.expand_selected_diff_hunks(cx);
25280 });
25281 }
25282 }),
25283 )
25284 },
25285 )
25286 .into_any_element()
25287}
25288
25289pub fn multibuffer_context_lines(cx: &App) -> u32 {
25290 EditorSettings::try_get(cx)
25291 .map(|settings| settings.excerpt_context_lines)
25292 .unwrap_or(2)
25293 .min(32)
25294}