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 if self.has_any_expanded_diff_hunks(cx) {
2635 key_context.add("diffs_expanded");
2636 }
2637
2638 key_context
2639 }
2640
2641 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2642 self.last_bounds.as_ref()
2643 }
2644
2645 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2646 if self.mouse_cursor_hidden {
2647 self.mouse_cursor_hidden = false;
2648 cx.notify();
2649 }
2650 }
2651
2652 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2653 let hide_mouse_cursor = match origin {
2654 HideMouseCursorOrigin::TypingAction => {
2655 matches!(
2656 self.hide_mouse_mode,
2657 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2658 )
2659 }
2660 HideMouseCursorOrigin::MovementAction => {
2661 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2662 }
2663 };
2664 if self.mouse_cursor_hidden != hide_mouse_cursor {
2665 self.mouse_cursor_hidden = hide_mouse_cursor;
2666 cx.notify();
2667 }
2668 }
2669
2670 pub fn edit_prediction_in_conflict(&self) -> bool {
2671 if !self.show_edit_predictions_in_menu() {
2672 return false;
2673 }
2674
2675 let showing_completions = self
2676 .context_menu
2677 .borrow()
2678 .as_ref()
2679 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2680
2681 showing_completions
2682 || self.edit_prediction_requires_modifier()
2683 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2684 // bindings to insert tab characters.
2685 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2686 }
2687
2688 pub fn accept_edit_prediction_keybind(
2689 &self,
2690 accept_partial: bool,
2691 window: &mut Window,
2692 cx: &mut App,
2693 ) -> AcceptEditPredictionBinding {
2694 let key_context = self.key_context_internal(true, window, cx);
2695 let in_conflict = self.edit_prediction_in_conflict();
2696
2697 let bindings = if accept_partial {
2698 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2699 } else {
2700 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2701 };
2702
2703 // TODO: if the binding contains multiple keystrokes, display all of them, not
2704 // just the first one.
2705 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2706 !in_conflict
2707 || binding
2708 .keystrokes()
2709 .first()
2710 .is_some_and(|keystroke| keystroke.modifiers().modified())
2711 }))
2712 }
2713
2714 pub fn new_file(
2715 workspace: &mut Workspace,
2716 _: &workspace::NewFile,
2717 window: &mut Window,
2718 cx: &mut Context<Workspace>,
2719 ) {
2720 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2721 "Failed to create buffer",
2722 window,
2723 cx,
2724 |e, _, _| match e.error_code() {
2725 ErrorCode::RemoteUpgradeRequired => Some(format!(
2726 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2727 e.error_tag("required").unwrap_or("the latest version")
2728 )),
2729 _ => None,
2730 },
2731 );
2732 }
2733
2734 pub fn new_in_workspace(
2735 workspace: &mut Workspace,
2736 window: &mut Window,
2737 cx: &mut Context<Workspace>,
2738 ) -> Task<Result<Entity<Editor>>> {
2739 let project = workspace.project().clone();
2740 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2741
2742 cx.spawn_in(window, async move |workspace, cx| {
2743 let buffer = create.await?;
2744 workspace.update_in(cx, |workspace, window, cx| {
2745 let editor =
2746 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2747 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2748 editor
2749 })
2750 })
2751 }
2752
2753 fn new_file_vertical(
2754 workspace: &mut Workspace,
2755 _: &workspace::NewFileSplitVertical,
2756 window: &mut Window,
2757 cx: &mut Context<Workspace>,
2758 ) {
2759 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2760 }
2761
2762 fn new_file_horizontal(
2763 workspace: &mut Workspace,
2764 _: &workspace::NewFileSplitHorizontal,
2765 window: &mut Window,
2766 cx: &mut Context<Workspace>,
2767 ) {
2768 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2769 }
2770
2771 fn new_file_split(
2772 workspace: &mut Workspace,
2773 action: &workspace::NewFileSplit,
2774 window: &mut Window,
2775 cx: &mut Context<Workspace>,
2776 ) {
2777 Self::new_file_in_direction(workspace, action.0, window, cx)
2778 }
2779
2780 fn new_file_in_direction(
2781 workspace: &mut Workspace,
2782 direction: SplitDirection,
2783 window: &mut Window,
2784 cx: &mut Context<Workspace>,
2785 ) {
2786 let project = workspace.project().clone();
2787 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2788
2789 cx.spawn_in(window, async move |workspace, cx| {
2790 let buffer = create.await?;
2791 workspace.update_in(cx, move |workspace, window, cx| {
2792 workspace.split_item(
2793 direction,
2794 Box::new(
2795 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2796 ),
2797 window,
2798 cx,
2799 )
2800 })?;
2801 anyhow::Ok(())
2802 })
2803 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2804 match e.error_code() {
2805 ErrorCode::RemoteUpgradeRequired => Some(format!(
2806 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2807 e.error_tag("required").unwrap_or("the latest version")
2808 )),
2809 _ => None,
2810 }
2811 });
2812 }
2813
2814 pub fn leader_id(&self) -> Option<CollaboratorId> {
2815 self.leader_id
2816 }
2817
2818 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2819 &self.buffer
2820 }
2821
2822 pub fn project(&self) -> Option<&Entity<Project>> {
2823 self.project.as_ref()
2824 }
2825
2826 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2827 self.workspace.as_ref()?.0.upgrade()
2828 }
2829
2830 /// Returns the workspace serialization ID if this editor should be serialized.
2831 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2832 self.workspace
2833 .as_ref()
2834 .filter(|_| self.should_serialize_buffer())
2835 .and_then(|workspace| workspace.1)
2836 }
2837
2838 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2839 self.buffer().read(cx).title(cx)
2840 }
2841
2842 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2843 let git_blame_gutter_max_author_length = self
2844 .render_git_blame_gutter(cx)
2845 .then(|| {
2846 if let Some(blame) = self.blame.as_ref() {
2847 let max_author_length =
2848 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2849 Some(max_author_length)
2850 } else {
2851 None
2852 }
2853 })
2854 .flatten();
2855
2856 EditorSnapshot {
2857 mode: self.mode.clone(),
2858 show_gutter: self.show_gutter,
2859 show_line_numbers: self.show_line_numbers,
2860 show_git_diff_gutter: self.show_git_diff_gutter,
2861 show_code_actions: self.show_code_actions,
2862 show_runnables: self.show_runnables,
2863 show_breakpoints: self.show_breakpoints,
2864 git_blame_gutter_max_author_length,
2865 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2866 placeholder_display_snapshot: self
2867 .placeholder_display_map
2868 .as_ref()
2869 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2870 scroll_anchor: self.scroll_manager.anchor(),
2871 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2872 is_focused: self.focus_handle.is_focused(window),
2873 current_line_highlight: self
2874 .current_line_highlight
2875 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2876 gutter_hovered: self.gutter_hovered,
2877 }
2878 }
2879
2880 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2881 self.buffer.read(cx).language_at(point, cx)
2882 }
2883
2884 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2885 self.buffer.read(cx).read(cx).file_at(point).cloned()
2886 }
2887
2888 pub fn active_excerpt(
2889 &self,
2890 cx: &App,
2891 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2892 self.buffer
2893 .read(cx)
2894 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2895 }
2896
2897 pub fn mode(&self) -> &EditorMode {
2898 &self.mode
2899 }
2900
2901 pub fn set_mode(&mut self, mode: EditorMode) {
2902 self.mode = mode;
2903 }
2904
2905 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2906 self.collaboration_hub.as_deref()
2907 }
2908
2909 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2910 self.collaboration_hub = Some(hub);
2911 }
2912
2913 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2914 self.in_project_search = in_project_search;
2915 }
2916
2917 pub fn set_custom_context_menu(
2918 &mut self,
2919 f: impl 'static
2920 + Fn(
2921 &mut Self,
2922 DisplayPoint,
2923 &mut Window,
2924 &mut Context<Self>,
2925 ) -> Option<Entity<ui::ContextMenu>>,
2926 ) {
2927 self.custom_context_menu = Some(Box::new(f))
2928 }
2929
2930 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2931 self.completion_provider = provider;
2932 }
2933
2934 #[cfg(any(test, feature = "test-support"))]
2935 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2936 self.completion_provider.clone()
2937 }
2938
2939 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2940 self.semantics_provider.clone()
2941 }
2942
2943 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2944 self.semantics_provider = provider;
2945 }
2946
2947 pub fn set_edit_prediction_provider<T>(
2948 &mut self,
2949 provider: Option<Entity<T>>,
2950 window: &mut Window,
2951 cx: &mut Context<Self>,
2952 ) where
2953 T: EditPredictionProvider,
2954 {
2955 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2956 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2957 if this.focus_handle.is_focused(window) {
2958 this.update_visible_edit_prediction(window, cx);
2959 }
2960 }),
2961 provider: Arc::new(provider),
2962 });
2963 self.update_edit_prediction_settings(cx);
2964 self.refresh_edit_prediction(false, false, window, cx);
2965 }
2966
2967 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2968 self.placeholder_display_map
2969 .as_ref()
2970 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2971 }
2972
2973 pub fn set_placeholder_text(
2974 &mut self,
2975 placeholder_text: &str,
2976 window: &mut Window,
2977 cx: &mut Context<Self>,
2978 ) {
2979 let multibuffer = cx
2980 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2981
2982 let style = window.text_style();
2983
2984 self.placeholder_display_map = Some(cx.new(|cx| {
2985 DisplayMap::new(
2986 multibuffer,
2987 style.font(),
2988 style.font_size.to_pixels(window.rem_size()),
2989 None,
2990 FILE_HEADER_HEIGHT,
2991 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2992 Default::default(),
2993 DiagnosticSeverity::Off,
2994 cx,
2995 )
2996 }));
2997 cx.notify();
2998 }
2999
3000 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3001 self.cursor_shape = cursor_shape;
3002
3003 // Disrupt blink for immediate user feedback that the cursor shape has changed
3004 self.blink_manager.update(cx, BlinkManager::show_cursor);
3005
3006 cx.notify();
3007 }
3008
3009 pub fn set_current_line_highlight(
3010 &mut self,
3011 current_line_highlight: Option<CurrentLineHighlight>,
3012 ) {
3013 self.current_line_highlight = current_line_highlight;
3014 }
3015
3016 pub fn range_for_match<T: std::marker::Copy>(
3017 &self,
3018 range: &Range<T>,
3019 collapse: bool,
3020 ) -> Range<T> {
3021 if collapse {
3022 return range.start..range.start;
3023 }
3024 range.clone()
3025 }
3026
3027 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3028 if self.display_map.read(cx).clip_at_line_ends != clip {
3029 self.display_map
3030 .update(cx, |map, _| map.clip_at_line_ends = clip);
3031 }
3032 }
3033
3034 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3035 self.input_enabled = input_enabled;
3036 }
3037
3038 pub fn set_edit_predictions_hidden_for_vim_mode(
3039 &mut self,
3040 hidden: bool,
3041 window: &mut Window,
3042 cx: &mut Context<Self>,
3043 ) {
3044 if hidden != self.edit_predictions_hidden_for_vim_mode {
3045 self.edit_predictions_hidden_for_vim_mode = hidden;
3046 if hidden {
3047 self.update_visible_edit_prediction(window, cx);
3048 } else {
3049 self.refresh_edit_prediction(true, false, window, cx);
3050 }
3051 }
3052 }
3053
3054 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3055 self.menu_edit_predictions_policy = value;
3056 }
3057
3058 pub fn set_autoindent(&mut self, autoindent: bool) {
3059 if autoindent {
3060 self.autoindent_mode = Some(AutoindentMode::EachLine);
3061 } else {
3062 self.autoindent_mode = None;
3063 }
3064 }
3065
3066 pub fn read_only(&self, cx: &App) -> bool {
3067 self.read_only || self.buffer.read(cx).read_only()
3068 }
3069
3070 pub fn set_read_only(&mut self, read_only: bool) {
3071 self.read_only = read_only;
3072 }
3073
3074 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3075 self.use_autoclose = autoclose;
3076 }
3077
3078 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3079 self.use_auto_surround = auto_surround;
3080 }
3081
3082 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3083 self.auto_replace_emoji_shortcode = auto_replace;
3084 }
3085
3086 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3087 self.buffer_serialization = should_serialize.then(|| {
3088 BufferSerialization::new(
3089 ProjectSettings::get_global(cx)
3090 .session
3091 .restore_unsaved_buffers,
3092 )
3093 })
3094 }
3095
3096 fn should_serialize_buffer(&self) -> bool {
3097 self.buffer_serialization.is_some()
3098 }
3099
3100 pub fn toggle_edit_predictions(
3101 &mut self,
3102 _: &ToggleEditPrediction,
3103 window: &mut Window,
3104 cx: &mut Context<Self>,
3105 ) {
3106 if self.show_edit_predictions_override.is_some() {
3107 self.set_show_edit_predictions(None, window, cx);
3108 } else {
3109 let show_edit_predictions = !self.edit_predictions_enabled();
3110 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3111 }
3112 }
3113
3114 pub fn set_show_edit_predictions(
3115 &mut self,
3116 show_edit_predictions: Option<bool>,
3117 window: &mut Window,
3118 cx: &mut Context<Self>,
3119 ) {
3120 self.show_edit_predictions_override = show_edit_predictions;
3121 self.update_edit_prediction_settings(cx);
3122
3123 if let Some(false) = show_edit_predictions {
3124 self.discard_edit_prediction(false, cx);
3125 } else {
3126 self.refresh_edit_prediction(false, true, window, cx);
3127 }
3128 }
3129
3130 fn edit_predictions_disabled_in_scope(
3131 &self,
3132 buffer: &Entity<Buffer>,
3133 buffer_position: language::Anchor,
3134 cx: &App,
3135 ) -> bool {
3136 let snapshot = buffer.read(cx).snapshot();
3137 let settings = snapshot.settings_at(buffer_position, cx);
3138
3139 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3140 return false;
3141 };
3142
3143 scope.override_name().is_some_and(|scope_name| {
3144 settings
3145 .edit_predictions_disabled_in
3146 .iter()
3147 .any(|s| s == scope_name)
3148 })
3149 }
3150
3151 pub fn set_use_modal_editing(&mut self, to: bool) {
3152 self.use_modal_editing = to;
3153 }
3154
3155 pub fn use_modal_editing(&self) -> bool {
3156 self.use_modal_editing
3157 }
3158
3159 fn selections_did_change(
3160 &mut self,
3161 local: bool,
3162 old_cursor_position: &Anchor,
3163 effects: SelectionEffects,
3164 window: &mut Window,
3165 cx: &mut Context<Self>,
3166 ) {
3167 window.invalidate_character_coordinates();
3168
3169 // Copy selections to primary selection buffer
3170 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3171 if local {
3172 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3173 let buffer_handle = self.buffer.read(cx).read(cx);
3174
3175 let mut text = String::new();
3176 for (index, selection) in selections.iter().enumerate() {
3177 let text_for_selection = buffer_handle
3178 .text_for_range(selection.start..selection.end)
3179 .collect::<String>();
3180
3181 text.push_str(&text_for_selection);
3182 if index != selections.len() - 1 {
3183 text.push('\n');
3184 }
3185 }
3186
3187 if !text.is_empty() {
3188 cx.write_to_primary(ClipboardItem::new_string(text));
3189 }
3190 }
3191
3192 let selection_anchors = self.selections.disjoint_anchors_arc();
3193
3194 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3195 self.buffer.update(cx, |buffer, cx| {
3196 buffer.set_active_selections(
3197 &selection_anchors,
3198 self.selections.line_mode(),
3199 self.cursor_shape,
3200 cx,
3201 )
3202 });
3203 }
3204 let display_map = self
3205 .display_map
3206 .update(cx, |display_map, cx| display_map.snapshot(cx));
3207 let buffer = display_map.buffer_snapshot();
3208 if self.selections.count() == 1 {
3209 self.add_selections_state = None;
3210 }
3211 self.select_next_state = None;
3212 self.select_prev_state = None;
3213 self.select_syntax_node_history.try_clear();
3214 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3215 self.snippet_stack.invalidate(&selection_anchors, buffer);
3216 self.take_rename(false, window, cx);
3217
3218 let newest_selection = self.selections.newest_anchor();
3219 let new_cursor_position = newest_selection.head();
3220 let selection_start = newest_selection.start;
3221
3222 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3223 self.push_to_nav_history(
3224 *old_cursor_position,
3225 Some(new_cursor_position.to_point(buffer)),
3226 false,
3227 effects.nav_history == Some(true),
3228 cx,
3229 );
3230 }
3231
3232 if local {
3233 if let Some(buffer_id) = new_cursor_position.buffer_id {
3234 self.register_buffer(buffer_id, cx);
3235 }
3236
3237 let mut context_menu = self.context_menu.borrow_mut();
3238 let completion_menu = match context_menu.as_ref() {
3239 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3240 Some(CodeContextMenu::CodeActions(_)) => {
3241 *context_menu = None;
3242 None
3243 }
3244 None => None,
3245 };
3246 let completion_position = completion_menu.map(|menu| menu.initial_position);
3247 drop(context_menu);
3248
3249 if effects.completions
3250 && let Some(completion_position) = completion_position
3251 {
3252 let start_offset = selection_start.to_offset(buffer);
3253 let position_matches = start_offset == completion_position.to_offset(buffer);
3254 let continue_showing = if position_matches {
3255 if self.snippet_stack.is_empty() {
3256 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3257 == Some(CharKind::Word)
3258 } else {
3259 // Snippet choices can be shown even when the cursor is in whitespace.
3260 // Dismissing the menu with actions like backspace is handled by
3261 // invalidation regions.
3262 true
3263 }
3264 } else {
3265 false
3266 };
3267
3268 if continue_showing {
3269 self.open_or_update_completions_menu(None, None, false, window, cx);
3270 } else {
3271 self.hide_context_menu(window, cx);
3272 }
3273 }
3274
3275 hide_hover(self, cx);
3276
3277 if old_cursor_position.to_display_point(&display_map).row()
3278 != new_cursor_position.to_display_point(&display_map).row()
3279 {
3280 self.available_code_actions.take();
3281 }
3282 self.refresh_code_actions(window, cx);
3283 self.refresh_document_highlights(cx);
3284 refresh_linked_ranges(self, window, cx);
3285
3286 self.refresh_selected_text_highlights(false, window, cx);
3287 self.refresh_matching_bracket_highlights(window, cx);
3288 self.update_visible_edit_prediction(window, cx);
3289 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3290 self.inline_blame_popover.take();
3291 if self.git_blame_inline_enabled {
3292 self.start_inline_blame_timer(window, cx);
3293 }
3294 }
3295
3296 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3297 cx.emit(EditorEvent::SelectionsChanged { local });
3298
3299 let selections = &self.selections.disjoint_anchors_arc();
3300 if selections.len() == 1 {
3301 cx.emit(SearchEvent::ActiveMatchChanged)
3302 }
3303 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3304 let inmemory_selections = selections
3305 .iter()
3306 .map(|s| {
3307 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3308 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3309 })
3310 .collect();
3311 self.update_restoration_data(cx, |data| {
3312 data.selections = inmemory_selections;
3313 });
3314
3315 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3316 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3317 {
3318 let snapshot = self.buffer().read(cx).snapshot(cx);
3319 let selections = selections.clone();
3320 let background_executor = cx.background_executor().clone();
3321 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3322 self.serialize_selections = cx.background_spawn(async move {
3323 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3324 let db_selections = selections
3325 .iter()
3326 .map(|selection| {
3327 (
3328 selection.start.to_offset(&snapshot),
3329 selection.end.to_offset(&snapshot),
3330 )
3331 })
3332 .collect();
3333
3334 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3335 .await
3336 .with_context(|| {
3337 format!(
3338 "persisting editor selections for editor {editor_id}, \
3339 workspace {workspace_id:?}"
3340 )
3341 })
3342 .log_err();
3343 });
3344 }
3345 }
3346
3347 cx.notify();
3348 }
3349
3350 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3351 use text::ToOffset as _;
3352 use text::ToPoint as _;
3353
3354 if self.mode.is_minimap()
3355 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3356 {
3357 return;
3358 }
3359
3360 if !self.buffer().read(cx).is_singleton() {
3361 return;
3362 }
3363
3364 let display_snapshot = self
3365 .display_map
3366 .update(cx, |display_map, cx| display_map.snapshot(cx));
3367 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3368 return;
3369 };
3370 let inmemory_folds = display_snapshot
3371 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3372 .map(|fold| {
3373 fold.range.start.text_anchor.to_point(&snapshot)
3374 ..fold.range.end.text_anchor.to_point(&snapshot)
3375 })
3376 .collect();
3377 self.update_restoration_data(cx, |data| {
3378 data.folds = inmemory_folds;
3379 });
3380
3381 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3382 return;
3383 };
3384 let background_executor = cx.background_executor().clone();
3385 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3386 let db_folds = display_snapshot
3387 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3388 .map(|fold| {
3389 (
3390 fold.range.start.text_anchor.to_offset(&snapshot),
3391 fold.range.end.text_anchor.to_offset(&snapshot),
3392 )
3393 })
3394 .collect();
3395 self.serialize_folds = cx.background_spawn(async move {
3396 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3397 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3398 .await
3399 .with_context(|| {
3400 format!(
3401 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3402 )
3403 })
3404 .log_err();
3405 });
3406 }
3407
3408 pub fn sync_selections(
3409 &mut self,
3410 other: Entity<Editor>,
3411 cx: &mut Context<Self>,
3412 ) -> gpui::Subscription {
3413 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3414 if !other_selections.is_empty() {
3415 self.selections
3416 .change_with(&self.display_snapshot(cx), |selections| {
3417 selections.select_anchors(other_selections);
3418 });
3419 }
3420
3421 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3422 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3423 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3424 if other_selections.is_empty() {
3425 return;
3426 }
3427 let snapshot = this.display_snapshot(cx);
3428 this.selections.change_with(&snapshot, |selections| {
3429 selections.select_anchors(other_selections);
3430 });
3431 }
3432 });
3433
3434 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3435 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3436 let these_selections = this.selections.disjoint_anchors().to_vec();
3437 if these_selections.is_empty() {
3438 return;
3439 }
3440 other.update(cx, |other_editor, cx| {
3441 let snapshot = other_editor.display_snapshot(cx);
3442 other_editor
3443 .selections
3444 .change_with(&snapshot, |selections| {
3445 selections.select_anchors(these_selections);
3446 })
3447 });
3448 }
3449 });
3450
3451 Subscription::join(other_subscription, this_subscription)
3452 }
3453
3454 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3455 if self.buffer().read(cx).is_singleton() {
3456 return;
3457 }
3458 let snapshot = self.buffer.read(cx).snapshot(cx);
3459 let buffer_ids: HashSet<BufferId> = self
3460 .selections
3461 .disjoint_anchor_ranges()
3462 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3463 .collect();
3464 for buffer_id in buffer_ids {
3465 self.unfold_buffer(buffer_id, cx);
3466 }
3467 }
3468
3469 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3470 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3471 /// effects of selection change occur at the end of the transaction.
3472 pub fn change_selections<R>(
3473 &mut self,
3474 effects: SelectionEffects,
3475 window: &mut Window,
3476 cx: &mut Context<Self>,
3477 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3478 ) -> R {
3479 let snapshot = self.display_snapshot(cx);
3480 if let Some(state) = &mut self.deferred_selection_effects_state {
3481 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3482 state.effects.completions = effects.completions;
3483 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3484 let (changed, result) = self.selections.change_with(&snapshot, change);
3485 state.changed |= changed;
3486 return result;
3487 }
3488 let mut state = DeferredSelectionEffectsState {
3489 changed: false,
3490 effects,
3491 old_cursor_position: self.selections.newest_anchor().head(),
3492 history_entry: SelectionHistoryEntry {
3493 selections: self.selections.disjoint_anchors_arc(),
3494 select_next_state: self.select_next_state.clone(),
3495 select_prev_state: self.select_prev_state.clone(),
3496 add_selections_state: self.add_selections_state.clone(),
3497 },
3498 };
3499 let (changed, result) = self.selections.change_with(&snapshot, change);
3500 state.changed = state.changed || changed;
3501 if self.defer_selection_effects {
3502 self.deferred_selection_effects_state = Some(state);
3503 } else {
3504 self.apply_selection_effects(state, window, cx);
3505 }
3506 result
3507 }
3508
3509 /// Defers the effects of selection change, so that the effects of multiple calls to
3510 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3511 /// to selection history and the state of popovers based on selection position aren't
3512 /// erroneously updated.
3513 pub fn with_selection_effects_deferred<R>(
3514 &mut self,
3515 window: &mut Window,
3516 cx: &mut Context<Self>,
3517 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3518 ) -> R {
3519 let already_deferred = self.defer_selection_effects;
3520 self.defer_selection_effects = true;
3521 let result = update(self, window, cx);
3522 if !already_deferred {
3523 self.defer_selection_effects = false;
3524 if let Some(state) = self.deferred_selection_effects_state.take() {
3525 self.apply_selection_effects(state, window, cx);
3526 }
3527 }
3528 result
3529 }
3530
3531 fn apply_selection_effects(
3532 &mut self,
3533 state: DeferredSelectionEffectsState,
3534 window: &mut Window,
3535 cx: &mut Context<Self>,
3536 ) {
3537 if state.changed {
3538 self.selection_history.push(state.history_entry);
3539
3540 if let Some(autoscroll) = state.effects.scroll {
3541 self.request_autoscroll(autoscroll, cx);
3542 }
3543
3544 let old_cursor_position = &state.old_cursor_position;
3545
3546 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3547
3548 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3549 self.show_signature_help(&ShowSignatureHelp, window, cx);
3550 }
3551 }
3552 }
3553
3554 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3555 where
3556 I: IntoIterator<Item = (Range<S>, T)>,
3557 S: ToOffset,
3558 T: Into<Arc<str>>,
3559 {
3560 if self.read_only(cx) {
3561 return;
3562 }
3563
3564 self.buffer
3565 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3566 }
3567
3568 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3569 where
3570 I: IntoIterator<Item = (Range<S>, T)>,
3571 S: ToOffset,
3572 T: Into<Arc<str>>,
3573 {
3574 if self.read_only(cx) {
3575 return;
3576 }
3577
3578 self.buffer.update(cx, |buffer, cx| {
3579 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3580 });
3581 }
3582
3583 pub fn edit_with_block_indent<I, S, T>(
3584 &mut self,
3585 edits: I,
3586 original_indent_columns: Vec<Option<u32>>,
3587 cx: &mut Context<Self>,
3588 ) where
3589 I: IntoIterator<Item = (Range<S>, T)>,
3590 S: ToOffset,
3591 T: Into<Arc<str>>,
3592 {
3593 if self.read_only(cx) {
3594 return;
3595 }
3596
3597 self.buffer.update(cx, |buffer, cx| {
3598 buffer.edit(
3599 edits,
3600 Some(AutoindentMode::Block {
3601 original_indent_columns,
3602 }),
3603 cx,
3604 )
3605 });
3606 }
3607
3608 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3609 self.hide_context_menu(window, cx);
3610
3611 match phase {
3612 SelectPhase::Begin {
3613 position,
3614 add,
3615 click_count,
3616 } => self.begin_selection(position, add, click_count, window, cx),
3617 SelectPhase::BeginColumnar {
3618 position,
3619 goal_column,
3620 reset,
3621 mode,
3622 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3623 SelectPhase::Extend {
3624 position,
3625 click_count,
3626 } => self.extend_selection(position, click_count, window, cx),
3627 SelectPhase::Update {
3628 position,
3629 goal_column,
3630 scroll_delta,
3631 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3632 SelectPhase::End => self.end_selection(window, cx),
3633 }
3634 }
3635
3636 fn extend_selection(
3637 &mut self,
3638 position: DisplayPoint,
3639 click_count: usize,
3640 window: &mut Window,
3641 cx: &mut Context<Self>,
3642 ) {
3643 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3644 let tail = self.selections.newest::<usize>(&display_map).tail();
3645 let click_count = click_count.max(match self.selections.select_mode() {
3646 SelectMode::Character => 1,
3647 SelectMode::Word(_) => 2,
3648 SelectMode::Line(_) => 3,
3649 SelectMode::All => 4,
3650 });
3651 self.begin_selection(position, false, click_count, window, cx);
3652
3653 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3654
3655 let current_selection = match self.selections.select_mode() {
3656 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3657 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3658 };
3659
3660 let mut pending_selection = self
3661 .selections
3662 .pending_anchor()
3663 .cloned()
3664 .expect("extend_selection not called with pending selection");
3665
3666 if pending_selection
3667 .start
3668 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3669 == Ordering::Greater
3670 {
3671 pending_selection.start = current_selection.start;
3672 }
3673 if pending_selection
3674 .end
3675 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3676 == Ordering::Less
3677 {
3678 pending_selection.end = current_selection.end;
3679 pending_selection.reversed = true;
3680 }
3681
3682 let mut pending_mode = self.selections.pending_mode().unwrap();
3683 match &mut pending_mode {
3684 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3685 _ => {}
3686 }
3687
3688 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3689 SelectionEffects::scroll(Autoscroll::fit())
3690 } else {
3691 SelectionEffects::no_scroll()
3692 };
3693
3694 self.change_selections(effects, window, cx, |s| {
3695 s.set_pending(pending_selection.clone(), pending_mode);
3696 s.set_is_extending(true);
3697 });
3698 }
3699
3700 fn begin_selection(
3701 &mut self,
3702 position: DisplayPoint,
3703 add: bool,
3704 click_count: usize,
3705 window: &mut Window,
3706 cx: &mut Context<Self>,
3707 ) {
3708 if !self.focus_handle.is_focused(window) {
3709 self.last_focused_descendant = None;
3710 window.focus(&self.focus_handle);
3711 }
3712
3713 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3714 let buffer = display_map.buffer_snapshot();
3715 let position = display_map.clip_point(position, Bias::Left);
3716
3717 let start;
3718 let end;
3719 let mode;
3720 let mut auto_scroll;
3721 match click_count {
3722 1 => {
3723 start = buffer.anchor_before(position.to_point(&display_map));
3724 end = start;
3725 mode = SelectMode::Character;
3726 auto_scroll = true;
3727 }
3728 2 => {
3729 let position = display_map
3730 .clip_point(position, Bias::Left)
3731 .to_offset(&display_map, Bias::Left);
3732 let (range, _) = buffer.surrounding_word(position, None);
3733 start = buffer.anchor_before(range.start);
3734 end = buffer.anchor_before(range.end);
3735 mode = SelectMode::Word(start..end);
3736 auto_scroll = true;
3737 }
3738 3 => {
3739 let position = display_map
3740 .clip_point(position, Bias::Left)
3741 .to_point(&display_map);
3742 let line_start = display_map.prev_line_boundary(position).0;
3743 let next_line_start = buffer.clip_point(
3744 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3745 Bias::Left,
3746 );
3747 start = buffer.anchor_before(line_start);
3748 end = buffer.anchor_before(next_line_start);
3749 mode = SelectMode::Line(start..end);
3750 auto_scroll = true;
3751 }
3752 _ => {
3753 start = buffer.anchor_before(0);
3754 end = buffer.anchor_before(buffer.len());
3755 mode = SelectMode::All;
3756 auto_scroll = false;
3757 }
3758 }
3759 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3760
3761 let point_to_delete: Option<usize> = {
3762 let selected_points: Vec<Selection<Point>> =
3763 self.selections.disjoint_in_range(start..end, &display_map);
3764
3765 if !add || click_count > 1 {
3766 None
3767 } else if !selected_points.is_empty() {
3768 Some(selected_points[0].id)
3769 } else {
3770 let clicked_point_already_selected =
3771 self.selections.disjoint_anchors().iter().find(|selection| {
3772 selection.start.to_point(buffer) == start.to_point(buffer)
3773 || selection.end.to_point(buffer) == end.to_point(buffer)
3774 });
3775
3776 clicked_point_already_selected.map(|selection| selection.id)
3777 }
3778 };
3779
3780 let selections_count = self.selections.count();
3781 let effects = if auto_scroll {
3782 SelectionEffects::default()
3783 } else {
3784 SelectionEffects::no_scroll()
3785 };
3786
3787 self.change_selections(effects, window, cx, |s| {
3788 if let Some(point_to_delete) = point_to_delete {
3789 s.delete(point_to_delete);
3790
3791 if selections_count == 1 {
3792 s.set_pending_anchor_range(start..end, mode);
3793 }
3794 } else {
3795 if !add {
3796 s.clear_disjoint();
3797 }
3798
3799 s.set_pending_anchor_range(start..end, mode);
3800 }
3801 });
3802 }
3803
3804 fn begin_columnar_selection(
3805 &mut self,
3806 position: DisplayPoint,
3807 goal_column: u32,
3808 reset: bool,
3809 mode: ColumnarMode,
3810 window: &mut Window,
3811 cx: &mut Context<Self>,
3812 ) {
3813 if !self.focus_handle.is_focused(window) {
3814 self.last_focused_descendant = None;
3815 window.focus(&self.focus_handle);
3816 }
3817
3818 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3819
3820 if reset {
3821 let pointer_position = display_map
3822 .buffer_snapshot()
3823 .anchor_before(position.to_point(&display_map));
3824
3825 self.change_selections(
3826 SelectionEffects::scroll(Autoscroll::newest()),
3827 window,
3828 cx,
3829 |s| {
3830 s.clear_disjoint();
3831 s.set_pending_anchor_range(
3832 pointer_position..pointer_position,
3833 SelectMode::Character,
3834 );
3835 },
3836 );
3837 };
3838
3839 let tail = self.selections.newest::<Point>(&display_map).tail();
3840 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3841 self.columnar_selection_state = match mode {
3842 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3843 selection_tail: selection_anchor,
3844 display_point: if reset {
3845 if position.column() != goal_column {
3846 Some(DisplayPoint::new(position.row(), goal_column))
3847 } else {
3848 None
3849 }
3850 } else {
3851 None
3852 },
3853 }),
3854 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3855 selection_tail: selection_anchor,
3856 }),
3857 };
3858
3859 if !reset {
3860 self.select_columns(position, goal_column, &display_map, window, cx);
3861 }
3862 }
3863
3864 fn update_selection(
3865 &mut self,
3866 position: DisplayPoint,
3867 goal_column: u32,
3868 scroll_delta: gpui::Point<f32>,
3869 window: &mut Window,
3870 cx: &mut Context<Self>,
3871 ) {
3872 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3873
3874 if self.columnar_selection_state.is_some() {
3875 self.select_columns(position, goal_column, &display_map, window, cx);
3876 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3877 let buffer = display_map.buffer_snapshot();
3878 let head;
3879 let tail;
3880 let mode = self.selections.pending_mode().unwrap();
3881 match &mode {
3882 SelectMode::Character => {
3883 head = position.to_point(&display_map);
3884 tail = pending.tail().to_point(buffer);
3885 }
3886 SelectMode::Word(original_range) => {
3887 let offset = display_map
3888 .clip_point(position, Bias::Left)
3889 .to_offset(&display_map, Bias::Left);
3890 let original_range = original_range.to_offset(buffer);
3891
3892 let head_offset = if buffer.is_inside_word(offset, None)
3893 || original_range.contains(&offset)
3894 {
3895 let (word_range, _) = buffer.surrounding_word(offset, None);
3896 if word_range.start < original_range.start {
3897 word_range.start
3898 } else {
3899 word_range.end
3900 }
3901 } else {
3902 offset
3903 };
3904
3905 head = head_offset.to_point(buffer);
3906 if head_offset <= original_range.start {
3907 tail = original_range.end.to_point(buffer);
3908 } else {
3909 tail = original_range.start.to_point(buffer);
3910 }
3911 }
3912 SelectMode::Line(original_range) => {
3913 let original_range = original_range.to_point(display_map.buffer_snapshot());
3914
3915 let position = display_map
3916 .clip_point(position, Bias::Left)
3917 .to_point(&display_map);
3918 let line_start = display_map.prev_line_boundary(position).0;
3919 let next_line_start = buffer.clip_point(
3920 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3921 Bias::Left,
3922 );
3923
3924 if line_start < original_range.start {
3925 head = line_start
3926 } else {
3927 head = next_line_start
3928 }
3929
3930 if head <= original_range.start {
3931 tail = original_range.end;
3932 } else {
3933 tail = original_range.start;
3934 }
3935 }
3936 SelectMode::All => {
3937 return;
3938 }
3939 };
3940
3941 if head < tail {
3942 pending.start = buffer.anchor_before(head);
3943 pending.end = buffer.anchor_before(tail);
3944 pending.reversed = true;
3945 } else {
3946 pending.start = buffer.anchor_before(tail);
3947 pending.end = buffer.anchor_before(head);
3948 pending.reversed = false;
3949 }
3950
3951 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3952 s.set_pending(pending.clone(), mode);
3953 });
3954 } else {
3955 log::error!("update_selection dispatched with no pending selection");
3956 return;
3957 }
3958
3959 self.apply_scroll_delta(scroll_delta, window, cx);
3960 cx.notify();
3961 }
3962
3963 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3964 self.columnar_selection_state.take();
3965 if let Some(pending_mode) = self.selections.pending_mode() {
3966 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3967 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3968 s.select(selections);
3969 s.clear_pending();
3970 if s.is_extending() {
3971 s.set_is_extending(false);
3972 } else {
3973 s.set_select_mode(pending_mode);
3974 }
3975 });
3976 }
3977 }
3978
3979 fn select_columns(
3980 &mut self,
3981 head: DisplayPoint,
3982 goal_column: u32,
3983 display_map: &DisplaySnapshot,
3984 window: &mut Window,
3985 cx: &mut Context<Self>,
3986 ) {
3987 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3988 return;
3989 };
3990
3991 let tail = match columnar_state {
3992 ColumnarSelectionState::FromMouse {
3993 selection_tail,
3994 display_point,
3995 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3996 ColumnarSelectionState::FromSelection { selection_tail } => {
3997 selection_tail.to_display_point(display_map)
3998 }
3999 };
4000
4001 let start_row = cmp::min(tail.row(), head.row());
4002 let end_row = cmp::max(tail.row(), head.row());
4003 let start_column = cmp::min(tail.column(), goal_column);
4004 let end_column = cmp::max(tail.column(), goal_column);
4005 let reversed = start_column < tail.column();
4006
4007 let selection_ranges = (start_row.0..=end_row.0)
4008 .map(DisplayRow)
4009 .filter_map(|row| {
4010 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4011 || start_column <= display_map.line_len(row))
4012 && !display_map.is_block_line(row)
4013 {
4014 let start = display_map
4015 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4016 .to_point(display_map);
4017 let end = display_map
4018 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4019 .to_point(display_map);
4020 if reversed {
4021 Some(end..start)
4022 } else {
4023 Some(start..end)
4024 }
4025 } else {
4026 None
4027 }
4028 })
4029 .collect::<Vec<_>>();
4030 if selection_ranges.is_empty() {
4031 return;
4032 }
4033
4034 let ranges = match columnar_state {
4035 ColumnarSelectionState::FromMouse { .. } => {
4036 let mut non_empty_ranges = selection_ranges
4037 .iter()
4038 .filter(|selection_range| selection_range.start != selection_range.end)
4039 .peekable();
4040 if non_empty_ranges.peek().is_some() {
4041 non_empty_ranges.cloned().collect()
4042 } else {
4043 selection_ranges
4044 }
4045 }
4046 _ => selection_ranges,
4047 };
4048
4049 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4050 s.select_ranges(ranges);
4051 });
4052 cx.notify();
4053 }
4054
4055 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4056 self.selections
4057 .all_adjusted(snapshot)
4058 .iter()
4059 .any(|selection| !selection.is_empty())
4060 }
4061
4062 pub fn has_pending_nonempty_selection(&self) -> bool {
4063 let pending_nonempty_selection = match self.selections.pending_anchor() {
4064 Some(Selection { start, end, .. }) => start != end,
4065 None => false,
4066 };
4067
4068 pending_nonempty_selection
4069 || (self.columnar_selection_state.is_some()
4070 && self.selections.disjoint_anchors().len() > 1)
4071 }
4072
4073 pub fn has_pending_selection(&self) -> bool {
4074 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4075 }
4076
4077 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4078 self.selection_mark_mode = false;
4079 self.selection_drag_state = SelectionDragState::None;
4080
4081 if self.dismiss_menus_and_popups(true, window, cx) {
4082 cx.notify();
4083 return;
4084 }
4085 if self.clear_expanded_diff_hunks(cx) {
4086 cx.notify();
4087 return;
4088 }
4089 if self.show_git_blame_gutter {
4090 self.show_git_blame_gutter = false;
4091 cx.notify();
4092 return;
4093 }
4094
4095 if self.mode.is_full()
4096 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4097 {
4098 cx.notify();
4099 return;
4100 }
4101
4102 cx.propagate();
4103 }
4104
4105 pub fn dismiss_menus_and_popups(
4106 &mut self,
4107 is_user_requested: bool,
4108 window: &mut Window,
4109 cx: &mut Context<Self>,
4110 ) -> bool {
4111 if self.take_rename(false, window, cx).is_some() {
4112 return true;
4113 }
4114
4115 if self.hide_blame_popover(true, cx) {
4116 return true;
4117 }
4118
4119 if hide_hover(self, cx) {
4120 return true;
4121 }
4122
4123 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
4124 return true;
4125 }
4126
4127 if self.hide_context_menu(window, cx).is_some() {
4128 return true;
4129 }
4130
4131 if self.mouse_context_menu.take().is_some() {
4132 return true;
4133 }
4134
4135 if is_user_requested && self.discard_edit_prediction(true, cx) {
4136 return true;
4137 }
4138
4139 if self.snippet_stack.pop().is_some() {
4140 return true;
4141 }
4142
4143 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4144 self.dismiss_diagnostics(cx);
4145 return true;
4146 }
4147
4148 false
4149 }
4150
4151 fn linked_editing_ranges_for(
4152 &self,
4153 selection: Range<text::Anchor>,
4154 cx: &App,
4155 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4156 if self.linked_edit_ranges.is_empty() {
4157 return None;
4158 }
4159 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4160 selection.end.buffer_id.and_then(|end_buffer_id| {
4161 if selection.start.buffer_id != Some(end_buffer_id) {
4162 return None;
4163 }
4164 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4165 let snapshot = buffer.read(cx).snapshot();
4166 self.linked_edit_ranges
4167 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4168 .map(|ranges| (ranges, snapshot, buffer))
4169 })?;
4170 use text::ToOffset as TO;
4171 // find offset from the start of current range to current cursor position
4172 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4173
4174 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4175 let start_difference = start_offset - start_byte_offset;
4176 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4177 let end_difference = end_offset - start_byte_offset;
4178 // Current range has associated linked ranges.
4179 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4180 for range in linked_ranges.iter() {
4181 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4182 let end_offset = start_offset + end_difference;
4183 let start_offset = start_offset + start_difference;
4184 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4185 continue;
4186 }
4187 if self.selections.disjoint_anchor_ranges().any(|s| {
4188 if s.start.buffer_id != selection.start.buffer_id
4189 || s.end.buffer_id != selection.end.buffer_id
4190 {
4191 return false;
4192 }
4193 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4194 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4195 }) {
4196 continue;
4197 }
4198 let start = buffer_snapshot.anchor_after(start_offset);
4199 let end = buffer_snapshot.anchor_after(end_offset);
4200 linked_edits
4201 .entry(buffer.clone())
4202 .or_default()
4203 .push(start..end);
4204 }
4205 Some(linked_edits)
4206 }
4207
4208 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4209 let text: Arc<str> = text.into();
4210
4211 if self.read_only(cx) {
4212 return;
4213 }
4214
4215 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4216
4217 self.unfold_buffers_with_selections(cx);
4218
4219 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4220 let mut bracket_inserted = false;
4221 let mut edits = Vec::new();
4222 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4223 let mut new_selections = Vec::with_capacity(selections.len());
4224 let mut new_autoclose_regions = Vec::new();
4225 let snapshot = self.buffer.read(cx).read(cx);
4226 let mut clear_linked_edit_ranges = false;
4227
4228 for (selection, autoclose_region) in
4229 self.selections_with_autoclose_regions(selections, &snapshot)
4230 {
4231 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4232 // Determine if the inserted text matches the opening or closing
4233 // bracket of any of this language's bracket pairs.
4234 let mut bracket_pair = None;
4235 let mut is_bracket_pair_start = false;
4236 let mut is_bracket_pair_end = false;
4237 if !text.is_empty() {
4238 let mut bracket_pair_matching_end = None;
4239 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4240 // and they are removing the character that triggered IME popup.
4241 for (pair, enabled) in scope.brackets() {
4242 if !pair.close && !pair.surround {
4243 continue;
4244 }
4245
4246 if enabled && pair.start.ends_with(text.as_ref()) {
4247 let prefix_len = pair.start.len() - text.len();
4248 let preceding_text_matches_prefix = prefix_len == 0
4249 || (selection.start.column >= (prefix_len as u32)
4250 && snapshot.contains_str_at(
4251 Point::new(
4252 selection.start.row,
4253 selection.start.column - (prefix_len as u32),
4254 ),
4255 &pair.start[..prefix_len],
4256 ));
4257 if preceding_text_matches_prefix {
4258 bracket_pair = Some(pair.clone());
4259 is_bracket_pair_start = true;
4260 break;
4261 }
4262 }
4263 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4264 {
4265 // take first bracket pair matching end, but don't break in case a later bracket
4266 // pair matches start
4267 bracket_pair_matching_end = Some(pair.clone());
4268 }
4269 }
4270 if let Some(end) = bracket_pair_matching_end
4271 && bracket_pair.is_none()
4272 {
4273 bracket_pair = Some(end);
4274 is_bracket_pair_end = true;
4275 }
4276 }
4277
4278 if let Some(bracket_pair) = bracket_pair {
4279 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4280 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4281 let auto_surround =
4282 self.use_auto_surround && snapshot_settings.use_auto_surround;
4283 if selection.is_empty() {
4284 if is_bracket_pair_start {
4285 // If the inserted text is a suffix of an opening bracket and the
4286 // selection is preceded by the rest of the opening bracket, then
4287 // insert the closing bracket.
4288 let following_text_allows_autoclose = snapshot
4289 .chars_at(selection.start)
4290 .next()
4291 .is_none_or(|c| scope.should_autoclose_before(c));
4292
4293 let preceding_text_allows_autoclose = selection.start.column == 0
4294 || snapshot
4295 .reversed_chars_at(selection.start)
4296 .next()
4297 .is_none_or(|c| {
4298 bracket_pair.start != bracket_pair.end
4299 || !snapshot
4300 .char_classifier_at(selection.start)
4301 .is_word(c)
4302 });
4303
4304 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4305 && bracket_pair.start.len() == 1
4306 {
4307 let target = bracket_pair.start.chars().next().unwrap();
4308 let current_line_count = snapshot
4309 .reversed_chars_at(selection.start)
4310 .take_while(|&c| c != '\n')
4311 .filter(|&c| c == target)
4312 .count();
4313 current_line_count % 2 == 1
4314 } else {
4315 false
4316 };
4317
4318 if autoclose
4319 && bracket_pair.close
4320 && following_text_allows_autoclose
4321 && preceding_text_allows_autoclose
4322 && !is_closing_quote
4323 {
4324 let anchor = snapshot.anchor_before(selection.end);
4325 new_selections.push((selection.map(|_| anchor), text.len()));
4326 new_autoclose_regions.push((
4327 anchor,
4328 text.len(),
4329 selection.id,
4330 bracket_pair.clone(),
4331 ));
4332 edits.push((
4333 selection.range(),
4334 format!("{}{}", text, bracket_pair.end).into(),
4335 ));
4336 bracket_inserted = true;
4337 continue;
4338 }
4339 }
4340
4341 if let Some(region) = autoclose_region {
4342 // If the selection is followed by an auto-inserted closing bracket,
4343 // then don't insert that closing bracket again; just move the selection
4344 // past the closing bracket.
4345 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4346 && text.as_ref() == region.pair.end.as_str()
4347 && snapshot.contains_str_at(region.range.end, text.as_ref());
4348 if should_skip {
4349 let anchor = snapshot.anchor_after(selection.end);
4350 new_selections
4351 .push((selection.map(|_| anchor), region.pair.end.len()));
4352 continue;
4353 }
4354 }
4355
4356 let always_treat_brackets_as_autoclosed = snapshot
4357 .language_settings_at(selection.start, cx)
4358 .always_treat_brackets_as_autoclosed;
4359 if always_treat_brackets_as_autoclosed
4360 && is_bracket_pair_end
4361 && snapshot.contains_str_at(selection.end, text.as_ref())
4362 {
4363 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4364 // and the inserted text is a closing bracket and the selection is followed
4365 // by the closing bracket then move the selection past the closing bracket.
4366 let anchor = snapshot.anchor_after(selection.end);
4367 new_selections.push((selection.map(|_| anchor), text.len()));
4368 continue;
4369 }
4370 }
4371 // If an opening bracket is 1 character long and is typed while
4372 // text is selected, then surround that text with the bracket pair.
4373 else if auto_surround
4374 && bracket_pair.surround
4375 && is_bracket_pair_start
4376 && bracket_pair.start.chars().count() == 1
4377 {
4378 edits.push((selection.start..selection.start, text.clone()));
4379 edits.push((
4380 selection.end..selection.end,
4381 bracket_pair.end.as_str().into(),
4382 ));
4383 bracket_inserted = true;
4384 new_selections.push((
4385 Selection {
4386 id: selection.id,
4387 start: snapshot.anchor_after(selection.start),
4388 end: snapshot.anchor_before(selection.end),
4389 reversed: selection.reversed,
4390 goal: selection.goal,
4391 },
4392 0,
4393 ));
4394 continue;
4395 }
4396 }
4397 }
4398
4399 if self.auto_replace_emoji_shortcode
4400 && selection.is_empty()
4401 && text.as_ref().ends_with(':')
4402 && let Some(possible_emoji_short_code) =
4403 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4404 && !possible_emoji_short_code.is_empty()
4405 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4406 {
4407 let emoji_shortcode_start = Point::new(
4408 selection.start.row,
4409 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4410 );
4411
4412 // Remove shortcode from buffer
4413 edits.push((
4414 emoji_shortcode_start..selection.start,
4415 "".to_string().into(),
4416 ));
4417 new_selections.push((
4418 Selection {
4419 id: selection.id,
4420 start: snapshot.anchor_after(emoji_shortcode_start),
4421 end: snapshot.anchor_before(selection.start),
4422 reversed: selection.reversed,
4423 goal: selection.goal,
4424 },
4425 0,
4426 ));
4427
4428 // Insert emoji
4429 let selection_start_anchor = snapshot.anchor_after(selection.start);
4430 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4431 edits.push((selection.start..selection.end, emoji.to_string().into()));
4432
4433 continue;
4434 }
4435
4436 // If not handling any auto-close operation, then just replace the selected
4437 // text with the given input and move the selection to the end of the
4438 // newly inserted text.
4439 let anchor = snapshot.anchor_after(selection.end);
4440 if !self.linked_edit_ranges.is_empty() {
4441 let start_anchor = snapshot.anchor_before(selection.start);
4442
4443 let is_word_char = text.chars().next().is_none_or(|char| {
4444 let classifier = snapshot
4445 .char_classifier_at(start_anchor.to_offset(&snapshot))
4446 .scope_context(Some(CharScopeContext::LinkedEdit));
4447 classifier.is_word(char)
4448 });
4449
4450 if is_word_char {
4451 if let Some(ranges) = self
4452 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4453 {
4454 for (buffer, edits) in ranges {
4455 linked_edits
4456 .entry(buffer.clone())
4457 .or_default()
4458 .extend(edits.into_iter().map(|range| (range, text.clone())));
4459 }
4460 }
4461 } else {
4462 clear_linked_edit_ranges = true;
4463 }
4464 }
4465
4466 new_selections.push((selection.map(|_| anchor), 0));
4467 edits.push((selection.start..selection.end, text.clone()));
4468 }
4469
4470 drop(snapshot);
4471
4472 self.transact(window, cx, |this, window, cx| {
4473 if clear_linked_edit_ranges {
4474 this.linked_edit_ranges.clear();
4475 }
4476 let initial_buffer_versions =
4477 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4478
4479 this.buffer.update(cx, |buffer, cx| {
4480 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4481 });
4482 for (buffer, edits) in linked_edits {
4483 buffer.update(cx, |buffer, cx| {
4484 let snapshot = buffer.snapshot();
4485 let edits = edits
4486 .into_iter()
4487 .map(|(range, text)| {
4488 use text::ToPoint as TP;
4489 let end_point = TP::to_point(&range.end, &snapshot);
4490 let start_point = TP::to_point(&range.start, &snapshot);
4491 (start_point..end_point, text)
4492 })
4493 .sorted_by_key(|(range, _)| range.start);
4494 buffer.edit(edits, None, cx);
4495 })
4496 }
4497 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4498 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4499 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4500 let new_selections =
4501 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4502 .zip(new_selection_deltas)
4503 .map(|(selection, delta)| Selection {
4504 id: selection.id,
4505 start: selection.start + delta,
4506 end: selection.end + delta,
4507 reversed: selection.reversed,
4508 goal: SelectionGoal::None,
4509 })
4510 .collect::<Vec<_>>();
4511
4512 let mut i = 0;
4513 for (position, delta, selection_id, pair) in new_autoclose_regions {
4514 let position = position.to_offset(map.buffer_snapshot()) + delta;
4515 let start = map.buffer_snapshot().anchor_before(position);
4516 let end = map.buffer_snapshot().anchor_after(position);
4517 while let Some(existing_state) = this.autoclose_regions.get(i) {
4518 match existing_state
4519 .range
4520 .start
4521 .cmp(&start, map.buffer_snapshot())
4522 {
4523 Ordering::Less => i += 1,
4524 Ordering::Greater => break,
4525 Ordering::Equal => {
4526 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4527 Ordering::Less => i += 1,
4528 Ordering::Equal => break,
4529 Ordering::Greater => break,
4530 }
4531 }
4532 }
4533 }
4534 this.autoclose_regions.insert(
4535 i,
4536 AutocloseRegion {
4537 selection_id,
4538 range: start..end,
4539 pair,
4540 },
4541 );
4542 }
4543
4544 let had_active_edit_prediction = this.has_active_edit_prediction();
4545 this.change_selections(
4546 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4547 window,
4548 cx,
4549 |s| s.select(new_selections),
4550 );
4551
4552 if !bracket_inserted
4553 && let Some(on_type_format_task) =
4554 this.trigger_on_type_formatting(text.to_string(), window, cx)
4555 {
4556 on_type_format_task.detach_and_log_err(cx);
4557 }
4558
4559 let editor_settings = EditorSettings::get_global(cx);
4560 if bracket_inserted
4561 && (editor_settings.auto_signature_help
4562 || editor_settings.show_signature_help_after_edits)
4563 {
4564 this.show_signature_help(&ShowSignatureHelp, window, cx);
4565 }
4566
4567 let trigger_in_words =
4568 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4569 if this.hard_wrap.is_some() {
4570 let latest: Range<Point> = this.selections.newest(&map).range();
4571 if latest.is_empty()
4572 && this
4573 .buffer()
4574 .read(cx)
4575 .snapshot(cx)
4576 .line_len(MultiBufferRow(latest.start.row))
4577 == latest.start.column
4578 {
4579 this.rewrap_impl(
4580 RewrapOptions {
4581 override_language_settings: true,
4582 preserve_existing_whitespace: true,
4583 },
4584 cx,
4585 )
4586 }
4587 }
4588 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4589 refresh_linked_ranges(this, window, cx);
4590 this.refresh_edit_prediction(true, false, window, cx);
4591 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4592 });
4593 }
4594
4595 fn find_possible_emoji_shortcode_at_position(
4596 snapshot: &MultiBufferSnapshot,
4597 position: Point,
4598 ) -> Option<String> {
4599 let mut chars = Vec::new();
4600 let mut found_colon = false;
4601 for char in snapshot.reversed_chars_at(position).take(100) {
4602 // Found a possible emoji shortcode in the middle of the buffer
4603 if found_colon {
4604 if char.is_whitespace() {
4605 chars.reverse();
4606 return Some(chars.iter().collect());
4607 }
4608 // If the previous character is not a whitespace, we are in the middle of a word
4609 // and we only want to complete the shortcode if the word is made up of other emojis
4610 let mut containing_word = String::new();
4611 for ch in snapshot
4612 .reversed_chars_at(position)
4613 .skip(chars.len() + 1)
4614 .take(100)
4615 {
4616 if ch.is_whitespace() {
4617 break;
4618 }
4619 containing_word.push(ch);
4620 }
4621 let containing_word = containing_word.chars().rev().collect::<String>();
4622 if util::word_consists_of_emojis(containing_word.as_str()) {
4623 chars.reverse();
4624 return Some(chars.iter().collect());
4625 }
4626 }
4627
4628 if char.is_whitespace() || !char.is_ascii() {
4629 return None;
4630 }
4631 if char == ':' {
4632 found_colon = true;
4633 } else {
4634 chars.push(char);
4635 }
4636 }
4637 // Found a possible emoji shortcode at the beginning of the buffer
4638 chars.reverse();
4639 Some(chars.iter().collect())
4640 }
4641
4642 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4643 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4644 self.transact(window, cx, |this, window, cx| {
4645 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4646 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4647 let multi_buffer = this.buffer.read(cx);
4648 let buffer = multi_buffer.snapshot(cx);
4649 selections
4650 .iter()
4651 .map(|selection| {
4652 let start_point = selection.start.to_point(&buffer);
4653 let mut existing_indent =
4654 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4655 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4656 let start = selection.start;
4657 let end = selection.end;
4658 let selection_is_empty = start == end;
4659 let language_scope = buffer.language_scope_at(start);
4660 let (
4661 comment_delimiter,
4662 doc_delimiter,
4663 insert_extra_newline,
4664 indent_on_newline,
4665 indent_on_extra_newline,
4666 ) = if let Some(language) = &language_scope {
4667 let mut insert_extra_newline =
4668 insert_extra_newline_brackets(&buffer, start..end, language)
4669 || insert_extra_newline_tree_sitter(&buffer, start..end);
4670
4671 // Comment extension on newline is allowed only for cursor selections
4672 let comment_delimiter = maybe!({
4673 if !selection_is_empty {
4674 return None;
4675 }
4676
4677 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4678 return None;
4679 }
4680
4681 let delimiters = language.line_comment_prefixes();
4682 let max_len_of_delimiter =
4683 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4684 let (snapshot, range) =
4685 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4686
4687 let num_of_whitespaces = snapshot
4688 .chars_for_range(range.clone())
4689 .take_while(|c| c.is_whitespace())
4690 .count();
4691 let comment_candidate = snapshot
4692 .chars_for_range(range.clone())
4693 .skip(num_of_whitespaces)
4694 .take(max_len_of_delimiter)
4695 .collect::<String>();
4696 let (delimiter, trimmed_len) = delimiters
4697 .iter()
4698 .filter_map(|delimiter| {
4699 let prefix = delimiter.trim_end();
4700 if comment_candidate.starts_with(prefix) {
4701 Some((delimiter, prefix.len()))
4702 } else {
4703 None
4704 }
4705 })
4706 .max_by_key(|(_, len)| *len)?;
4707
4708 if let Some(BlockCommentConfig {
4709 start: block_start, ..
4710 }) = language.block_comment()
4711 {
4712 let block_start_trimmed = block_start.trim_end();
4713 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4714 let line_content = snapshot
4715 .chars_for_range(range)
4716 .skip(num_of_whitespaces)
4717 .take(block_start_trimmed.len())
4718 .collect::<String>();
4719
4720 if line_content.starts_with(block_start_trimmed) {
4721 return None;
4722 }
4723 }
4724 }
4725
4726 let cursor_is_placed_after_comment_marker =
4727 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4728 if cursor_is_placed_after_comment_marker {
4729 Some(delimiter.clone())
4730 } else {
4731 None
4732 }
4733 });
4734
4735 let mut indent_on_newline = IndentSize::spaces(0);
4736 let mut indent_on_extra_newline = IndentSize::spaces(0);
4737
4738 let doc_delimiter = maybe!({
4739 if !selection_is_empty {
4740 return None;
4741 }
4742
4743 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4744 return None;
4745 }
4746
4747 let BlockCommentConfig {
4748 start: start_tag,
4749 end: end_tag,
4750 prefix: delimiter,
4751 tab_size: len,
4752 } = language.documentation_comment()?;
4753 let is_within_block_comment = buffer
4754 .language_scope_at(start_point)
4755 .is_some_and(|scope| scope.override_name() == Some("comment"));
4756 if !is_within_block_comment {
4757 return None;
4758 }
4759
4760 let (snapshot, range) =
4761 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4762
4763 let num_of_whitespaces = snapshot
4764 .chars_for_range(range.clone())
4765 .take_while(|c| c.is_whitespace())
4766 .count();
4767
4768 // 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.
4769 let column = start_point.column;
4770 let cursor_is_after_start_tag = {
4771 let start_tag_len = start_tag.len();
4772 let start_tag_line = snapshot
4773 .chars_for_range(range.clone())
4774 .skip(num_of_whitespaces)
4775 .take(start_tag_len)
4776 .collect::<String>();
4777 if start_tag_line.starts_with(start_tag.as_ref()) {
4778 num_of_whitespaces + start_tag_len <= column as usize
4779 } else {
4780 false
4781 }
4782 };
4783
4784 let cursor_is_after_delimiter = {
4785 let delimiter_trim = delimiter.trim_end();
4786 let delimiter_line = snapshot
4787 .chars_for_range(range.clone())
4788 .skip(num_of_whitespaces)
4789 .take(delimiter_trim.len())
4790 .collect::<String>();
4791 if delimiter_line.starts_with(delimiter_trim) {
4792 num_of_whitespaces + delimiter_trim.len() <= column as usize
4793 } else {
4794 false
4795 }
4796 };
4797
4798 let cursor_is_before_end_tag_if_exists = {
4799 let mut char_position = 0u32;
4800 let mut end_tag_offset = None;
4801
4802 'outer: for chunk in snapshot.text_for_range(range) {
4803 if let Some(byte_pos) = chunk.find(&**end_tag) {
4804 let chars_before_match =
4805 chunk[..byte_pos].chars().count() as u32;
4806 end_tag_offset =
4807 Some(char_position + chars_before_match);
4808 break 'outer;
4809 }
4810 char_position += chunk.chars().count() as u32;
4811 }
4812
4813 if let Some(end_tag_offset) = end_tag_offset {
4814 let cursor_is_before_end_tag = column <= end_tag_offset;
4815 if cursor_is_after_start_tag {
4816 if cursor_is_before_end_tag {
4817 insert_extra_newline = true;
4818 }
4819 let cursor_is_at_start_of_end_tag =
4820 column == end_tag_offset;
4821 if cursor_is_at_start_of_end_tag {
4822 indent_on_extra_newline.len = *len;
4823 }
4824 }
4825 cursor_is_before_end_tag
4826 } else {
4827 true
4828 }
4829 };
4830
4831 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4832 && cursor_is_before_end_tag_if_exists
4833 {
4834 if cursor_is_after_start_tag {
4835 indent_on_newline.len = *len;
4836 }
4837 Some(delimiter.clone())
4838 } else {
4839 None
4840 }
4841 });
4842
4843 (
4844 comment_delimiter,
4845 doc_delimiter,
4846 insert_extra_newline,
4847 indent_on_newline,
4848 indent_on_extra_newline,
4849 )
4850 } else {
4851 (
4852 None,
4853 None,
4854 false,
4855 IndentSize::default(),
4856 IndentSize::default(),
4857 )
4858 };
4859
4860 let prevent_auto_indent = doc_delimiter.is_some();
4861 let delimiter = comment_delimiter.or(doc_delimiter);
4862
4863 let capacity_for_delimiter =
4864 delimiter.as_deref().map(str::len).unwrap_or_default();
4865 let mut new_text = String::with_capacity(
4866 1 + capacity_for_delimiter
4867 + existing_indent.len as usize
4868 + indent_on_newline.len as usize
4869 + indent_on_extra_newline.len as usize,
4870 );
4871 new_text.push('\n');
4872 new_text.extend(existing_indent.chars());
4873 new_text.extend(indent_on_newline.chars());
4874
4875 if let Some(delimiter) = &delimiter {
4876 new_text.push_str(delimiter);
4877 }
4878
4879 if insert_extra_newline {
4880 new_text.push('\n');
4881 new_text.extend(existing_indent.chars());
4882 new_text.extend(indent_on_extra_newline.chars());
4883 }
4884
4885 let anchor = buffer.anchor_after(end);
4886 let new_selection = selection.map(|_| anchor);
4887 (
4888 ((start..end, new_text), prevent_auto_indent),
4889 (insert_extra_newline, new_selection),
4890 )
4891 })
4892 .unzip()
4893 };
4894
4895 let mut auto_indent_edits = Vec::new();
4896 let mut edits = Vec::new();
4897 for (edit, prevent_auto_indent) in edits_with_flags {
4898 if prevent_auto_indent {
4899 edits.push(edit);
4900 } else {
4901 auto_indent_edits.push(edit);
4902 }
4903 }
4904 if !edits.is_empty() {
4905 this.edit(edits, cx);
4906 }
4907 if !auto_indent_edits.is_empty() {
4908 this.edit_with_autoindent(auto_indent_edits, cx);
4909 }
4910
4911 let buffer = this.buffer.read(cx).snapshot(cx);
4912 let new_selections = selection_info
4913 .into_iter()
4914 .map(|(extra_newline_inserted, new_selection)| {
4915 let mut cursor = new_selection.end.to_point(&buffer);
4916 if extra_newline_inserted {
4917 cursor.row -= 1;
4918 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4919 }
4920 new_selection.map(|_| cursor)
4921 })
4922 .collect();
4923
4924 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4925 this.refresh_edit_prediction(true, false, window, cx);
4926 });
4927 }
4928
4929 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4930 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4931
4932 let buffer = self.buffer.read(cx);
4933 let snapshot = buffer.snapshot(cx);
4934
4935 let mut edits = Vec::new();
4936 let mut rows = Vec::new();
4937
4938 for (rows_inserted, selection) in self
4939 .selections
4940 .all_adjusted(&self.display_snapshot(cx))
4941 .into_iter()
4942 .enumerate()
4943 {
4944 let cursor = selection.head();
4945 let row = cursor.row;
4946
4947 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4948
4949 let newline = "\n".to_string();
4950 edits.push((start_of_line..start_of_line, newline));
4951
4952 rows.push(row + rows_inserted as u32);
4953 }
4954
4955 self.transact(window, cx, |editor, window, cx| {
4956 editor.edit(edits, cx);
4957
4958 editor.change_selections(Default::default(), window, cx, |s| {
4959 let mut index = 0;
4960 s.move_cursors_with(|map, _, _| {
4961 let row = rows[index];
4962 index += 1;
4963
4964 let point = Point::new(row, 0);
4965 let boundary = map.next_line_boundary(point).1;
4966 let clipped = map.clip_point(boundary, Bias::Left);
4967
4968 (clipped, SelectionGoal::None)
4969 });
4970 });
4971
4972 let mut indent_edits = Vec::new();
4973 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4974 for row in rows {
4975 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4976 for (row, indent) in indents {
4977 if indent.len == 0 {
4978 continue;
4979 }
4980
4981 let text = match indent.kind {
4982 IndentKind::Space => " ".repeat(indent.len as usize),
4983 IndentKind::Tab => "\t".repeat(indent.len as usize),
4984 };
4985 let point = Point::new(row.0, 0);
4986 indent_edits.push((point..point, text));
4987 }
4988 }
4989 editor.edit(indent_edits, cx);
4990 });
4991 }
4992
4993 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4994 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4995
4996 let buffer = self.buffer.read(cx);
4997 let snapshot = buffer.snapshot(cx);
4998
4999 let mut edits = Vec::new();
5000 let mut rows = Vec::new();
5001 let mut rows_inserted = 0;
5002
5003 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5004 let cursor = selection.head();
5005 let row = cursor.row;
5006
5007 let point = Point::new(row + 1, 0);
5008 let start_of_line = snapshot.clip_point(point, Bias::Left);
5009
5010 let newline = "\n".to_string();
5011 edits.push((start_of_line..start_of_line, newline));
5012
5013 rows_inserted += 1;
5014 rows.push(row + rows_inserted);
5015 }
5016
5017 self.transact(window, cx, |editor, window, cx| {
5018 editor.edit(edits, cx);
5019
5020 editor.change_selections(Default::default(), window, cx, |s| {
5021 let mut index = 0;
5022 s.move_cursors_with(|map, _, _| {
5023 let row = rows[index];
5024 index += 1;
5025
5026 let point = Point::new(row, 0);
5027 let boundary = map.next_line_boundary(point).1;
5028 let clipped = map.clip_point(boundary, Bias::Left);
5029
5030 (clipped, SelectionGoal::None)
5031 });
5032 });
5033
5034 let mut indent_edits = Vec::new();
5035 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5036 for row in rows {
5037 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5038 for (row, indent) in indents {
5039 if indent.len == 0 {
5040 continue;
5041 }
5042
5043 let text = match indent.kind {
5044 IndentKind::Space => " ".repeat(indent.len as usize),
5045 IndentKind::Tab => "\t".repeat(indent.len as usize),
5046 };
5047 let point = Point::new(row.0, 0);
5048 indent_edits.push((point..point, text));
5049 }
5050 }
5051 editor.edit(indent_edits, cx);
5052 });
5053 }
5054
5055 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5056 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5057 original_indent_columns: Vec::new(),
5058 });
5059 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5060 }
5061
5062 fn insert_with_autoindent_mode(
5063 &mut self,
5064 text: &str,
5065 autoindent_mode: Option<AutoindentMode>,
5066 window: &mut Window,
5067 cx: &mut Context<Self>,
5068 ) {
5069 if self.read_only(cx) {
5070 return;
5071 }
5072
5073 let text: Arc<str> = text.into();
5074 self.transact(window, cx, |this, window, cx| {
5075 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5076 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5077 let anchors = {
5078 let snapshot = buffer.read(cx);
5079 old_selections
5080 .iter()
5081 .map(|s| {
5082 let anchor = snapshot.anchor_after(s.head());
5083 s.map(|_| anchor)
5084 })
5085 .collect::<Vec<_>>()
5086 };
5087 buffer.edit(
5088 old_selections
5089 .iter()
5090 .map(|s| (s.start..s.end, text.clone())),
5091 autoindent_mode,
5092 cx,
5093 );
5094 anchors
5095 });
5096
5097 this.change_selections(Default::default(), window, cx, |s| {
5098 s.select_anchors(selection_anchors);
5099 });
5100
5101 cx.notify();
5102 });
5103 }
5104
5105 fn trigger_completion_on_input(
5106 &mut self,
5107 text: &str,
5108 trigger_in_words: bool,
5109 window: &mut Window,
5110 cx: &mut Context<Self>,
5111 ) {
5112 let completions_source = self
5113 .context_menu
5114 .borrow()
5115 .as_ref()
5116 .and_then(|menu| match menu {
5117 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5118 CodeContextMenu::CodeActions(_) => None,
5119 });
5120
5121 match completions_source {
5122 Some(CompletionsMenuSource::Words { .. }) => {
5123 self.open_or_update_completions_menu(
5124 Some(CompletionsMenuSource::Words {
5125 ignore_threshold: false,
5126 }),
5127 None,
5128 trigger_in_words,
5129 window,
5130 cx,
5131 );
5132 }
5133 _ => self.open_or_update_completions_menu(
5134 None,
5135 Some(text.to_owned()).filter(|x| !x.is_empty()),
5136 true,
5137 window,
5138 cx,
5139 ),
5140 }
5141 }
5142
5143 /// If any empty selections is touching the start of its innermost containing autoclose
5144 /// region, expand it to select the brackets.
5145 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5146 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5147 let buffer = self.buffer.read(cx).read(cx);
5148 let new_selections = self
5149 .selections_with_autoclose_regions(selections, &buffer)
5150 .map(|(mut selection, region)| {
5151 if !selection.is_empty() {
5152 return selection;
5153 }
5154
5155 if let Some(region) = region {
5156 let mut range = region.range.to_offset(&buffer);
5157 if selection.start == range.start && range.start >= region.pair.start.len() {
5158 range.start -= region.pair.start.len();
5159 if buffer.contains_str_at(range.start, ®ion.pair.start)
5160 && buffer.contains_str_at(range.end, ®ion.pair.end)
5161 {
5162 range.end += region.pair.end.len();
5163 selection.start = range.start;
5164 selection.end = range.end;
5165
5166 return selection;
5167 }
5168 }
5169 }
5170
5171 let always_treat_brackets_as_autoclosed = buffer
5172 .language_settings_at(selection.start, cx)
5173 .always_treat_brackets_as_autoclosed;
5174
5175 if !always_treat_brackets_as_autoclosed {
5176 return selection;
5177 }
5178
5179 if let Some(scope) = buffer.language_scope_at(selection.start) {
5180 for (pair, enabled) in scope.brackets() {
5181 if !enabled || !pair.close {
5182 continue;
5183 }
5184
5185 if buffer.contains_str_at(selection.start, &pair.end) {
5186 let pair_start_len = pair.start.len();
5187 if buffer.contains_str_at(
5188 selection.start.saturating_sub(pair_start_len),
5189 &pair.start,
5190 ) {
5191 selection.start -= pair_start_len;
5192 selection.end += pair.end.len();
5193
5194 return selection;
5195 }
5196 }
5197 }
5198 }
5199
5200 selection
5201 })
5202 .collect();
5203
5204 drop(buffer);
5205 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5206 selections.select(new_selections)
5207 });
5208 }
5209
5210 /// Iterate the given selections, and for each one, find the smallest surrounding
5211 /// autoclose region. This uses the ordering of the selections and the autoclose
5212 /// regions to avoid repeated comparisons.
5213 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5214 &'a self,
5215 selections: impl IntoIterator<Item = Selection<D>>,
5216 buffer: &'a MultiBufferSnapshot,
5217 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5218 let mut i = 0;
5219 let mut regions = self.autoclose_regions.as_slice();
5220 selections.into_iter().map(move |selection| {
5221 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5222
5223 let mut enclosing = None;
5224 while let Some(pair_state) = regions.get(i) {
5225 if pair_state.range.end.to_offset(buffer) < range.start {
5226 regions = ®ions[i + 1..];
5227 i = 0;
5228 } else if pair_state.range.start.to_offset(buffer) > range.end {
5229 break;
5230 } else {
5231 if pair_state.selection_id == selection.id {
5232 enclosing = Some(pair_state);
5233 }
5234 i += 1;
5235 }
5236 }
5237
5238 (selection, enclosing)
5239 })
5240 }
5241
5242 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5243 fn invalidate_autoclose_regions(
5244 &mut self,
5245 mut selections: &[Selection<Anchor>],
5246 buffer: &MultiBufferSnapshot,
5247 ) {
5248 self.autoclose_regions.retain(|state| {
5249 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5250 return false;
5251 }
5252
5253 let mut i = 0;
5254 while let Some(selection) = selections.get(i) {
5255 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5256 selections = &selections[1..];
5257 continue;
5258 }
5259 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5260 break;
5261 }
5262 if selection.id == state.selection_id {
5263 return true;
5264 } else {
5265 i += 1;
5266 }
5267 }
5268 false
5269 });
5270 }
5271
5272 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5273 let offset = position.to_offset(buffer);
5274 let (word_range, kind) =
5275 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5276 if offset > word_range.start && kind == Some(CharKind::Word) {
5277 Some(
5278 buffer
5279 .text_for_range(word_range.start..offset)
5280 .collect::<String>(),
5281 )
5282 } else {
5283 None
5284 }
5285 }
5286
5287 pub fn visible_excerpts(
5288 &self,
5289 cx: &mut Context<Editor>,
5290 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5291 let Some(project) = self.project() else {
5292 return HashMap::default();
5293 };
5294 let project = project.read(cx);
5295 let multi_buffer = self.buffer().read(cx);
5296 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5297 let multi_buffer_visible_start = self
5298 .scroll_manager
5299 .anchor()
5300 .anchor
5301 .to_point(&multi_buffer_snapshot);
5302 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5303 multi_buffer_visible_start
5304 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5305 Bias::Left,
5306 );
5307 multi_buffer_snapshot
5308 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5309 .into_iter()
5310 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5311 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5312 let buffer_file = project::File::from_dyn(buffer.file())?;
5313 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5314 let worktree_entry = buffer_worktree
5315 .read(cx)
5316 .entry_for_id(buffer_file.project_entry_id()?)?;
5317 if worktree_entry.is_ignored {
5318 None
5319 } else {
5320 Some((
5321 excerpt_id,
5322 (
5323 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5324 buffer.version().clone(),
5325 excerpt_visible_range,
5326 ),
5327 ))
5328 }
5329 })
5330 .collect()
5331 }
5332
5333 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5334 TextLayoutDetails {
5335 text_system: window.text_system().clone(),
5336 editor_style: self.style.clone().unwrap(),
5337 rem_size: window.rem_size(),
5338 scroll_anchor: self.scroll_manager.anchor(),
5339 visible_rows: self.visible_line_count(),
5340 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5341 }
5342 }
5343
5344 fn trigger_on_type_formatting(
5345 &self,
5346 input: String,
5347 window: &mut Window,
5348 cx: &mut Context<Self>,
5349 ) -> Option<Task<Result<()>>> {
5350 if input.len() != 1 {
5351 return None;
5352 }
5353
5354 let project = self.project()?;
5355 let position = self.selections.newest_anchor().head();
5356 let (buffer, buffer_position) = self
5357 .buffer
5358 .read(cx)
5359 .text_anchor_for_position(position, cx)?;
5360
5361 let settings = language_settings::language_settings(
5362 buffer
5363 .read(cx)
5364 .language_at(buffer_position)
5365 .map(|l| l.name()),
5366 buffer.read(cx).file(),
5367 cx,
5368 );
5369 if !settings.use_on_type_format {
5370 return None;
5371 }
5372
5373 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5374 // hence we do LSP request & edit on host side only — add formats to host's history.
5375 let push_to_lsp_host_history = true;
5376 // If this is not the host, append its history with new edits.
5377 let push_to_client_history = project.read(cx).is_via_collab();
5378
5379 let on_type_formatting = project.update(cx, |project, cx| {
5380 project.on_type_format(
5381 buffer.clone(),
5382 buffer_position,
5383 input,
5384 push_to_lsp_host_history,
5385 cx,
5386 )
5387 });
5388 Some(cx.spawn_in(window, async move |editor, cx| {
5389 if let Some(transaction) = on_type_formatting.await? {
5390 if push_to_client_history {
5391 buffer
5392 .update(cx, |buffer, _| {
5393 buffer.push_transaction(transaction, Instant::now());
5394 buffer.finalize_last_transaction();
5395 })
5396 .ok();
5397 }
5398 editor.update(cx, |editor, cx| {
5399 editor.refresh_document_highlights(cx);
5400 })?;
5401 }
5402 Ok(())
5403 }))
5404 }
5405
5406 pub fn show_word_completions(
5407 &mut self,
5408 _: &ShowWordCompletions,
5409 window: &mut Window,
5410 cx: &mut Context<Self>,
5411 ) {
5412 self.open_or_update_completions_menu(
5413 Some(CompletionsMenuSource::Words {
5414 ignore_threshold: true,
5415 }),
5416 None,
5417 false,
5418 window,
5419 cx,
5420 );
5421 }
5422
5423 pub fn show_completions(
5424 &mut self,
5425 _: &ShowCompletions,
5426 window: &mut Window,
5427 cx: &mut Context<Self>,
5428 ) {
5429 self.open_or_update_completions_menu(None, None, false, window, cx);
5430 }
5431
5432 fn open_or_update_completions_menu(
5433 &mut self,
5434 requested_source: Option<CompletionsMenuSource>,
5435 trigger: Option<String>,
5436 trigger_in_words: bool,
5437 window: &mut Window,
5438 cx: &mut Context<Self>,
5439 ) {
5440 if self.pending_rename.is_some() {
5441 return;
5442 }
5443
5444 let completions_source = self
5445 .context_menu
5446 .borrow()
5447 .as_ref()
5448 .and_then(|menu| match menu {
5449 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5450 CodeContextMenu::CodeActions(_) => None,
5451 });
5452
5453 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5454
5455 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5456 // inserted and selected. To handle that case, the start of the selection is used so that
5457 // the menu starts with all choices.
5458 let position = self
5459 .selections
5460 .newest_anchor()
5461 .start
5462 .bias_right(&multibuffer_snapshot);
5463 if position.diff_base_anchor.is_some() {
5464 return;
5465 }
5466 let buffer_position = multibuffer_snapshot.anchor_before(position);
5467 let Some(buffer) = buffer_position
5468 .buffer_id
5469 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5470 else {
5471 return;
5472 };
5473 let buffer_snapshot = buffer.read(cx).snapshot();
5474
5475 let query: Option<Arc<String>> =
5476 Self::completion_query(&multibuffer_snapshot, buffer_position)
5477 .map(|query| query.into());
5478
5479 drop(multibuffer_snapshot);
5480
5481 // Hide the current completions menu when query is empty. Without this, cached
5482 // completions from before the trigger char may be reused (#32774).
5483 if query.is_none() {
5484 let menu_is_open = matches!(
5485 self.context_menu.borrow().as_ref(),
5486 Some(CodeContextMenu::Completions(_))
5487 );
5488 if menu_is_open {
5489 self.hide_context_menu(window, cx);
5490 }
5491 }
5492
5493 let mut ignore_word_threshold = false;
5494 let provider = match requested_source {
5495 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5496 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5497 ignore_word_threshold = ignore_threshold;
5498 None
5499 }
5500 Some(CompletionsMenuSource::SnippetChoices)
5501 | Some(CompletionsMenuSource::SnippetsOnly) => {
5502 log::error!("bug: SnippetChoices requested_source is not handled");
5503 None
5504 }
5505 };
5506
5507 let sort_completions = provider
5508 .as_ref()
5509 .is_some_and(|provider| provider.sort_completions());
5510
5511 let filter_completions = provider
5512 .as_ref()
5513 .is_none_or(|provider| provider.filter_completions());
5514
5515 let was_snippets_only = matches!(
5516 completions_source,
5517 Some(CompletionsMenuSource::SnippetsOnly)
5518 );
5519
5520 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5521 if filter_completions {
5522 menu.filter(
5523 query.clone().unwrap_or_default(),
5524 buffer_position.text_anchor,
5525 &buffer,
5526 provider.clone(),
5527 window,
5528 cx,
5529 );
5530 }
5531 // When `is_incomplete` is false, no need to re-query completions when the current query
5532 // is a suffix of the initial query.
5533 let was_complete = !menu.is_incomplete;
5534 if was_complete && !was_snippets_only {
5535 // If the new query is a suffix of the old query (typing more characters) and
5536 // the previous result was complete, the existing completions can be filtered.
5537 //
5538 // Note that snippet completions are always complete.
5539 let query_matches = match (&menu.initial_query, &query) {
5540 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5541 (None, _) => true,
5542 _ => false,
5543 };
5544 if query_matches {
5545 let position_matches = if menu.initial_position == position {
5546 true
5547 } else {
5548 let snapshot = self.buffer.read(cx).read(cx);
5549 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5550 };
5551 if position_matches {
5552 return;
5553 }
5554 }
5555 }
5556 };
5557
5558 let Anchor {
5559 excerpt_id: buffer_excerpt_id,
5560 text_anchor: buffer_position,
5561 ..
5562 } = buffer_position;
5563
5564 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5565 buffer_snapshot.surrounding_word(buffer_position, None)
5566 {
5567 let word_to_exclude = buffer_snapshot
5568 .text_for_range(word_range.clone())
5569 .collect::<String>();
5570 (
5571 buffer_snapshot.anchor_before(word_range.start)
5572 ..buffer_snapshot.anchor_after(buffer_position),
5573 Some(word_to_exclude),
5574 )
5575 } else {
5576 (buffer_position..buffer_position, None)
5577 };
5578
5579 let language = buffer_snapshot
5580 .language_at(buffer_position)
5581 .map(|language| language.name());
5582
5583 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5584 .completions
5585 .clone();
5586
5587 let show_completion_documentation = buffer_snapshot
5588 .settings_at(buffer_position, cx)
5589 .show_completion_documentation;
5590
5591 // The document can be large, so stay in reasonable bounds when searching for words,
5592 // otherwise completion pop-up might be slow to appear.
5593 const WORD_LOOKUP_ROWS: u32 = 5_000;
5594 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5595 let min_word_search = buffer_snapshot.clip_point(
5596 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5597 Bias::Left,
5598 );
5599 let max_word_search = buffer_snapshot.clip_point(
5600 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5601 Bias::Right,
5602 );
5603 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5604 ..buffer_snapshot.point_to_offset(max_word_search);
5605
5606 let skip_digits = query
5607 .as_ref()
5608 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5609
5610 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5611 trigger.as_ref().is_none_or(|trigger| {
5612 provider.is_completion_trigger(
5613 &buffer,
5614 position.text_anchor,
5615 trigger,
5616 trigger_in_words,
5617 completions_source.is_some(),
5618 cx,
5619 )
5620 })
5621 });
5622
5623 let provider_responses = if let Some(provider) = &provider
5624 && load_provider_completions
5625 {
5626 let trigger_character =
5627 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5628 let completion_context = CompletionContext {
5629 trigger_kind: match &trigger_character {
5630 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5631 None => CompletionTriggerKind::INVOKED,
5632 },
5633 trigger_character,
5634 };
5635
5636 provider.completions(
5637 buffer_excerpt_id,
5638 &buffer,
5639 buffer_position,
5640 completion_context,
5641 window,
5642 cx,
5643 )
5644 } else {
5645 Task::ready(Ok(Vec::new()))
5646 };
5647
5648 let load_word_completions = if !self.word_completions_enabled {
5649 false
5650 } else if requested_source
5651 == Some(CompletionsMenuSource::Words {
5652 ignore_threshold: true,
5653 })
5654 {
5655 true
5656 } else {
5657 load_provider_completions
5658 && completion_settings.words != WordsCompletionMode::Disabled
5659 && (ignore_word_threshold || {
5660 let words_min_length = completion_settings.words_min_length;
5661 // check whether word has at least `words_min_length` characters
5662 let query_chars = query.iter().flat_map(|q| q.chars());
5663 query_chars.take(words_min_length).count() == words_min_length
5664 })
5665 };
5666
5667 let mut words = if load_word_completions {
5668 cx.background_spawn({
5669 let buffer_snapshot = buffer_snapshot.clone();
5670 async move {
5671 buffer_snapshot.words_in_range(WordsQuery {
5672 fuzzy_contents: None,
5673 range: word_search_range,
5674 skip_digits,
5675 })
5676 }
5677 })
5678 } else {
5679 Task::ready(BTreeMap::default())
5680 };
5681
5682 let snippets = if let Some(provider) = &provider
5683 && provider.show_snippets()
5684 && let Some(project) = self.project()
5685 {
5686 let char_classifier = buffer_snapshot
5687 .char_classifier_at(buffer_position)
5688 .scope_context(Some(CharScopeContext::Completion));
5689 project.update(cx, |project, cx| {
5690 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5691 })
5692 } else {
5693 Task::ready(Ok(CompletionResponse {
5694 completions: Vec::new(),
5695 display_options: Default::default(),
5696 is_incomplete: false,
5697 }))
5698 };
5699
5700 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5701
5702 let id = post_inc(&mut self.next_completion_id);
5703 let task = cx.spawn_in(window, async move |editor, cx| {
5704 let Ok(()) = editor.update(cx, |this, _| {
5705 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5706 }) else {
5707 return;
5708 };
5709
5710 // TODO: Ideally completions from different sources would be selectively re-queried, so
5711 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5712 let mut completions = Vec::new();
5713 let mut is_incomplete = false;
5714 let mut display_options: Option<CompletionDisplayOptions> = None;
5715 if let Some(provider_responses) = provider_responses.await.log_err()
5716 && !provider_responses.is_empty()
5717 {
5718 for response in provider_responses {
5719 completions.extend(response.completions);
5720 is_incomplete = is_incomplete || response.is_incomplete;
5721 match display_options.as_mut() {
5722 None => {
5723 display_options = Some(response.display_options);
5724 }
5725 Some(options) => options.merge(&response.display_options),
5726 }
5727 }
5728 if completion_settings.words == WordsCompletionMode::Fallback {
5729 words = Task::ready(BTreeMap::default());
5730 }
5731 }
5732 let display_options = display_options.unwrap_or_default();
5733
5734 let mut words = words.await;
5735 if let Some(word_to_exclude) = &word_to_exclude {
5736 words.remove(word_to_exclude);
5737 }
5738 for lsp_completion in &completions {
5739 words.remove(&lsp_completion.new_text);
5740 }
5741 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5742 replace_range: word_replace_range.clone(),
5743 new_text: word.clone(),
5744 label: CodeLabel::plain(word, None),
5745 match_start: None,
5746 snippet_deduplication_key: None,
5747 icon_path: None,
5748 documentation: None,
5749 source: CompletionSource::BufferWord {
5750 word_range,
5751 resolved: false,
5752 },
5753 insert_text_mode: Some(InsertTextMode::AS_IS),
5754 confirm: None,
5755 }));
5756
5757 completions.extend(
5758 snippets
5759 .await
5760 .into_iter()
5761 .flat_map(|response| response.completions),
5762 );
5763
5764 let menu = if completions.is_empty() {
5765 None
5766 } else {
5767 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5768 let languages = editor
5769 .workspace
5770 .as_ref()
5771 .and_then(|(workspace, _)| workspace.upgrade())
5772 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5773 let menu = CompletionsMenu::new(
5774 id,
5775 requested_source.unwrap_or(if load_provider_completions {
5776 CompletionsMenuSource::Normal
5777 } else {
5778 CompletionsMenuSource::SnippetsOnly
5779 }),
5780 sort_completions,
5781 show_completion_documentation,
5782 position,
5783 query.clone(),
5784 is_incomplete,
5785 buffer.clone(),
5786 completions.into(),
5787 display_options,
5788 snippet_sort_order,
5789 languages,
5790 language,
5791 cx,
5792 );
5793
5794 let query = if filter_completions { query } else { None };
5795 let matches_task = menu.do_async_filtering(
5796 query.unwrap_or_default(),
5797 buffer_position,
5798 &buffer,
5799 cx,
5800 );
5801 (menu, matches_task)
5802 }) else {
5803 return;
5804 };
5805
5806 let matches = matches_task.await;
5807
5808 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5809 // Newer menu already set, so exit.
5810 if let Some(CodeContextMenu::Completions(prev_menu)) =
5811 editor.context_menu.borrow().as_ref()
5812 && prev_menu.id > id
5813 {
5814 return;
5815 };
5816
5817 // Only valid to take prev_menu because either the new menu is immediately set
5818 // below, or the menu is hidden.
5819 if let Some(CodeContextMenu::Completions(prev_menu)) =
5820 editor.context_menu.borrow_mut().take()
5821 {
5822 let position_matches =
5823 if prev_menu.initial_position == menu.initial_position {
5824 true
5825 } else {
5826 let snapshot = editor.buffer.read(cx).read(cx);
5827 prev_menu.initial_position.to_offset(&snapshot)
5828 == menu.initial_position.to_offset(&snapshot)
5829 };
5830 if position_matches {
5831 // Preserve markdown cache before `set_filter_results` because it will
5832 // try to populate the documentation cache.
5833 menu.preserve_markdown_cache(prev_menu);
5834 }
5835 };
5836
5837 menu.set_filter_results(matches, provider, window, cx);
5838 }) else {
5839 return;
5840 };
5841
5842 menu.visible().then_some(menu)
5843 };
5844
5845 editor
5846 .update_in(cx, |editor, window, cx| {
5847 if editor.focus_handle.is_focused(window)
5848 && let Some(menu) = menu
5849 {
5850 *editor.context_menu.borrow_mut() =
5851 Some(CodeContextMenu::Completions(menu));
5852
5853 crate::hover_popover::hide_hover(editor, cx);
5854 if editor.show_edit_predictions_in_menu() {
5855 editor.update_visible_edit_prediction(window, cx);
5856 } else {
5857 editor.discard_edit_prediction(false, cx);
5858 }
5859
5860 cx.notify();
5861 return;
5862 }
5863
5864 if editor.completion_tasks.len() <= 1 {
5865 // If there are no more completion tasks and the last menu was empty, we should hide it.
5866 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5867 // If it was already hidden and we don't show edit predictions in the menu,
5868 // we should also show the edit prediction when available.
5869 if was_hidden && editor.show_edit_predictions_in_menu() {
5870 editor.update_visible_edit_prediction(window, cx);
5871 }
5872 }
5873 })
5874 .ok();
5875 });
5876
5877 self.completion_tasks.push((id, task));
5878 }
5879
5880 #[cfg(feature = "test-support")]
5881 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5882 let menu = self.context_menu.borrow();
5883 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5884 let completions = menu.completions.borrow();
5885 Some(completions.to_vec())
5886 } else {
5887 None
5888 }
5889 }
5890
5891 pub fn with_completions_menu_matching_id<R>(
5892 &self,
5893 id: CompletionId,
5894 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5895 ) -> R {
5896 let mut context_menu = self.context_menu.borrow_mut();
5897 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5898 return f(None);
5899 };
5900 if completions_menu.id != id {
5901 return f(None);
5902 }
5903 f(Some(completions_menu))
5904 }
5905
5906 pub fn confirm_completion(
5907 &mut self,
5908 action: &ConfirmCompletion,
5909 window: &mut Window,
5910 cx: &mut Context<Self>,
5911 ) -> Option<Task<Result<()>>> {
5912 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5913 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5914 }
5915
5916 pub fn confirm_completion_insert(
5917 &mut self,
5918 _: &ConfirmCompletionInsert,
5919 window: &mut Window,
5920 cx: &mut Context<Self>,
5921 ) -> Option<Task<Result<()>>> {
5922 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5923 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5924 }
5925
5926 pub fn confirm_completion_replace(
5927 &mut self,
5928 _: &ConfirmCompletionReplace,
5929 window: &mut Window,
5930 cx: &mut Context<Self>,
5931 ) -> Option<Task<Result<()>>> {
5932 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5933 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5934 }
5935
5936 pub fn compose_completion(
5937 &mut self,
5938 action: &ComposeCompletion,
5939 window: &mut Window,
5940 cx: &mut Context<Self>,
5941 ) -> Option<Task<Result<()>>> {
5942 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5943 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5944 }
5945
5946 fn do_completion(
5947 &mut self,
5948 item_ix: Option<usize>,
5949 intent: CompletionIntent,
5950 window: &mut Window,
5951 cx: &mut Context<Editor>,
5952 ) -> Option<Task<Result<()>>> {
5953 use language::ToOffset as _;
5954
5955 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5956 else {
5957 return None;
5958 };
5959
5960 let candidate_id = {
5961 let entries = completions_menu.entries.borrow();
5962 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5963 if self.show_edit_predictions_in_menu() {
5964 self.discard_edit_prediction(true, cx);
5965 }
5966 mat.candidate_id
5967 };
5968
5969 let completion = completions_menu
5970 .completions
5971 .borrow()
5972 .get(candidate_id)?
5973 .clone();
5974 cx.stop_propagation();
5975
5976 let buffer_handle = completions_menu.buffer.clone();
5977
5978 let CompletionEdit {
5979 new_text,
5980 snippet,
5981 replace_range,
5982 } = process_completion_for_edit(
5983 &completion,
5984 intent,
5985 &buffer_handle,
5986 &completions_menu.initial_position.text_anchor,
5987 cx,
5988 );
5989
5990 let buffer = buffer_handle.read(cx);
5991 let snapshot = self.buffer.read(cx).snapshot(cx);
5992 let newest_anchor = self.selections.newest_anchor();
5993 let replace_range_multibuffer = {
5994 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5995 excerpt.map_range_from_buffer(replace_range.clone())
5996 };
5997 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5998 return None;
5999 }
6000
6001 let old_text = buffer
6002 .text_for_range(replace_range.clone())
6003 .collect::<String>();
6004 let lookbehind = newest_anchor
6005 .start
6006 .text_anchor
6007 .to_offset(buffer)
6008 .saturating_sub(replace_range.start);
6009 let lookahead = replace_range
6010 .end
6011 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6012 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6013 let suffix = &old_text[lookbehind.min(old_text.len())..];
6014
6015 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
6016 let mut ranges = Vec::new();
6017 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6018
6019 for selection in &selections {
6020 let range = if selection.id == newest_anchor.id {
6021 replace_range_multibuffer.clone()
6022 } else {
6023 let mut range = selection.range();
6024
6025 // if prefix is present, don't duplicate it
6026 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6027 range.start = range.start.saturating_sub(lookbehind);
6028
6029 // if suffix is also present, mimic the newest cursor and replace it
6030 if selection.id != newest_anchor.id
6031 && snapshot.contains_str_at(range.end, suffix)
6032 {
6033 range.end += lookahead;
6034 }
6035 }
6036 range
6037 };
6038
6039 ranges.push(range.clone());
6040
6041 if !self.linked_edit_ranges.is_empty() {
6042 let start_anchor = snapshot.anchor_before(range.start);
6043 let end_anchor = snapshot.anchor_after(range.end);
6044 if let Some(ranges) = self
6045 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6046 {
6047 for (buffer, edits) in ranges {
6048 linked_edits
6049 .entry(buffer.clone())
6050 .or_default()
6051 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6052 }
6053 }
6054 }
6055 }
6056
6057 let common_prefix_len = old_text
6058 .chars()
6059 .zip(new_text.chars())
6060 .take_while(|(a, b)| a == b)
6061 .map(|(a, _)| a.len_utf8())
6062 .sum::<usize>();
6063
6064 cx.emit(EditorEvent::InputHandled {
6065 utf16_range_to_replace: None,
6066 text: new_text[common_prefix_len..].into(),
6067 });
6068
6069 self.transact(window, cx, |editor, window, cx| {
6070 if let Some(mut snippet) = snippet {
6071 snippet.text = new_text.to_string();
6072 editor
6073 .insert_snippet(&ranges, snippet, window, cx)
6074 .log_err();
6075 } else {
6076 editor.buffer.update(cx, |multi_buffer, cx| {
6077 let auto_indent = match completion.insert_text_mode {
6078 Some(InsertTextMode::AS_IS) => None,
6079 _ => editor.autoindent_mode.clone(),
6080 };
6081 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6082 multi_buffer.edit(edits, auto_indent, cx);
6083 });
6084 }
6085 for (buffer, edits) in linked_edits {
6086 buffer.update(cx, |buffer, cx| {
6087 let snapshot = buffer.snapshot();
6088 let edits = edits
6089 .into_iter()
6090 .map(|(range, text)| {
6091 use text::ToPoint as TP;
6092 let end_point = TP::to_point(&range.end, &snapshot);
6093 let start_point = TP::to_point(&range.start, &snapshot);
6094 (start_point..end_point, text)
6095 })
6096 .sorted_by_key(|(range, _)| range.start);
6097 buffer.edit(edits, None, cx);
6098 })
6099 }
6100
6101 editor.refresh_edit_prediction(true, false, window, cx);
6102 });
6103 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6104
6105 let show_new_completions_on_confirm = completion
6106 .confirm
6107 .as_ref()
6108 .is_some_and(|confirm| confirm(intent, window, cx));
6109 if show_new_completions_on_confirm {
6110 self.open_or_update_completions_menu(None, None, false, window, cx);
6111 }
6112
6113 let provider = self.completion_provider.as_ref()?;
6114 drop(completion);
6115 let apply_edits = provider.apply_additional_edits_for_completion(
6116 buffer_handle,
6117 completions_menu.completions.clone(),
6118 candidate_id,
6119 true,
6120 cx,
6121 );
6122
6123 let editor_settings = EditorSettings::get_global(cx);
6124 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6125 // After the code completion is finished, users often want to know what signatures are needed.
6126 // so we should automatically call signature_help
6127 self.show_signature_help(&ShowSignatureHelp, window, cx);
6128 }
6129
6130 Some(cx.foreground_executor().spawn(async move {
6131 apply_edits.await?;
6132 Ok(())
6133 }))
6134 }
6135
6136 pub fn toggle_code_actions(
6137 &mut self,
6138 action: &ToggleCodeActions,
6139 window: &mut Window,
6140 cx: &mut Context<Self>,
6141 ) {
6142 let quick_launch = action.quick_launch;
6143 let mut context_menu = self.context_menu.borrow_mut();
6144 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6145 if code_actions.deployed_from == action.deployed_from {
6146 // Toggle if we're selecting the same one
6147 *context_menu = None;
6148 cx.notify();
6149 return;
6150 } else {
6151 // Otherwise, clear it and start a new one
6152 *context_menu = None;
6153 cx.notify();
6154 }
6155 }
6156 drop(context_menu);
6157 let snapshot = self.snapshot(window, cx);
6158 let deployed_from = action.deployed_from.clone();
6159 let action = action.clone();
6160 self.completion_tasks.clear();
6161 self.discard_edit_prediction(false, cx);
6162
6163 let multibuffer_point = match &action.deployed_from {
6164 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6165 DisplayPoint::new(*row, 0).to_point(&snapshot)
6166 }
6167 _ => self
6168 .selections
6169 .newest::<Point>(&snapshot.display_snapshot)
6170 .head(),
6171 };
6172 let Some((buffer, buffer_row)) = snapshot
6173 .buffer_snapshot()
6174 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6175 .and_then(|(buffer_snapshot, range)| {
6176 self.buffer()
6177 .read(cx)
6178 .buffer(buffer_snapshot.remote_id())
6179 .map(|buffer| (buffer, range.start.row))
6180 })
6181 else {
6182 return;
6183 };
6184 let buffer_id = buffer.read(cx).remote_id();
6185 let tasks = self
6186 .tasks
6187 .get(&(buffer_id, buffer_row))
6188 .map(|t| Arc::new(t.to_owned()));
6189
6190 if !self.focus_handle.is_focused(window) {
6191 return;
6192 }
6193 let project = self.project.clone();
6194
6195 let code_actions_task = match deployed_from {
6196 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6197 _ => self.code_actions(buffer_row, window, cx),
6198 };
6199
6200 let runnable_task = match deployed_from {
6201 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6202 _ => {
6203 let mut task_context_task = Task::ready(None);
6204 if let Some(tasks) = &tasks
6205 && let Some(project) = project
6206 {
6207 task_context_task =
6208 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6209 }
6210
6211 cx.spawn_in(window, {
6212 let buffer = buffer.clone();
6213 async move |editor, cx| {
6214 let task_context = task_context_task.await;
6215
6216 let resolved_tasks =
6217 tasks
6218 .zip(task_context.clone())
6219 .map(|(tasks, task_context)| ResolvedTasks {
6220 templates: tasks.resolve(&task_context).collect(),
6221 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6222 multibuffer_point.row,
6223 tasks.column,
6224 )),
6225 });
6226 let debug_scenarios = editor
6227 .update(cx, |editor, cx| {
6228 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6229 })?
6230 .await;
6231 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6232 }
6233 })
6234 }
6235 };
6236
6237 cx.spawn_in(window, async move |editor, cx| {
6238 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6239 let code_actions = code_actions_task.await;
6240 let spawn_straight_away = quick_launch
6241 && resolved_tasks
6242 .as_ref()
6243 .is_some_and(|tasks| tasks.templates.len() == 1)
6244 && code_actions
6245 .as_ref()
6246 .is_none_or(|actions| actions.is_empty())
6247 && debug_scenarios.is_empty();
6248
6249 editor.update_in(cx, |editor, window, cx| {
6250 crate::hover_popover::hide_hover(editor, cx);
6251 let actions = CodeActionContents::new(
6252 resolved_tasks,
6253 code_actions,
6254 debug_scenarios,
6255 task_context.unwrap_or_default(),
6256 );
6257
6258 // Don't show the menu if there are no actions available
6259 if actions.is_empty() {
6260 cx.notify();
6261 return Task::ready(Ok(()));
6262 }
6263
6264 *editor.context_menu.borrow_mut() =
6265 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6266 buffer,
6267 actions,
6268 selected_item: Default::default(),
6269 scroll_handle: UniformListScrollHandle::default(),
6270 deployed_from,
6271 }));
6272 cx.notify();
6273 if spawn_straight_away
6274 && let Some(task) = editor.confirm_code_action(
6275 &ConfirmCodeAction { item_ix: Some(0) },
6276 window,
6277 cx,
6278 )
6279 {
6280 return task;
6281 }
6282
6283 Task::ready(Ok(()))
6284 })
6285 })
6286 .detach_and_log_err(cx);
6287 }
6288
6289 fn debug_scenarios(
6290 &mut self,
6291 resolved_tasks: &Option<ResolvedTasks>,
6292 buffer: &Entity<Buffer>,
6293 cx: &mut App,
6294 ) -> Task<Vec<task::DebugScenario>> {
6295 maybe!({
6296 let project = self.project()?;
6297 let dap_store = project.read(cx).dap_store();
6298 let mut scenarios = vec![];
6299 let resolved_tasks = resolved_tasks.as_ref()?;
6300 let buffer = buffer.read(cx);
6301 let language = buffer.language()?;
6302 let file = buffer.file();
6303 let debug_adapter = language_settings(language.name().into(), file, cx)
6304 .debuggers
6305 .first()
6306 .map(SharedString::from)
6307 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6308
6309 dap_store.update(cx, |dap_store, cx| {
6310 for (_, task) in &resolved_tasks.templates {
6311 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6312 task.original_task().clone(),
6313 debug_adapter.clone().into(),
6314 task.display_label().to_owned().into(),
6315 cx,
6316 );
6317 scenarios.push(maybe_scenario);
6318 }
6319 });
6320 Some(cx.background_spawn(async move {
6321 futures::future::join_all(scenarios)
6322 .await
6323 .into_iter()
6324 .flatten()
6325 .collect::<Vec<_>>()
6326 }))
6327 })
6328 .unwrap_or_else(|| Task::ready(vec![]))
6329 }
6330
6331 fn code_actions(
6332 &mut self,
6333 buffer_row: u32,
6334 window: &mut Window,
6335 cx: &mut Context<Self>,
6336 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6337 let mut task = self.code_actions_task.take();
6338 cx.spawn_in(window, async move |editor, cx| {
6339 while let Some(prev_task) = task {
6340 prev_task.await.log_err();
6341 task = editor
6342 .update(cx, |this, _| this.code_actions_task.take())
6343 .ok()?;
6344 }
6345
6346 editor
6347 .update(cx, |editor, cx| {
6348 editor
6349 .available_code_actions
6350 .clone()
6351 .and_then(|(location, code_actions)| {
6352 let snapshot = location.buffer.read(cx).snapshot();
6353 let point_range = location.range.to_point(&snapshot);
6354 let point_range = point_range.start.row..=point_range.end.row;
6355 if point_range.contains(&buffer_row) {
6356 Some(code_actions)
6357 } else {
6358 None
6359 }
6360 })
6361 })
6362 .ok()
6363 .flatten()
6364 })
6365 }
6366
6367 pub fn confirm_code_action(
6368 &mut self,
6369 action: &ConfirmCodeAction,
6370 window: &mut Window,
6371 cx: &mut Context<Self>,
6372 ) -> Option<Task<Result<()>>> {
6373 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6374
6375 let actions_menu =
6376 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6377 menu
6378 } else {
6379 return None;
6380 };
6381
6382 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6383 let action = actions_menu.actions.get(action_ix)?;
6384 let title = action.label();
6385 let buffer = actions_menu.buffer;
6386 let workspace = self.workspace()?;
6387
6388 match action {
6389 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6390 workspace.update(cx, |workspace, cx| {
6391 workspace.schedule_resolved_task(
6392 task_source_kind,
6393 resolved_task,
6394 false,
6395 window,
6396 cx,
6397 );
6398
6399 Some(Task::ready(Ok(())))
6400 })
6401 }
6402 CodeActionsItem::CodeAction {
6403 excerpt_id,
6404 action,
6405 provider,
6406 } => {
6407 let apply_code_action =
6408 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6409 let workspace = workspace.downgrade();
6410 Some(cx.spawn_in(window, async move |editor, cx| {
6411 let project_transaction = apply_code_action.await?;
6412 Self::open_project_transaction(
6413 &editor,
6414 workspace,
6415 project_transaction,
6416 title,
6417 cx,
6418 )
6419 .await
6420 }))
6421 }
6422 CodeActionsItem::DebugScenario(scenario) => {
6423 let context = actions_menu.actions.context;
6424
6425 workspace.update(cx, |workspace, cx| {
6426 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6427 workspace.start_debug_session(
6428 scenario,
6429 context,
6430 Some(buffer),
6431 None,
6432 window,
6433 cx,
6434 );
6435 });
6436 Some(Task::ready(Ok(())))
6437 }
6438 }
6439 }
6440
6441 pub async fn open_project_transaction(
6442 editor: &WeakEntity<Editor>,
6443 workspace: WeakEntity<Workspace>,
6444 transaction: ProjectTransaction,
6445 title: String,
6446 cx: &mut AsyncWindowContext,
6447 ) -> Result<()> {
6448 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6449 cx.update(|_, cx| {
6450 entries.sort_unstable_by_key(|(buffer, _)| {
6451 buffer.read(cx).file().map(|f| f.path().clone())
6452 });
6453 })?;
6454 if entries.is_empty() {
6455 return Ok(());
6456 }
6457
6458 // If the project transaction's edits are all contained within this editor, then
6459 // avoid opening a new editor to display them.
6460
6461 if let [(buffer, transaction)] = &*entries {
6462 let excerpt = editor.update(cx, |editor, cx| {
6463 editor
6464 .buffer()
6465 .read(cx)
6466 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6467 })?;
6468 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6469 && excerpted_buffer == *buffer
6470 {
6471 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6472 let excerpt_range = excerpt_range.to_offset(buffer);
6473 buffer
6474 .edited_ranges_for_transaction::<usize>(transaction)
6475 .all(|range| {
6476 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6477 })
6478 })?;
6479
6480 if all_edits_within_excerpt {
6481 return Ok(());
6482 }
6483 }
6484 }
6485
6486 let mut ranges_to_highlight = Vec::new();
6487 let excerpt_buffer = cx.new(|cx| {
6488 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6489 for (buffer_handle, transaction) in &entries {
6490 let edited_ranges = buffer_handle
6491 .read(cx)
6492 .edited_ranges_for_transaction::<Point>(transaction)
6493 .collect::<Vec<_>>();
6494 let (ranges, _) = multibuffer.set_excerpts_for_path(
6495 PathKey::for_buffer(buffer_handle, cx),
6496 buffer_handle.clone(),
6497 edited_ranges,
6498 multibuffer_context_lines(cx),
6499 cx,
6500 );
6501
6502 ranges_to_highlight.extend(ranges);
6503 }
6504 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6505 multibuffer
6506 })?;
6507
6508 workspace.update_in(cx, |workspace, window, cx| {
6509 let project = workspace.project().clone();
6510 let editor =
6511 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6512 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6513 editor.update(cx, |editor, cx| {
6514 editor.highlight_background::<Self>(
6515 &ranges_to_highlight,
6516 |theme| theme.colors().editor_highlighted_line_background,
6517 cx,
6518 );
6519 });
6520 })?;
6521
6522 Ok(())
6523 }
6524
6525 pub fn clear_code_action_providers(&mut self) {
6526 self.code_action_providers.clear();
6527 self.available_code_actions.take();
6528 }
6529
6530 pub fn add_code_action_provider(
6531 &mut self,
6532 provider: Rc<dyn CodeActionProvider>,
6533 window: &mut Window,
6534 cx: &mut Context<Self>,
6535 ) {
6536 if self
6537 .code_action_providers
6538 .iter()
6539 .any(|existing_provider| existing_provider.id() == provider.id())
6540 {
6541 return;
6542 }
6543
6544 self.code_action_providers.push(provider);
6545 self.refresh_code_actions(window, cx);
6546 }
6547
6548 pub fn remove_code_action_provider(
6549 &mut self,
6550 id: Arc<str>,
6551 window: &mut Window,
6552 cx: &mut Context<Self>,
6553 ) {
6554 self.code_action_providers
6555 .retain(|provider| provider.id() != id);
6556 self.refresh_code_actions(window, cx);
6557 }
6558
6559 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6560 !self.code_action_providers.is_empty()
6561 && EditorSettings::get_global(cx).toolbar.code_actions
6562 }
6563
6564 pub fn has_available_code_actions(&self) -> bool {
6565 self.available_code_actions
6566 .as_ref()
6567 .is_some_and(|(_, actions)| !actions.is_empty())
6568 }
6569
6570 fn render_inline_code_actions(
6571 &self,
6572 icon_size: ui::IconSize,
6573 display_row: DisplayRow,
6574 is_active: bool,
6575 cx: &mut Context<Self>,
6576 ) -> AnyElement {
6577 let show_tooltip = !self.context_menu_visible();
6578 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6579 .icon_size(icon_size)
6580 .shape(ui::IconButtonShape::Square)
6581 .icon_color(ui::Color::Hidden)
6582 .toggle_state(is_active)
6583 .when(show_tooltip, |this| {
6584 this.tooltip({
6585 let focus_handle = self.focus_handle.clone();
6586 move |_window, cx| {
6587 Tooltip::for_action_in(
6588 "Toggle Code Actions",
6589 &ToggleCodeActions {
6590 deployed_from: None,
6591 quick_launch: false,
6592 },
6593 &focus_handle,
6594 cx,
6595 )
6596 }
6597 })
6598 })
6599 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6600 window.focus(&editor.focus_handle(cx));
6601 editor.toggle_code_actions(
6602 &crate::actions::ToggleCodeActions {
6603 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6604 display_row,
6605 )),
6606 quick_launch: false,
6607 },
6608 window,
6609 cx,
6610 );
6611 }))
6612 .into_any_element()
6613 }
6614
6615 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6616 &self.context_menu
6617 }
6618
6619 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6620 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6621 cx.background_executor()
6622 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6623 .await;
6624
6625 let (start_buffer, start, _, end, newest_selection) = this
6626 .update(cx, |this, cx| {
6627 let newest_selection = this.selections.newest_anchor().clone();
6628 if newest_selection.head().diff_base_anchor.is_some() {
6629 return None;
6630 }
6631 let display_snapshot = this.display_snapshot(cx);
6632 let newest_selection_adjusted =
6633 this.selections.newest_adjusted(&display_snapshot);
6634 let buffer = this.buffer.read(cx);
6635
6636 let (start_buffer, start) =
6637 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6638 let (end_buffer, end) =
6639 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6640
6641 Some((start_buffer, start, end_buffer, end, newest_selection))
6642 })?
6643 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6644 .context(
6645 "Expected selection to lie in a single buffer when refreshing code actions",
6646 )?;
6647 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6648 let providers = this.code_action_providers.clone();
6649 let tasks = this
6650 .code_action_providers
6651 .iter()
6652 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6653 .collect::<Vec<_>>();
6654 (providers, tasks)
6655 })?;
6656
6657 let mut actions = Vec::new();
6658 for (provider, provider_actions) in
6659 providers.into_iter().zip(future::join_all(tasks).await)
6660 {
6661 if let Some(provider_actions) = provider_actions.log_err() {
6662 actions.extend(provider_actions.into_iter().map(|action| {
6663 AvailableCodeAction {
6664 excerpt_id: newest_selection.start.excerpt_id,
6665 action,
6666 provider: provider.clone(),
6667 }
6668 }));
6669 }
6670 }
6671
6672 this.update(cx, |this, cx| {
6673 this.available_code_actions = if actions.is_empty() {
6674 None
6675 } else {
6676 Some((
6677 Location {
6678 buffer: start_buffer,
6679 range: start..end,
6680 },
6681 actions.into(),
6682 ))
6683 };
6684 cx.notify();
6685 })
6686 }));
6687 }
6688
6689 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6690 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6691 self.show_git_blame_inline = false;
6692
6693 self.show_git_blame_inline_delay_task =
6694 Some(cx.spawn_in(window, async move |this, cx| {
6695 cx.background_executor().timer(delay).await;
6696
6697 this.update(cx, |this, cx| {
6698 this.show_git_blame_inline = true;
6699 cx.notify();
6700 })
6701 .log_err();
6702 }));
6703 }
6704 }
6705
6706 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6707 let snapshot = self.snapshot(window, cx);
6708 let cursor = self
6709 .selections
6710 .newest::<Point>(&snapshot.display_snapshot)
6711 .head();
6712 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6713 else {
6714 return;
6715 };
6716
6717 let Some(blame) = self.blame.as_ref() else {
6718 return;
6719 };
6720
6721 let row_info = RowInfo {
6722 buffer_id: Some(buffer.remote_id()),
6723 buffer_row: Some(point.row),
6724 ..Default::default()
6725 };
6726 let Some((buffer, blame_entry)) = blame
6727 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6728 .flatten()
6729 else {
6730 return;
6731 };
6732
6733 let anchor = self.selections.newest_anchor().head();
6734 let position = self.to_pixel_point(anchor, &snapshot, window);
6735 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6736 self.show_blame_popover(
6737 buffer,
6738 &blame_entry,
6739 position + last_bounds.origin,
6740 true,
6741 cx,
6742 );
6743 };
6744 }
6745
6746 fn show_blame_popover(
6747 &mut self,
6748 buffer: BufferId,
6749 blame_entry: &BlameEntry,
6750 position: gpui::Point<Pixels>,
6751 ignore_timeout: bool,
6752 cx: &mut Context<Self>,
6753 ) {
6754 if let Some(state) = &mut self.inline_blame_popover {
6755 state.hide_task.take();
6756 } else {
6757 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6758 let blame_entry = blame_entry.clone();
6759 let show_task = cx.spawn(async move |editor, cx| {
6760 if !ignore_timeout {
6761 cx.background_executor()
6762 .timer(std::time::Duration::from_millis(blame_popover_delay))
6763 .await;
6764 }
6765 editor
6766 .update(cx, |editor, cx| {
6767 editor.inline_blame_popover_show_task.take();
6768 let Some(blame) = editor.blame.as_ref() else {
6769 return;
6770 };
6771 let blame = blame.read(cx);
6772 let details = blame.details_for_entry(buffer, &blame_entry);
6773 let markdown = cx.new(|cx| {
6774 Markdown::new(
6775 details
6776 .as_ref()
6777 .map(|message| message.message.clone())
6778 .unwrap_or_default(),
6779 None,
6780 None,
6781 cx,
6782 )
6783 });
6784 editor.inline_blame_popover = Some(InlineBlamePopover {
6785 position,
6786 hide_task: None,
6787 popover_bounds: None,
6788 popover_state: InlineBlamePopoverState {
6789 scroll_handle: ScrollHandle::new(),
6790 commit_message: details,
6791 markdown,
6792 },
6793 keyboard_grace: ignore_timeout,
6794 });
6795 cx.notify();
6796 })
6797 .ok();
6798 });
6799 self.inline_blame_popover_show_task = Some(show_task);
6800 }
6801 }
6802
6803 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6804 self.inline_blame_popover_show_task.take();
6805 if let Some(state) = &mut self.inline_blame_popover {
6806 let hide_task = cx.spawn(async move |editor, cx| {
6807 if !ignore_timeout {
6808 cx.background_executor()
6809 .timer(std::time::Duration::from_millis(100))
6810 .await;
6811 }
6812 editor
6813 .update(cx, |editor, cx| {
6814 editor.inline_blame_popover.take();
6815 cx.notify();
6816 })
6817 .ok();
6818 });
6819 state.hide_task = Some(hide_task);
6820 true
6821 } else {
6822 false
6823 }
6824 }
6825
6826 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6827 if self.pending_rename.is_some() {
6828 return None;
6829 }
6830
6831 let provider = self.semantics_provider.clone()?;
6832 let buffer = self.buffer.read(cx);
6833 let newest_selection = self.selections.newest_anchor().clone();
6834 let cursor_position = newest_selection.head();
6835 let (cursor_buffer, cursor_buffer_position) =
6836 buffer.text_anchor_for_position(cursor_position, cx)?;
6837 let (tail_buffer, tail_buffer_position) =
6838 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6839 if cursor_buffer != tail_buffer {
6840 return None;
6841 }
6842
6843 let snapshot = cursor_buffer.read(cx).snapshot();
6844 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6845 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6846 if start_word_range != end_word_range {
6847 self.document_highlights_task.take();
6848 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6849 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6850 return None;
6851 }
6852
6853 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6854 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6855 cx.background_executor()
6856 .timer(Duration::from_millis(debounce))
6857 .await;
6858
6859 let highlights = if let Some(highlights) = cx
6860 .update(|cx| {
6861 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6862 })
6863 .ok()
6864 .flatten()
6865 {
6866 highlights.await.log_err()
6867 } else {
6868 None
6869 };
6870
6871 if let Some(highlights) = highlights {
6872 this.update(cx, |this, cx| {
6873 if this.pending_rename.is_some() {
6874 return;
6875 }
6876
6877 let buffer = this.buffer.read(cx);
6878 if buffer
6879 .text_anchor_for_position(cursor_position, cx)
6880 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6881 {
6882 return;
6883 }
6884
6885 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6886 let mut write_ranges = Vec::new();
6887 let mut read_ranges = Vec::new();
6888 for highlight in highlights {
6889 let buffer_id = cursor_buffer.read(cx).remote_id();
6890 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6891 {
6892 let start = highlight
6893 .range
6894 .start
6895 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6896 let end = highlight
6897 .range
6898 .end
6899 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6900 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6901 continue;
6902 }
6903
6904 let range =
6905 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6906 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6907 write_ranges.push(range);
6908 } else {
6909 read_ranges.push(range);
6910 }
6911 }
6912 }
6913
6914 this.highlight_background::<DocumentHighlightRead>(
6915 &read_ranges,
6916 |theme| theme.colors().editor_document_highlight_read_background,
6917 cx,
6918 );
6919 this.highlight_background::<DocumentHighlightWrite>(
6920 &write_ranges,
6921 |theme| theme.colors().editor_document_highlight_write_background,
6922 cx,
6923 );
6924 cx.notify();
6925 })
6926 .log_err();
6927 }
6928 }));
6929 None
6930 }
6931
6932 fn prepare_highlight_query_from_selection(
6933 &mut self,
6934 window: &Window,
6935 cx: &mut Context<Editor>,
6936 ) -> Option<(String, Range<Anchor>)> {
6937 if matches!(self.mode, EditorMode::SingleLine) {
6938 return None;
6939 }
6940 if !EditorSettings::get_global(cx).selection_highlight {
6941 return None;
6942 }
6943 if self.selections.count() != 1 || self.selections.line_mode() {
6944 return None;
6945 }
6946 let snapshot = self.snapshot(window, cx);
6947 let selection = self.selections.newest::<Point>(&snapshot);
6948 // If the selection spans multiple rows OR it is empty
6949 if selection.start.row != selection.end.row
6950 || selection.start.column == selection.end.column
6951 {
6952 return None;
6953 }
6954 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6955 let query = snapshot
6956 .buffer_snapshot()
6957 .text_for_range(selection_anchor_range.clone())
6958 .collect::<String>();
6959 if query.trim().is_empty() {
6960 return None;
6961 }
6962 Some((query, selection_anchor_range))
6963 }
6964
6965 fn update_selection_occurrence_highlights(
6966 &mut self,
6967 query_text: String,
6968 query_range: Range<Anchor>,
6969 multi_buffer_range_to_query: Range<Point>,
6970 use_debounce: bool,
6971 window: &mut Window,
6972 cx: &mut Context<Editor>,
6973 ) -> Task<()> {
6974 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6975 cx.spawn_in(window, async move |editor, cx| {
6976 if use_debounce {
6977 cx.background_executor()
6978 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6979 .await;
6980 }
6981 let match_task = cx.background_spawn(async move {
6982 let buffer_ranges = multi_buffer_snapshot
6983 .range_to_buffer_ranges(multi_buffer_range_to_query)
6984 .into_iter()
6985 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6986 let mut match_ranges = Vec::new();
6987 let Ok(regex) = project::search::SearchQuery::text(
6988 query_text.clone(),
6989 false,
6990 false,
6991 false,
6992 Default::default(),
6993 Default::default(),
6994 false,
6995 None,
6996 ) else {
6997 return Vec::default();
6998 };
6999 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7000 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7001 match_ranges.extend(
7002 regex
7003 .search(buffer_snapshot, Some(search_range.clone()))
7004 .await
7005 .into_iter()
7006 .filter_map(|match_range| {
7007 let match_start = buffer_snapshot
7008 .anchor_after(search_range.start + match_range.start);
7009 let match_end = buffer_snapshot
7010 .anchor_before(search_range.start + match_range.end);
7011 let match_anchor_range = Anchor::range_in_buffer(
7012 excerpt_id,
7013 buffer_snapshot.remote_id(),
7014 match_start..match_end,
7015 );
7016 (match_anchor_range != query_range).then_some(match_anchor_range)
7017 }),
7018 );
7019 }
7020 match_ranges
7021 });
7022 let match_ranges = match_task.await;
7023 editor
7024 .update_in(cx, |editor, _, cx| {
7025 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7026 if !match_ranges.is_empty() {
7027 editor.highlight_background::<SelectedTextHighlight>(
7028 &match_ranges,
7029 |theme| theme.colors().editor_document_highlight_bracket_background,
7030 cx,
7031 )
7032 }
7033 })
7034 .log_err();
7035 })
7036 }
7037
7038 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7039 struct NewlineFold;
7040 let type_id = std::any::TypeId::of::<NewlineFold>();
7041 if !self.mode.is_single_line() {
7042 return;
7043 }
7044 let snapshot = self.snapshot(window, cx);
7045 if snapshot.buffer_snapshot().max_point().row == 0 {
7046 return;
7047 }
7048 let task = cx.background_spawn(async move {
7049 let new_newlines = snapshot
7050 .buffer_chars_at(0)
7051 .filter_map(|(c, i)| {
7052 if c == '\n' {
7053 Some(
7054 snapshot.buffer_snapshot().anchor_after(i)
7055 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7056 )
7057 } else {
7058 None
7059 }
7060 })
7061 .collect::<Vec<_>>();
7062 let existing_newlines = snapshot
7063 .folds_in_range(0..snapshot.buffer_snapshot().len())
7064 .filter_map(|fold| {
7065 if fold.placeholder.type_tag == Some(type_id) {
7066 Some(fold.range.start..fold.range.end)
7067 } else {
7068 None
7069 }
7070 })
7071 .collect::<Vec<_>>();
7072
7073 (new_newlines, existing_newlines)
7074 });
7075 self.folding_newlines = cx.spawn(async move |this, cx| {
7076 let (new_newlines, existing_newlines) = task.await;
7077 if new_newlines == existing_newlines {
7078 return;
7079 }
7080 let placeholder = FoldPlaceholder {
7081 render: Arc::new(move |_, _, cx| {
7082 div()
7083 .bg(cx.theme().status().hint_background)
7084 .border_b_1()
7085 .size_full()
7086 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7087 .border_color(cx.theme().status().hint)
7088 .child("\\n")
7089 .into_any()
7090 }),
7091 constrain_width: false,
7092 merge_adjacent: false,
7093 type_tag: Some(type_id),
7094 };
7095 let creases = new_newlines
7096 .into_iter()
7097 .map(|range| Crease::simple(range, placeholder.clone()))
7098 .collect();
7099 this.update(cx, |this, cx| {
7100 this.display_map.update(cx, |display_map, cx| {
7101 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7102 display_map.fold(creases, cx);
7103 });
7104 })
7105 .ok();
7106 });
7107 }
7108
7109 fn refresh_selected_text_highlights(
7110 &mut self,
7111 on_buffer_edit: bool,
7112 window: &mut Window,
7113 cx: &mut Context<Editor>,
7114 ) {
7115 let Some((query_text, query_range)) =
7116 self.prepare_highlight_query_from_selection(window, cx)
7117 else {
7118 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7119 self.quick_selection_highlight_task.take();
7120 self.debounced_selection_highlight_task.take();
7121 return;
7122 };
7123 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7124 if on_buffer_edit
7125 || self
7126 .quick_selection_highlight_task
7127 .as_ref()
7128 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7129 {
7130 let multi_buffer_visible_start = self
7131 .scroll_manager
7132 .anchor()
7133 .anchor
7134 .to_point(&multi_buffer_snapshot);
7135 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7136 multi_buffer_visible_start
7137 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7138 Bias::Left,
7139 );
7140 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7141 self.quick_selection_highlight_task = Some((
7142 query_range.clone(),
7143 self.update_selection_occurrence_highlights(
7144 query_text.clone(),
7145 query_range.clone(),
7146 multi_buffer_visible_range,
7147 false,
7148 window,
7149 cx,
7150 ),
7151 ));
7152 }
7153 if on_buffer_edit
7154 || self
7155 .debounced_selection_highlight_task
7156 .as_ref()
7157 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7158 {
7159 let multi_buffer_start = multi_buffer_snapshot
7160 .anchor_before(0)
7161 .to_point(&multi_buffer_snapshot);
7162 let multi_buffer_end = multi_buffer_snapshot
7163 .anchor_after(multi_buffer_snapshot.len())
7164 .to_point(&multi_buffer_snapshot);
7165 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7166 self.debounced_selection_highlight_task = Some((
7167 query_range.clone(),
7168 self.update_selection_occurrence_highlights(
7169 query_text,
7170 query_range,
7171 multi_buffer_full_range,
7172 true,
7173 window,
7174 cx,
7175 ),
7176 ));
7177 }
7178 }
7179
7180 pub fn refresh_edit_prediction(
7181 &mut self,
7182 debounce: bool,
7183 user_requested: bool,
7184 window: &mut Window,
7185 cx: &mut Context<Self>,
7186 ) -> Option<()> {
7187 if DisableAiSettings::get_global(cx).disable_ai {
7188 return None;
7189 }
7190
7191 let provider = self.edit_prediction_provider()?;
7192 let cursor = self.selections.newest_anchor().head();
7193 let (buffer, cursor_buffer_position) =
7194 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7195
7196 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7197 self.discard_edit_prediction(false, cx);
7198 return None;
7199 }
7200
7201 self.update_visible_edit_prediction(window, cx);
7202
7203 if !user_requested
7204 && (!self.should_show_edit_predictions()
7205 || !self.is_focused(window)
7206 || buffer.read(cx).is_empty())
7207 {
7208 self.discard_edit_prediction(false, cx);
7209 return None;
7210 }
7211
7212 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7213 Some(())
7214 }
7215
7216 fn show_edit_predictions_in_menu(&self) -> bool {
7217 match self.edit_prediction_settings {
7218 EditPredictionSettings::Disabled => false,
7219 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7220 }
7221 }
7222
7223 pub fn edit_predictions_enabled(&self) -> bool {
7224 match self.edit_prediction_settings {
7225 EditPredictionSettings::Disabled => false,
7226 EditPredictionSettings::Enabled { .. } => true,
7227 }
7228 }
7229
7230 fn edit_prediction_requires_modifier(&self) -> bool {
7231 match self.edit_prediction_settings {
7232 EditPredictionSettings::Disabled => false,
7233 EditPredictionSettings::Enabled {
7234 preview_requires_modifier,
7235 ..
7236 } => preview_requires_modifier,
7237 }
7238 }
7239
7240 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7241 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7242 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7243 self.discard_edit_prediction(false, cx);
7244 } else {
7245 let selection = self.selections.newest_anchor();
7246 let cursor = selection.head();
7247
7248 if let Some((buffer, cursor_buffer_position)) =
7249 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7250 {
7251 self.edit_prediction_settings =
7252 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7253 }
7254 }
7255 }
7256
7257 fn edit_prediction_settings_at_position(
7258 &self,
7259 buffer: &Entity<Buffer>,
7260 buffer_position: language::Anchor,
7261 cx: &App,
7262 ) -> EditPredictionSettings {
7263 if !self.mode.is_full()
7264 || !self.show_edit_predictions_override.unwrap_or(true)
7265 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7266 {
7267 return EditPredictionSettings::Disabled;
7268 }
7269
7270 let buffer = buffer.read(cx);
7271
7272 let file = buffer.file();
7273
7274 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7275 return EditPredictionSettings::Disabled;
7276 };
7277
7278 let by_provider = matches!(
7279 self.menu_edit_predictions_policy,
7280 MenuEditPredictionsPolicy::ByProvider
7281 );
7282
7283 let show_in_menu = by_provider
7284 && self
7285 .edit_prediction_provider
7286 .as_ref()
7287 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7288
7289 let preview_requires_modifier =
7290 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7291
7292 EditPredictionSettings::Enabled {
7293 show_in_menu,
7294 preview_requires_modifier,
7295 }
7296 }
7297
7298 fn should_show_edit_predictions(&self) -> bool {
7299 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7300 }
7301
7302 pub fn edit_prediction_preview_is_active(&self) -> bool {
7303 matches!(
7304 self.edit_prediction_preview,
7305 EditPredictionPreview::Active { .. }
7306 )
7307 }
7308
7309 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7310 let cursor = self.selections.newest_anchor().head();
7311 if let Some((buffer, cursor_position)) =
7312 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7313 {
7314 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7315 } else {
7316 false
7317 }
7318 }
7319
7320 pub fn supports_minimap(&self, cx: &App) -> bool {
7321 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7322 }
7323
7324 fn edit_predictions_enabled_in_buffer(
7325 &self,
7326 buffer: &Entity<Buffer>,
7327 buffer_position: language::Anchor,
7328 cx: &App,
7329 ) -> bool {
7330 maybe!({
7331 if self.read_only(cx) {
7332 return Some(false);
7333 }
7334 let provider = self.edit_prediction_provider()?;
7335 if !provider.is_enabled(buffer, buffer_position, cx) {
7336 return Some(false);
7337 }
7338 let buffer = buffer.read(cx);
7339 let Some(file) = buffer.file() else {
7340 return Some(true);
7341 };
7342 let settings = all_language_settings(Some(file), cx);
7343 Some(settings.edit_predictions_enabled_for_file(file, cx))
7344 })
7345 .unwrap_or(false)
7346 }
7347
7348 fn cycle_edit_prediction(
7349 &mut self,
7350 direction: Direction,
7351 window: &mut Window,
7352 cx: &mut Context<Self>,
7353 ) -> Option<()> {
7354 let provider = self.edit_prediction_provider()?;
7355 let cursor = self.selections.newest_anchor().head();
7356 let (buffer, cursor_buffer_position) =
7357 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7358 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7359 return None;
7360 }
7361
7362 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7363 self.update_visible_edit_prediction(window, cx);
7364
7365 Some(())
7366 }
7367
7368 pub fn show_edit_prediction(
7369 &mut self,
7370 _: &ShowEditPrediction,
7371 window: &mut Window,
7372 cx: &mut Context<Self>,
7373 ) {
7374 if !self.has_active_edit_prediction() {
7375 self.refresh_edit_prediction(false, true, window, cx);
7376 return;
7377 }
7378
7379 self.update_visible_edit_prediction(window, cx);
7380 }
7381
7382 pub fn display_cursor_names(
7383 &mut self,
7384 _: &DisplayCursorNames,
7385 window: &mut Window,
7386 cx: &mut Context<Self>,
7387 ) {
7388 self.show_cursor_names(window, cx);
7389 }
7390
7391 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7392 self.show_cursor_names = true;
7393 cx.notify();
7394 cx.spawn_in(window, async move |this, cx| {
7395 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7396 this.update(cx, |this, cx| {
7397 this.show_cursor_names = false;
7398 cx.notify()
7399 })
7400 .ok()
7401 })
7402 .detach();
7403 }
7404
7405 pub fn next_edit_prediction(
7406 &mut self,
7407 _: &NextEditPrediction,
7408 window: &mut Window,
7409 cx: &mut Context<Self>,
7410 ) {
7411 if self.has_active_edit_prediction() {
7412 self.cycle_edit_prediction(Direction::Next, window, cx);
7413 } else {
7414 let is_copilot_disabled = self
7415 .refresh_edit_prediction(false, true, window, cx)
7416 .is_none();
7417 if is_copilot_disabled {
7418 cx.propagate();
7419 }
7420 }
7421 }
7422
7423 pub fn previous_edit_prediction(
7424 &mut self,
7425 _: &PreviousEditPrediction,
7426 window: &mut Window,
7427 cx: &mut Context<Self>,
7428 ) {
7429 if self.has_active_edit_prediction() {
7430 self.cycle_edit_prediction(Direction::Prev, window, cx);
7431 } else {
7432 let is_copilot_disabled = self
7433 .refresh_edit_prediction(false, true, window, cx)
7434 .is_none();
7435 if is_copilot_disabled {
7436 cx.propagate();
7437 }
7438 }
7439 }
7440
7441 pub fn accept_edit_prediction(
7442 &mut self,
7443 _: &AcceptEditPrediction,
7444 window: &mut Window,
7445 cx: &mut Context<Self>,
7446 ) {
7447 if self.show_edit_predictions_in_menu() {
7448 self.hide_context_menu(window, cx);
7449 }
7450
7451 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7452 return;
7453 };
7454
7455 match &active_edit_prediction.completion {
7456 EditPrediction::MoveWithin { target, .. } => {
7457 let target = *target;
7458
7459 if let Some(position_map) = &self.last_position_map {
7460 if position_map
7461 .visible_row_range
7462 .contains(&target.to_display_point(&position_map.snapshot).row())
7463 || !self.edit_prediction_requires_modifier()
7464 {
7465 self.unfold_ranges(&[target..target], true, false, cx);
7466 // Note that this is also done in vim's handler of the Tab action.
7467 self.change_selections(
7468 SelectionEffects::scroll(Autoscroll::newest()),
7469 window,
7470 cx,
7471 |selections| {
7472 selections.select_anchor_ranges([target..target]);
7473 },
7474 );
7475 self.clear_row_highlights::<EditPredictionPreview>();
7476
7477 self.edit_prediction_preview
7478 .set_previous_scroll_position(None);
7479 } else {
7480 self.edit_prediction_preview
7481 .set_previous_scroll_position(Some(
7482 position_map.snapshot.scroll_anchor,
7483 ));
7484
7485 self.highlight_rows::<EditPredictionPreview>(
7486 target..target,
7487 cx.theme().colors().editor_highlighted_line_background,
7488 RowHighlightOptions {
7489 autoscroll: true,
7490 ..Default::default()
7491 },
7492 cx,
7493 );
7494 self.request_autoscroll(Autoscroll::fit(), cx);
7495 }
7496 }
7497 }
7498 EditPrediction::MoveOutside { snapshot, target } => {
7499 if let Some(workspace) = self.workspace() {
7500 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7501 .detach_and_log_err(cx);
7502 }
7503 }
7504 EditPrediction::Edit { edits, .. } => {
7505 self.report_edit_prediction_event(
7506 active_edit_prediction.completion_id.clone(),
7507 true,
7508 cx,
7509 );
7510
7511 if let Some(provider) = self.edit_prediction_provider() {
7512 provider.accept(cx);
7513 }
7514
7515 // Store the transaction ID and selections before applying the edit
7516 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7517
7518 let snapshot = self.buffer.read(cx).snapshot(cx);
7519 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7520
7521 self.buffer.update(cx, |buffer, cx| {
7522 buffer.edit(edits.iter().cloned(), None, cx)
7523 });
7524
7525 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7526 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7527 });
7528
7529 let selections = self.selections.disjoint_anchors_arc();
7530 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7531 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7532 if has_new_transaction {
7533 self.selection_history
7534 .insert_transaction(transaction_id_now, selections);
7535 }
7536 }
7537
7538 self.update_visible_edit_prediction(window, cx);
7539 if self.active_edit_prediction.is_none() {
7540 self.refresh_edit_prediction(true, true, window, cx);
7541 }
7542
7543 cx.notify();
7544 }
7545 }
7546
7547 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7548 }
7549
7550 pub fn accept_partial_edit_prediction(
7551 &mut self,
7552 _: &AcceptPartialEditPrediction,
7553 window: &mut Window,
7554 cx: &mut Context<Self>,
7555 ) {
7556 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7557 return;
7558 };
7559 if self.selections.count() != 1 {
7560 return;
7561 }
7562
7563 match &active_edit_prediction.completion {
7564 EditPrediction::MoveWithin { target, .. } => {
7565 let target = *target;
7566 self.change_selections(
7567 SelectionEffects::scroll(Autoscroll::newest()),
7568 window,
7569 cx,
7570 |selections| {
7571 selections.select_anchor_ranges([target..target]);
7572 },
7573 );
7574 }
7575 EditPrediction::MoveOutside { snapshot, target } => {
7576 if let Some(workspace) = self.workspace() {
7577 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7578 .detach_and_log_err(cx);
7579 }
7580 }
7581 EditPrediction::Edit { edits, .. } => {
7582 self.report_edit_prediction_event(
7583 active_edit_prediction.completion_id.clone(),
7584 true,
7585 cx,
7586 );
7587
7588 // Find an insertion that starts at the cursor position.
7589 let snapshot = self.buffer.read(cx).snapshot(cx);
7590 let cursor_offset = self
7591 .selections
7592 .newest::<usize>(&self.display_snapshot(cx))
7593 .head();
7594 let insertion = edits.iter().find_map(|(range, text)| {
7595 let range = range.to_offset(&snapshot);
7596 if range.is_empty() && range.start == cursor_offset {
7597 Some(text)
7598 } else {
7599 None
7600 }
7601 });
7602
7603 if let Some(text) = insertion {
7604 let mut partial_completion = text
7605 .chars()
7606 .by_ref()
7607 .take_while(|c| c.is_alphabetic())
7608 .collect::<String>();
7609 if partial_completion.is_empty() {
7610 partial_completion = text
7611 .chars()
7612 .by_ref()
7613 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7614 .collect::<String>();
7615 }
7616
7617 cx.emit(EditorEvent::InputHandled {
7618 utf16_range_to_replace: None,
7619 text: partial_completion.clone().into(),
7620 });
7621
7622 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7623
7624 self.refresh_edit_prediction(true, true, window, cx);
7625 cx.notify();
7626 } else {
7627 self.accept_edit_prediction(&Default::default(), window, cx);
7628 }
7629 }
7630 }
7631 }
7632
7633 fn discard_edit_prediction(
7634 &mut self,
7635 should_report_edit_prediction_event: bool,
7636 cx: &mut Context<Self>,
7637 ) -> bool {
7638 if should_report_edit_prediction_event {
7639 let completion_id = self
7640 .active_edit_prediction
7641 .as_ref()
7642 .and_then(|active_completion| active_completion.completion_id.clone());
7643
7644 self.report_edit_prediction_event(completion_id, false, cx);
7645 }
7646
7647 if let Some(provider) = self.edit_prediction_provider() {
7648 provider.discard(cx);
7649 }
7650
7651 self.take_active_edit_prediction(cx)
7652 }
7653
7654 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7655 let Some(provider) = self.edit_prediction_provider() else {
7656 return;
7657 };
7658
7659 let Some((_, buffer, _)) = self
7660 .buffer
7661 .read(cx)
7662 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7663 else {
7664 return;
7665 };
7666
7667 let extension = buffer
7668 .read(cx)
7669 .file()
7670 .and_then(|file| Some(file.path().extension()?.to_string()));
7671
7672 let event_type = match accepted {
7673 true => "Edit Prediction Accepted",
7674 false => "Edit Prediction Discarded",
7675 };
7676 telemetry::event!(
7677 event_type,
7678 provider = provider.name(),
7679 prediction_id = id,
7680 suggestion_accepted = accepted,
7681 file_extension = extension,
7682 );
7683 }
7684
7685 fn open_editor_at_anchor(
7686 snapshot: &language::BufferSnapshot,
7687 target: language::Anchor,
7688 workspace: &Entity<Workspace>,
7689 window: &mut Window,
7690 cx: &mut App,
7691 ) -> Task<Result<()>> {
7692 workspace.update(cx, |workspace, cx| {
7693 let path = snapshot.file().map(|file| file.full_path(cx));
7694 let Some(path) =
7695 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7696 else {
7697 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7698 };
7699 let target = text::ToPoint::to_point(&target, snapshot);
7700 let item = workspace.open_path(path, None, true, window, cx);
7701 window.spawn(cx, async move |cx| {
7702 let Some(editor) = item.await?.downcast::<Editor>() else {
7703 return Ok(());
7704 };
7705 editor
7706 .update_in(cx, |editor, window, cx| {
7707 editor.go_to_singleton_buffer_point(target, window, cx);
7708 })
7709 .ok();
7710 anyhow::Ok(())
7711 })
7712 })
7713 }
7714
7715 pub fn has_active_edit_prediction(&self) -> bool {
7716 self.active_edit_prediction.is_some()
7717 }
7718
7719 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7720 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7721 return false;
7722 };
7723
7724 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7725 self.clear_highlights::<EditPredictionHighlight>(cx);
7726 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7727 true
7728 }
7729
7730 /// Returns true when we're displaying the edit prediction popover below the cursor
7731 /// like we are not previewing and the LSP autocomplete menu is visible
7732 /// or we are in `when_holding_modifier` mode.
7733 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7734 if self.edit_prediction_preview_is_active()
7735 || !self.show_edit_predictions_in_menu()
7736 || !self.edit_predictions_enabled()
7737 {
7738 return false;
7739 }
7740
7741 if self.has_visible_completions_menu() {
7742 return true;
7743 }
7744
7745 has_completion && self.edit_prediction_requires_modifier()
7746 }
7747
7748 fn handle_modifiers_changed(
7749 &mut self,
7750 modifiers: Modifiers,
7751 position_map: &PositionMap,
7752 window: &mut Window,
7753 cx: &mut Context<Self>,
7754 ) {
7755 // Ensure that the edit prediction preview is updated, even when not
7756 // enabled, if there's an active edit prediction preview.
7757 if self.show_edit_predictions_in_menu()
7758 || matches!(
7759 self.edit_prediction_preview,
7760 EditPredictionPreview::Active { .. }
7761 )
7762 {
7763 self.update_edit_prediction_preview(&modifiers, window, cx);
7764 }
7765
7766 self.update_selection_mode(&modifiers, position_map, window, cx);
7767
7768 let mouse_position = window.mouse_position();
7769 if !position_map.text_hitbox.is_hovered(window) {
7770 return;
7771 }
7772
7773 self.update_hovered_link(
7774 position_map.point_for_position(mouse_position),
7775 &position_map.snapshot,
7776 modifiers,
7777 window,
7778 cx,
7779 )
7780 }
7781
7782 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7783 match EditorSettings::get_global(cx).multi_cursor_modifier {
7784 MultiCursorModifier::Alt => modifiers.secondary(),
7785 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7786 }
7787 }
7788
7789 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7790 match EditorSettings::get_global(cx).multi_cursor_modifier {
7791 MultiCursorModifier::Alt => modifiers.alt,
7792 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7793 }
7794 }
7795
7796 fn columnar_selection_mode(
7797 modifiers: &Modifiers,
7798 cx: &mut Context<Self>,
7799 ) -> Option<ColumnarMode> {
7800 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7801 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7802 Some(ColumnarMode::FromMouse)
7803 } else if Self::is_alt_pressed(modifiers, cx) {
7804 Some(ColumnarMode::FromSelection)
7805 } else {
7806 None
7807 }
7808 } else {
7809 None
7810 }
7811 }
7812
7813 fn update_selection_mode(
7814 &mut self,
7815 modifiers: &Modifiers,
7816 position_map: &PositionMap,
7817 window: &mut Window,
7818 cx: &mut Context<Self>,
7819 ) {
7820 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7821 return;
7822 };
7823 if self.selections.pending_anchor().is_none() {
7824 return;
7825 }
7826
7827 let mouse_position = window.mouse_position();
7828 let point_for_position = position_map.point_for_position(mouse_position);
7829 let position = point_for_position.previous_valid;
7830
7831 self.select(
7832 SelectPhase::BeginColumnar {
7833 position,
7834 reset: false,
7835 mode,
7836 goal_column: point_for_position.exact_unclipped.column(),
7837 },
7838 window,
7839 cx,
7840 );
7841 }
7842
7843 fn update_edit_prediction_preview(
7844 &mut self,
7845 modifiers: &Modifiers,
7846 window: &mut Window,
7847 cx: &mut Context<Self>,
7848 ) {
7849 let mut modifiers_held = false;
7850 if let Some(accept_keystroke) = self
7851 .accept_edit_prediction_keybind(false, window, cx)
7852 .keystroke()
7853 {
7854 modifiers_held = modifiers_held
7855 || (accept_keystroke.modifiers() == modifiers
7856 && accept_keystroke.modifiers().modified());
7857 };
7858 if let Some(accept_partial_keystroke) = self
7859 .accept_edit_prediction_keybind(true, window, cx)
7860 .keystroke()
7861 {
7862 modifiers_held = modifiers_held
7863 || (accept_partial_keystroke.modifiers() == modifiers
7864 && accept_partial_keystroke.modifiers().modified());
7865 }
7866
7867 if modifiers_held {
7868 if matches!(
7869 self.edit_prediction_preview,
7870 EditPredictionPreview::Inactive { .. }
7871 ) {
7872 if let Some(provider) = self.edit_prediction_provider.as_ref() {
7873 provider.provider.did_show(cx)
7874 }
7875
7876 self.edit_prediction_preview = EditPredictionPreview::Active {
7877 previous_scroll_position: None,
7878 since: Instant::now(),
7879 };
7880
7881 self.update_visible_edit_prediction(window, cx);
7882 cx.notify();
7883 }
7884 } else if let EditPredictionPreview::Active {
7885 previous_scroll_position,
7886 since,
7887 } = self.edit_prediction_preview
7888 {
7889 if let (Some(previous_scroll_position), Some(position_map)) =
7890 (previous_scroll_position, self.last_position_map.as_ref())
7891 {
7892 self.set_scroll_position(
7893 previous_scroll_position
7894 .scroll_position(&position_map.snapshot.display_snapshot),
7895 window,
7896 cx,
7897 );
7898 }
7899
7900 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7901 released_too_fast: since.elapsed() < Duration::from_millis(200),
7902 };
7903 self.clear_row_highlights::<EditPredictionPreview>();
7904 self.update_visible_edit_prediction(window, cx);
7905 cx.notify();
7906 }
7907 }
7908
7909 fn update_visible_edit_prediction(
7910 &mut self,
7911 _window: &mut Window,
7912 cx: &mut Context<Self>,
7913 ) -> Option<()> {
7914 if DisableAiSettings::get_global(cx).disable_ai {
7915 return None;
7916 }
7917
7918 if self.ime_transaction.is_some() {
7919 self.discard_edit_prediction(false, cx);
7920 return None;
7921 }
7922
7923 let selection = self.selections.newest_anchor();
7924 let cursor = selection.head();
7925 let multibuffer = self.buffer.read(cx).snapshot(cx);
7926 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7927 let excerpt_id = cursor.excerpt_id;
7928
7929 let show_in_menu = self.show_edit_predictions_in_menu();
7930 let completions_menu_has_precedence = !show_in_menu
7931 && (self.context_menu.borrow().is_some()
7932 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7933
7934 if completions_menu_has_precedence
7935 || !offset_selection.is_empty()
7936 || self
7937 .active_edit_prediction
7938 .as_ref()
7939 .is_some_and(|completion| {
7940 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7941 return false;
7942 };
7943 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7944 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7945 !invalidation_range.contains(&offset_selection.head())
7946 })
7947 {
7948 self.discard_edit_prediction(false, cx);
7949 return None;
7950 }
7951
7952 self.take_active_edit_prediction(cx);
7953 let Some(provider) = self.edit_prediction_provider() else {
7954 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7955 return None;
7956 };
7957
7958 let (buffer, cursor_buffer_position) =
7959 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7960
7961 self.edit_prediction_settings =
7962 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7963
7964 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7965
7966 if self.edit_prediction_indent_conflict {
7967 let cursor_point = cursor.to_point(&multibuffer);
7968
7969 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7970
7971 if let Some((_, indent)) = indents.iter().next()
7972 && indent.len == cursor_point.column
7973 {
7974 self.edit_prediction_indent_conflict = false;
7975 }
7976 }
7977
7978 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7979
7980 let (completion_id, edits, edit_preview) = match edit_prediction {
7981 edit_prediction::EditPrediction::Local {
7982 id,
7983 edits,
7984 edit_preview,
7985 } => (id, edits, edit_preview),
7986 edit_prediction::EditPrediction::Jump {
7987 id,
7988 snapshot,
7989 target,
7990 } => {
7991 self.stale_edit_prediction_in_menu = None;
7992 self.active_edit_prediction = Some(EditPredictionState {
7993 inlay_ids: vec![],
7994 completion: EditPrediction::MoveOutside { snapshot, target },
7995 completion_id: id,
7996 invalidation_range: None,
7997 });
7998 cx.notify();
7999 return Some(());
8000 }
8001 };
8002
8003 let edits = edits
8004 .into_iter()
8005 .flat_map(|(range, new_text)| {
8006 Some((
8007 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8008 new_text,
8009 ))
8010 })
8011 .collect::<Vec<_>>();
8012 if edits.is_empty() {
8013 return None;
8014 }
8015
8016 let first_edit_start = edits.first().unwrap().0.start;
8017 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8018 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8019
8020 let last_edit_end = edits.last().unwrap().0.end;
8021 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8022 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8023
8024 let cursor_row = cursor.to_point(&multibuffer).row;
8025
8026 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8027
8028 let mut inlay_ids = Vec::new();
8029 let invalidation_row_range;
8030 let move_invalidation_row_range = if cursor_row < edit_start_row {
8031 Some(cursor_row..edit_end_row)
8032 } else if cursor_row > edit_end_row {
8033 Some(edit_start_row..cursor_row)
8034 } else {
8035 None
8036 };
8037 let supports_jump = self
8038 .edit_prediction_provider
8039 .as_ref()
8040 .map(|provider| provider.provider.supports_jump_to_edit())
8041 .unwrap_or(true);
8042
8043 let is_move = supports_jump
8044 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8045 let completion = if is_move {
8046 invalidation_row_range =
8047 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8048 let target = first_edit_start;
8049 EditPrediction::MoveWithin { target, snapshot }
8050 } else {
8051 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8052 && !self.edit_predictions_hidden_for_vim_mode;
8053
8054 if show_completions_in_buffer {
8055 if let Some(provider) = &self.edit_prediction_provider {
8056 provider.provider.did_show(cx);
8057 }
8058 if edits
8059 .iter()
8060 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8061 {
8062 let mut inlays = Vec::new();
8063 for (range, new_text) in &edits {
8064 let inlay = Inlay::edit_prediction(
8065 post_inc(&mut self.next_inlay_id),
8066 range.start,
8067 new_text.as_ref(),
8068 );
8069 inlay_ids.push(inlay.id);
8070 inlays.push(inlay);
8071 }
8072
8073 self.splice_inlays(&[], inlays, cx);
8074 } else {
8075 let background_color = cx.theme().status().deleted_background;
8076 self.highlight_text::<EditPredictionHighlight>(
8077 edits.iter().map(|(range, _)| range.clone()).collect(),
8078 HighlightStyle {
8079 background_color: Some(background_color),
8080 ..Default::default()
8081 },
8082 cx,
8083 );
8084 }
8085 }
8086
8087 invalidation_row_range = edit_start_row..edit_end_row;
8088
8089 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8090 if provider.show_tab_accept_marker() {
8091 EditDisplayMode::TabAccept
8092 } else {
8093 EditDisplayMode::Inline
8094 }
8095 } else {
8096 EditDisplayMode::DiffPopover
8097 };
8098
8099 EditPrediction::Edit {
8100 edits,
8101 edit_preview,
8102 display_mode,
8103 snapshot,
8104 }
8105 };
8106
8107 let invalidation_range = multibuffer
8108 .anchor_before(Point::new(invalidation_row_range.start, 0))
8109 ..multibuffer.anchor_after(Point::new(
8110 invalidation_row_range.end,
8111 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8112 ));
8113
8114 self.stale_edit_prediction_in_menu = None;
8115 self.active_edit_prediction = Some(EditPredictionState {
8116 inlay_ids,
8117 completion,
8118 completion_id,
8119 invalidation_range: Some(invalidation_range),
8120 });
8121
8122 cx.notify();
8123
8124 Some(())
8125 }
8126
8127 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8128 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8129 }
8130
8131 fn clear_tasks(&mut self) {
8132 self.tasks.clear()
8133 }
8134
8135 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8136 if self.tasks.insert(key, value).is_some() {
8137 // This case should hopefully be rare, but just in case...
8138 log::error!(
8139 "multiple different run targets found on a single line, only the last target will be rendered"
8140 )
8141 }
8142 }
8143
8144 /// Get all display points of breakpoints that will be rendered within editor
8145 ///
8146 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8147 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8148 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8149 fn active_breakpoints(
8150 &self,
8151 range: Range<DisplayRow>,
8152 window: &mut Window,
8153 cx: &mut Context<Self>,
8154 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8155 let mut breakpoint_display_points = HashMap::default();
8156
8157 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8158 return breakpoint_display_points;
8159 };
8160
8161 let snapshot = self.snapshot(window, cx);
8162
8163 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8164 let Some(project) = self.project() else {
8165 return breakpoint_display_points;
8166 };
8167
8168 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8169 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8170
8171 for (buffer_snapshot, range, excerpt_id) in
8172 multi_buffer_snapshot.range_to_buffer_ranges(range)
8173 {
8174 let Some(buffer) = project
8175 .read(cx)
8176 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8177 else {
8178 continue;
8179 };
8180 let breakpoints = breakpoint_store.read(cx).breakpoints(
8181 &buffer,
8182 Some(
8183 buffer_snapshot.anchor_before(range.start)
8184 ..buffer_snapshot.anchor_after(range.end),
8185 ),
8186 buffer_snapshot,
8187 cx,
8188 );
8189 for (breakpoint, state) in breakpoints {
8190 let multi_buffer_anchor =
8191 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8192 let position = multi_buffer_anchor
8193 .to_point(&multi_buffer_snapshot)
8194 .to_display_point(&snapshot);
8195
8196 breakpoint_display_points.insert(
8197 position.row(),
8198 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8199 );
8200 }
8201 }
8202
8203 breakpoint_display_points
8204 }
8205
8206 fn breakpoint_context_menu(
8207 &self,
8208 anchor: Anchor,
8209 window: &mut Window,
8210 cx: &mut Context<Self>,
8211 ) -> Entity<ui::ContextMenu> {
8212 let weak_editor = cx.weak_entity();
8213 let focus_handle = self.focus_handle(cx);
8214
8215 let row = self
8216 .buffer
8217 .read(cx)
8218 .snapshot(cx)
8219 .summary_for_anchor::<Point>(&anchor)
8220 .row;
8221
8222 let breakpoint = self
8223 .breakpoint_at_row(row, window, cx)
8224 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8225
8226 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8227 "Edit Log Breakpoint"
8228 } else {
8229 "Set Log Breakpoint"
8230 };
8231
8232 let condition_breakpoint_msg = if breakpoint
8233 .as_ref()
8234 .is_some_and(|bp| bp.1.condition.is_some())
8235 {
8236 "Edit Condition Breakpoint"
8237 } else {
8238 "Set Condition Breakpoint"
8239 };
8240
8241 let hit_condition_breakpoint_msg = if breakpoint
8242 .as_ref()
8243 .is_some_and(|bp| bp.1.hit_condition.is_some())
8244 {
8245 "Edit Hit Condition Breakpoint"
8246 } else {
8247 "Set Hit Condition Breakpoint"
8248 };
8249
8250 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8251 "Unset Breakpoint"
8252 } else {
8253 "Set Breakpoint"
8254 };
8255
8256 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8257
8258 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8259 BreakpointState::Enabled => Some("Disable"),
8260 BreakpointState::Disabled => Some("Enable"),
8261 });
8262
8263 let (anchor, breakpoint) =
8264 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8265
8266 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8267 menu.on_blur_subscription(Subscription::new(|| {}))
8268 .context(focus_handle)
8269 .when(run_to_cursor, |this| {
8270 let weak_editor = weak_editor.clone();
8271 this.entry("Run to cursor", None, move |window, cx| {
8272 weak_editor
8273 .update(cx, |editor, cx| {
8274 editor.change_selections(
8275 SelectionEffects::no_scroll(),
8276 window,
8277 cx,
8278 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8279 );
8280 })
8281 .ok();
8282
8283 window.dispatch_action(Box::new(RunToCursor), cx);
8284 })
8285 .separator()
8286 })
8287 .when_some(toggle_state_msg, |this, msg| {
8288 this.entry(msg, None, {
8289 let weak_editor = weak_editor.clone();
8290 let breakpoint = breakpoint.clone();
8291 move |_window, cx| {
8292 weak_editor
8293 .update(cx, |this, cx| {
8294 this.edit_breakpoint_at_anchor(
8295 anchor,
8296 breakpoint.as_ref().clone(),
8297 BreakpointEditAction::InvertState,
8298 cx,
8299 );
8300 })
8301 .log_err();
8302 }
8303 })
8304 })
8305 .entry(set_breakpoint_msg, None, {
8306 let weak_editor = weak_editor.clone();
8307 let breakpoint = breakpoint.clone();
8308 move |_window, cx| {
8309 weak_editor
8310 .update(cx, |this, cx| {
8311 this.edit_breakpoint_at_anchor(
8312 anchor,
8313 breakpoint.as_ref().clone(),
8314 BreakpointEditAction::Toggle,
8315 cx,
8316 );
8317 })
8318 .log_err();
8319 }
8320 })
8321 .entry(log_breakpoint_msg, None, {
8322 let breakpoint = breakpoint.clone();
8323 let weak_editor = weak_editor.clone();
8324 move |window, cx| {
8325 weak_editor
8326 .update(cx, |this, cx| {
8327 this.add_edit_breakpoint_block(
8328 anchor,
8329 breakpoint.as_ref(),
8330 BreakpointPromptEditAction::Log,
8331 window,
8332 cx,
8333 );
8334 })
8335 .log_err();
8336 }
8337 })
8338 .entry(condition_breakpoint_msg, None, {
8339 let breakpoint = breakpoint.clone();
8340 let weak_editor = weak_editor.clone();
8341 move |window, cx| {
8342 weak_editor
8343 .update(cx, |this, cx| {
8344 this.add_edit_breakpoint_block(
8345 anchor,
8346 breakpoint.as_ref(),
8347 BreakpointPromptEditAction::Condition,
8348 window,
8349 cx,
8350 );
8351 })
8352 .log_err();
8353 }
8354 })
8355 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8356 weak_editor
8357 .update(cx, |this, cx| {
8358 this.add_edit_breakpoint_block(
8359 anchor,
8360 breakpoint.as_ref(),
8361 BreakpointPromptEditAction::HitCondition,
8362 window,
8363 cx,
8364 );
8365 })
8366 .log_err();
8367 })
8368 })
8369 }
8370
8371 fn render_breakpoint(
8372 &self,
8373 position: Anchor,
8374 row: DisplayRow,
8375 breakpoint: &Breakpoint,
8376 state: Option<BreakpointSessionState>,
8377 cx: &mut Context<Self>,
8378 ) -> IconButton {
8379 let is_rejected = state.is_some_and(|s| !s.verified);
8380 // Is it a breakpoint that shows up when hovering over gutter?
8381 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8382 (false, false),
8383 |PhantomBreakpointIndicator {
8384 is_active,
8385 display_row,
8386 collides_with_existing_breakpoint,
8387 }| {
8388 (
8389 is_active && display_row == row,
8390 collides_with_existing_breakpoint,
8391 )
8392 },
8393 );
8394
8395 let (color, icon) = {
8396 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8397 (false, false) => ui::IconName::DebugBreakpoint,
8398 (true, false) => ui::IconName::DebugLogBreakpoint,
8399 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8400 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8401 };
8402
8403 let color = if is_phantom {
8404 Color::Hint
8405 } else if is_rejected {
8406 Color::Disabled
8407 } else {
8408 Color::Debugger
8409 };
8410
8411 (color, icon)
8412 };
8413
8414 let breakpoint = Arc::from(breakpoint.clone());
8415
8416 let alt_as_text = gpui::Keystroke {
8417 modifiers: Modifiers::secondary_key(),
8418 ..Default::default()
8419 };
8420 let primary_action_text = if breakpoint.is_disabled() {
8421 "Enable breakpoint"
8422 } else if is_phantom && !collides_with_existing {
8423 "Set breakpoint"
8424 } else {
8425 "Unset breakpoint"
8426 };
8427 let focus_handle = self.focus_handle.clone();
8428
8429 let meta = if is_rejected {
8430 SharedString::from("No executable code is associated with this line.")
8431 } else if collides_with_existing && !breakpoint.is_disabled() {
8432 SharedString::from(format!(
8433 "{alt_as_text}-click to disable,\nright-click for more options."
8434 ))
8435 } else {
8436 SharedString::from("Right-click for more options.")
8437 };
8438 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8439 .icon_size(IconSize::XSmall)
8440 .size(ui::ButtonSize::None)
8441 .when(is_rejected, |this| {
8442 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8443 })
8444 .icon_color(color)
8445 .style(ButtonStyle::Transparent)
8446 .on_click(cx.listener({
8447 move |editor, event: &ClickEvent, window, cx| {
8448 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8449 BreakpointEditAction::InvertState
8450 } else {
8451 BreakpointEditAction::Toggle
8452 };
8453
8454 window.focus(&editor.focus_handle(cx));
8455 editor.edit_breakpoint_at_anchor(
8456 position,
8457 breakpoint.as_ref().clone(),
8458 edit_action,
8459 cx,
8460 );
8461 }
8462 }))
8463 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8464 editor.set_breakpoint_context_menu(
8465 row,
8466 Some(position),
8467 event.position(),
8468 window,
8469 cx,
8470 );
8471 }))
8472 .tooltip(move |_window, cx| {
8473 Tooltip::with_meta_in(
8474 primary_action_text,
8475 Some(&ToggleBreakpoint),
8476 meta.clone(),
8477 &focus_handle,
8478 cx,
8479 )
8480 })
8481 }
8482
8483 fn build_tasks_context(
8484 project: &Entity<Project>,
8485 buffer: &Entity<Buffer>,
8486 buffer_row: u32,
8487 tasks: &Arc<RunnableTasks>,
8488 cx: &mut Context<Self>,
8489 ) -> Task<Option<task::TaskContext>> {
8490 let position = Point::new(buffer_row, tasks.column);
8491 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8492 let location = Location {
8493 buffer: buffer.clone(),
8494 range: range_start..range_start,
8495 };
8496 // Fill in the environmental variables from the tree-sitter captures
8497 let mut captured_task_variables = TaskVariables::default();
8498 for (capture_name, value) in tasks.extra_variables.clone() {
8499 captured_task_variables.insert(
8500 task::VariableName::Custom(capture_name.into()),
8501 value.clone(),
8502 );
8503 }
8504 project.update(cx, |project, cx| {
8505 project.task_store().update(cx, |task_store, cx| {
8506 task_store.task_context_for_location(captured_task_variables, location, cx)
8507 })
8508 })
8509 }
8510
8511 pub fn spawn_nearest_task(
8512 &mut self,
8513 action: &SpawnNearestTask,
8514 window: &mut Window,
8515 cx: &mut Context<Self>,
8516 ) {
8517 let Some((workspace, _)) = self.workspace.clone() else {
8518 return;
8519 };
8520 let Some(project) = self.project.clone() else {
8521 return;
8522 };
8523
8524 // Try to find a closest, enclosing node using tree-sitter that has a task
8525 let Some((buffer, buffer_row, tasks)) = self
8526 .find_enclosing_node_task(cx)
8527 // Or find the task that's closest in row-distance.
8528 .or_else(|| self.find_closest_task(cx))
8529 else {
8530 return;
8531 };
8532
8533 let reveal_strategy = action.reveal;
8534 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8535 cx.spawn_in(window, async move |_, cx| {
8536 let context = task_context.await?;
8537 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8538
8539 let resolved = &mut resolved_task.resolved;
8540 resolved.reveal = reveal_strategy;
8541
8542 workspace
8543 .update_in(cx, |workspace, window, cx| {
8544 workspace.schedule_resolved_task(
8545 task_source_kind,
8546 resolved_task,
8547 false,
8548 window,
8549 cx,
8550 );
8551 })
8552 .ok()
8553 })
8554 .detach();
8555 }
8556
8557 fn find_closest_task(
8558 &mut self,
8559 cx: &mut Context<Self>,
8560 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8561 let cursor_row = self
8562 .selections
8563 .newest_adjusted(&self.display_snapshot(cx))
8564 .head()
8565 .row;
8566
8567 let ((buffer_id, row), tasks) = self
8568 .tasks
8569 .iter()
8570 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8571
8572 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8573 let tasks = Arc::new(tasks.to_owned());
8574 Some((buffer, *row, tasks))
8575 }
8576
8577 fn find_enclosing_node_task(
8578 &mut self,
8579 cx: &mut Context<Self>,
8580 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8581 let snapshot = self.buffer.read(cx).snapshot(cx);
8582 let offset = self
8583 .selections
8584 .newest::<usize>(&self.display_snapshot(cx))
8585 .head();
8586 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8587 let buffer_id = excerpt.buffer().remote_id();
8588
8589 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8590 let mut cursor = layer.node().walk();
8591
8592 while cursor.goto_first_child_for_byte(offset).is_some() {
8593 if cursor.node().end_byte() == offset {
8594 cursor.goto_next_sibling();
8595 }
8596 }
8597
8598 // Ascend to the smallest ancestor that contains the range and has a task.
8599 loop {
8600 let node = cursor.node();
8601 let node_range = node.byte_range();
8602 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8603
8604 // Check if this node contains our offset
8605 if node_range.start <= offset && node_range.end >= offset {
8606 // If it contains offset, check for task
8607 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8608 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8609 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8610 }
8611 }
8612
8613 if !cursor.goto_parent() {
8614 break;
8615 }
8616 }
8617 None
8618 }
8619
8620 fn render_run_indicator(
8621 &self,
8622 _style: &EditorStyle,
8623 is_active: bool,
8624 row: DisplayRow,
8625 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8626 cx: &mut Context<Self>,
8627 ) -> IconButton {
8628 let color = Color::Muted;
8629 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8630
8631 IconButton::new(
8632 ("run_indicator", row.0 as usize),
8633 ui::IconName::PlayOutlined,
8634 )
8635 .shape(ui::IconButtonShape::Square)
8636 .icon_size(IconSize::XSmall)
8637 .icon_color(color)
8638 .toggle_state(is_active)
8639 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8640 let quick_launch = match e {
8641 ClickEvent::Keyboard(_) => true,
8642 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8643 };
8644
8645 window.focus(&editor.focus_handle(cx));
8646 editor.toggle_code_actions(
8647 &ToggleCodeActions {
8648 deployed_from: Some(CodeActionSource::RunMenu(row)),
8649 quick_launch,
8650 },
8651 window,
8652 cx,
8653 );
8654 }))
8655 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8656 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8657 }))
8658 }
8659
8660 pub fn context_menu_visible(&self) -> bool {
8661 !self.edit_prediction_preview_is_active()
8662 && self
8663 .context_menu
8664 .borrow()
8665 .as_ref()
8666 .is_some_and(|menu| menu.visible())
8667 }
8668
8669 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8670 self.context_menu
8671 .borrow()
8672 .as_ref()
8673 .map(|menu| menu.origin())
8674 }
8675
8676 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8677 self.context_menu_options = Some(options);
8678 }
8679
8680 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8681 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8682
8683 fn render_edit_prediction_popover(
8684 &mut self,
8685 text_bounds: &Bounds<Pixels>,
8686 content_origin: gpui::Point<Pixels>,
8687 right_margin: Pixels,
8688 editor_snapshot: &EditorSnapshot,
8689 visible_row_range: Range<DisplayRow>,
8690 scroll_top: ScrollOffset,
8691 scroll_bottom: ScrollOffset,
8692 line_layouts: &[LineWithInvisibles],
8693 line_height: Pixels,
8694 scroll_position: gpui::Point<ScrollOffset>,
8695 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8696 newest_selection_head: Option<DisplayPoint>,
8697 editor_width: Pixels,
8698 style: &EditorStyle,
8699 window: &mut Window,
8700 cx: &mut App,
8701 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8702 if self.mode().is_minimap() {
8703 return None;
8704 }
8705 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8706
8707 if self.edit_prediction_visible_in_cursor_popover(true) {
8708 return None;
8709 }
8710
8711 match &active_edit_prediction.completion {
8712 EditPrediction::MoveWithin { target, .. } => {
8713 let target_display_point = target.to_display_point(editor_snapshot);
8714
8715 if self.edit_prediction_requires_modifier() {
8716 if !self.edit_prediction_preview_is_active() {
8717 return None;
8718 }
8719
8720 self.render_edit_prediction_modifier_jump_popover(
8721 text_bounds,
8722 content_origin,
8723 visible_row_range,
8724 line_layouts,
8725 line_height,
8726 scroll_pixel_position,
8727 newest_selection_head,
8728 target_display_point,
8729 window,
8730 cx,
8731 )
8732 } else {
8733 self.render_edit_prediction_eager_jump_popover(
8734 text_bounds,
8735 content_origin,
8736 editor_snapshot,
8737 visible_row_range,
8738 scroll_top,
8739 scroll_bottom,
8740 line_height,
8741 scroll_pixel_position,
8742 target_display_point,
8743 editor_width,
8744 window,
8745 cx,
8746 )
8747 }
8748 }
8749 EditPrediction::Edit {
8750 display_mode: EditDisplayMode::Inline,
8751 ..
8752 } => None,
8753 EditPrediction::Edit {
8754 display_mode: EditDisplayMode::TabAccept,
8755 edits,
8756 ..
8757 } => {
8758 let range = &edits.first()?.0;
8759 let target_display_point = range.end.to_display_point(editor_snapshot);
8760
8761 self.render_edit_prediction_end_of_line_popover(
8762 "Accept",
8763 editor_snapshot,
8764 visible_row_range,
8765 target_display_point,
8766 line_height,
8767 scroll_pixel_position,
8768 content_origin,
8769 editor_width,
8770 window,
8771 cx,
8772 )
8773 }
8774 EditPrediction::Edit {
8775 edits,
8776 edit_preview,
8777 display_mode: EditDisplayMode::DiffPopover,
8778 snapshot,
8779 } => self.render_edit_prediction_diff_popover(
8780 text_bounds,
8781 content_origin,
8782 right_margin,
8783 editor_snapshot,
8784 visible_row_range,
8785 line_layouts,
8786 line_height,
8787 scroll_position,
8788 scroll_pixel_position,
8789 newest_selection_head,
8790 editor_width,
8791 style,
8792 edits,
8793 edit_preview,
8794 snapshot,
8795 window,
8796 cx,
8797 ),
8798 EditPrediction::MoveOutside { snapshot, .. } => {
8799 let file_name = snapshot
8800 .file()
8801 .map(|file| file.file_name(cx))
8802 .unwrap_or("untitled");
8803 let mut element = self
8804 .render_edit_prediction_line_popover(
8805 format!("Jump to {file_name}"),
8806 Some(IconName::ZedPredict),
8807 window,
8808 cx,
8809 )
8810 .into_any();
8811
8812 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8813 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8814 let origin_y = text_bounds.size.height - size.height - px(30.);
8815 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8816 element.prepaint_at(origin, window, cx);
8817
8818 Some((element, origin))
8819 }
8820 }
8821 }
8822
8823 fn render_edit_prediction_modifier_jump_popover(
8824 &mut self,
8825 text_bounds: &Bounds<Pixels>,
8826 content_origin: gpui::Point<Pixels>,
8827 visible_row_range: Range<DisplayRow>,
8828 line_layouts: &[LineWithInvisibles],
8829 line_height: Pixels,
8830 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8831 newest_selection_head: Option<DisplayPoint>,
8832 target_display_point: DisplayPoint,
8833 window: &mut Window,
8834 cx: &mut App,
8835 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8836 let scrolled_content_origin =
8837 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8838
8839 const SCROLL_PADDING_Y: Pixels = px(12.);
8840
8841 if target_display_point.row() < visible_row_range.start {
8842 return self.render_edit_prediction_scroll_popover(
8843 |_| SCROLL_PADDING_Y,
8844 IconName::ArrowUp,
8845 visible_row_range,
8846 line_layouts,
8847 newest_selection_head,
8848 scrolled_content_origin,
8849 window,
8850 cx,
8851 );
8852 } else if target_display_point.row() >= visible_row_range.end {
8853 return self.render_edit_prediction_scroll_popover(
8854 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8855 IconName::ArrowDown,
8856 visible_row_range,
8857 line_layouts,
8858 newest_selection_head,
8859 scrolled_content_origin,
8860 window,
8861 cx,
8862 );
8863 }
8864
8865 const POLE_WIDTH: Pixels = px(2.);
8866
8867 let line_layout =
8868 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8869 let target_column = target_display_point.column() as usize;
8870
8871 let target_x = line_layout.x_for_index(target_column);
8872 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8873 - scroll_pixel_position.y;
8874
8875 let flag_on_right = target_x < text_bounds.size.width / 2.;
8876
8877 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8878 border_color.l += 0.001;
8879
8880 let mut element = v_flex()
8881 .items_end()
8882 .when(flag_on_right, |el| el.items_start())
8883 .child(if flag_on_right {
8884 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8885 .rounded_bl(px(0.))
8886 .rounded_tl(px(0.))
8887 .border_l_2()
8888 .border_color(border_color)
8889 } else {
8890 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8891 .rounded_br(px(0.))
8892 .rounded_tr(px(0.))
8893 .border_r_2()
8894 .border_color(border_color)
8895 })
8896 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8897 .into_any();
8898
8899 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8900
8901 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8902 - point(
8903 if flag_on_right {
8904 POLE_WIDTH
8905 } else {
8906 size.width - POLE_WIDTH
8907 },
8908 size.height - line_height,
8909 );
8910
8911 origin.x = origin.x.max(content_origin.x);
8912
8913 element.prepaint_at(origin, window, cx);
8914
8915 Some((element, origin))
8916 }
8917
8918 fn render_edit_prediction_scroll_popover(
8919 &mut self,
8920 to_y: impl Fn(Size<Pixels>) -> Pixels,
8921 scroll_icon: IconName,
8922 visible_row_range: Range<DisplayRow>,
8923 line_layouts: &[LineWithInvisibles],
8924 newest_selection_head: Option<DisplayPoint>,
8925 scrolled_content_origin: gpui::Point<Pixels>,
8926 window: &mut Window,
8927 cx: &mut App,
8928 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8929 let mut element = self
8930 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8931 .into_any();
8932
8933 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8934
8935 let cursor = newest_selection_head?;
8936 let cursor_row_layout =
8937 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8938 let cursor_column = cursor.column() as usize;
8939
8940 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8941
8942 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8943
8944 element.prepaint_at(origin, window, cx);
8945 Some((element, origin))
8946 }
8947
8948 fn render_edit_prediction_eager_jump_popover(
8949 &mut self,
8950 text_bounds: &Bounds<Pixels>,
8951 content_origin: gpui::Point<Pixels>,
8952 editor_snapshot: &EditorSnapshot,
8953 visible_row_range: Range<DisplayRow>,
8954 scroll_top: ScrollOffset,
8955 scroll_bottom: ScrollOffset,
8956 line_height: Pixels,
8957 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8958 target_display_point: DisplayPoint,
8959 editor_width: Pixels,
8960 window: &mut Window,
8961 cx: &mut App,
8962 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8963 if target_display_point.row().as_f64() < scroll_top {
8964 let mut element = self
8965 .render_edit_prediction_line_popover(
8966 "Jump to Edit",
8967 Some(IconName::ArrowUp),
8968 window,
8969 cx,
8970 )
8971 .into_any();
8972
8973 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8974 let offset = point(
8975 (text_bounds.size.width - size.width) / 2.,
8976 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8977 );
8978
8979 let origin = text_bounds.origin + offset;
8980 element.prepaint_at(origin, window, cx);
8981 Some((element, origin))
8982 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8983 let mut element = self
8984 .render_edit_prediction_line_popover(
8985 "Jump to Edit",
8986 Some(IconName::ArrowDown),
8987 window,
8988 cx,
8989 )
8990 .into_any();
8991
8992 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8993 let offset = point(
8994 (text_bounds.size.width - size.width) / 2.,
8995 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8996 );
8997
8998 let origin = text_bounds.origin + offset;
8999 element.prepaint_at(origin, window, cx);
9000 Some((element, origin))
9001 } else {
9002 self.render_edit_prediction_end_of_line_popover(
9003 "Jump to Edit",
9004 editor_snapshot,
9005 visible_row_range,
9006 target_display_point,
9007 line_height,
9008 scroll_pixel_position,
9009 content_origin,
9010 editor_width,
9011 window,
9012 cx,
9013 )
9014 }
9015 }
9016
9017 fn render_edit_prediction_end_of_line_popover(
9018 self: &mut Editor,
9019 label: &'static str,
9020 editor_snapshot: &EditorSnapshot,
9021 visible_row_range: Range<DisplayRow>,
9022 target_display_point: DisplayPoint,
9023 line_height: Pixels,
9024 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9025 content_origin: gpui::Point<Pixels>,
9026 editor_width: Pixels,
9027 window: &mut Window,
9028 cx: &mut App,
9029 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9030 let target_line_end = DisplayPoint::new(
9031 target_display_point.row(),
9032 editor_snapshot.line_len(target_display_point.row()),
9033 );
9034
9035 let mut element = self
9036 .render_edit_prediction_line_popover(label, None, window, cx)
9037 .into_any();
9038
9039 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9040
9041 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9042
9043 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9044 let mut origin = start_point
9045 + line_origin
9046 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9047 origin.x = origin.x.max(content_origin.x);
9048
9049 let max_x = content_origin.x + editor_width - size.width;
9050
9051 if origin.x > max_x {
9052 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9053
9054 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9055 origin.y += offset;
9056 IconName::ArrowUp
9057 } else {
9058 origin.y -= offset;
9059 IconName::ArrowDown
9060 };
9061
9062 element = self
9063 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9064 .into_any();
9065
9066 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9067
9068 origin.x = content_origin.x + editor_width - size.width - px(2.);
9069 }
9070
9071 element.prepaint_at(origin, window, cx);
9072 Some((element, origin))
9073 }
9074
9075 fn render_edit_prediction_diff_popover(
9076 self: &Editor,
9077 text_bounds: &Bounds<Pixels>,
9078 content_origin: gpui::Point<Pixels>,
9079 right_margin: Pixels,
9080 editor_snapshot: &EditorSnapshot,
9081 visible_row_range: Range<DisplayRow>,
9082 line_layouts: &[LineWithInvisibles],
9083 line_height: Pixels,
9084 scroll_position: gpui::Point<ScrollOffset>,
9085 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9086 newest_selection_head: Option<DisplayPoint>,
9087 editor_width: Pixels,
9088 style: &EditorStyle,
9089 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9090 edit_preview: &Option<language::EditPreview>,
9091 snapshot: &language::BufferSnapshot,
9092 window: &mut Window,
9093 cx: &mut App,
9094 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9095 let edit_start = edits
9096 .first()
9097 .unwrap()
9098 .0
9099 .start
9100 .to_display_point(editor_snapshot);
9101 let edit_end = edits
9102 .last()
9103 .unwrap()
9104 .0
9105 .end
9106 .to_display_point(editor_snapshot);
9107
9108 let is_visible = visible_row_range.contains(&edit_start.row())
9109 || visible_row_range.contains(&edit_end.row());
9110 if !is_visible {
9111 return None;
9112 }
9113
9114 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9115 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9116 } else {
9117 // Fallback for providers without edit_preview
9118 crate::edit_prediction_fallback_text(edits, cx)
9119 };
9120
9121 let styled_text = highlighted_edits.to_styled_text(&style.text);
9122 let line_count = highlighted_edits.text.lines().count();
9123
9124 const BORDER_WIDTH: Pixels = px(1.);
9125
9126 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9127 let has_keybind = keybind.is_some();
9128
9129 let mut element = h_flex()
9130 .items_start()
9131 .child(
9132 h_flex()
9133 .bg(cx.theme().colors().editor_background)
9134 .border(BORDER_WIDTH)
9135 .shadow_xs()
9136 .border_color(cx.theme().colors().border)
9137 .rounded_l_lg()
9138 .when(line_count > 1, |el| el.rounded_br_lg())
9139 .pr_1()
9140 .child(styled_text),
9141 )
9142 .child(
9143 h_flex()
9144 .h(line_height + BORDER_WIDTH * 2.)
9145 .px_1p5()
9146 .gap_1()
9147 // Workaround: For some reason, there's a gap if we don't do this
9148 .ml(-BORDER_WIDTH)
9149 .shadow(vec![gpui::BoxShadow {
9150 color: gpui::black().opacity(0.05),
9151 offset: point(px(1.), px(1.)),
9152 blur_radius: px(2.),
9153 spread_radius: px(0.),
9154 }])
9155 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9156 .border(BORDER_WIDTH)
9157 .border_color(cx.theme().colors().border)
9158 .rounded_r_lg()
9159 .id("edit_prediction_diff_popover_keybind")
9160 .when(!has_keybind, |el| {
9161 let status_colors = cx.theme().status();
9162
9163 el.bg(status_colors.error_background)
9164 .border_color(status_colors.error.opacity(0.6))
9165 .child(Icon::new(IconName::Info).color(Color::Error))
9166 .cursor_default()
9167 .hoverable_tooltip(move |_window, cx| {
9168 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9169 })
9170 })
9171 .children(keybind),
9172 )
9173 .into_any();
9174
9175 let longest_row =
9176 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9177 let longest_line_width = if visible_row_range.contains(&longest_row) {
9178 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9179 } else {
9180 layout_line(
9181 longest_row,
9182 editor_snapshot,
9183 style,
9184 editor_width,
9185 |_| false,
9186 window,
9187 cx,
9188 )
9189 .width
9190 };
9191
9192 let viewport_bounds =
9193 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9194 right: -right_margin,
9195 ..Default::default()
9196 });
9197
9198 let x_after_longest = Pixels::from(
9199 ScrollPixelOffset::from(
9200 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9201 ) - scroll_pixel_position.x,
9202 );
9203
9204 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9205
9206 // Fully visible if it can be displayed within the window (allow overlapping other
9207 // panes). However, this is only allowed if the popover starts within text_bounds.
9208 let can_position_to_the_right = x_after_longest < text_bounds.right()
9209 && x_after_longest + element_bounds.width < viewport_bounds.right();
9210
9211 let mut origin = if can_position_to_the_right {
9212 point(
9213 x_after_longest,
9214 text_bounds.origin.y
9215 + Pixels::from(
9216 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9217 - scroll_pixel_position.y,
9218 ),
9219 )
9220 } else {
9221 let cursor_row = newest_selection_head.map(|head| head.row());
9222 let above_edit = edit_start
9223 .row()
9224 .0
9225 .checked_sub(line_count as u32)
9226 .map(DisplayRow);
9227 let below_edit = Some(edit_end.row() + 1);
9228 let above_cursor =
9229 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9230 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9231
9232 // Place the edit popover adjacent to the edit if there is a location
9233 // available that is onscreen and does not obscure the cursor. Otherwise,
9234 // place it adjacent to the cursor.
9235 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9236 .into_iter()
9237 .flatten()
9238 .find(|&start_row| {
9239 let end_row = start_row + line_count as u32;
9240 visible_row_range.contains(&start_row)
9241 && visible_row_range.contains(&end_row)
9242 && cursor_row
9243 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9244 })?;
9245
9246 content_origin
9247 + point(
9248 Pixels::from(-scroll_pixel_position.x),
9249 Pixels::from(
9250 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9251 ),
9252 )
9253 };
9254
9255 origin.x -= BORDER_WIDTH;
9256
9257 window.defer_draw(element, origin, 1);
9258
9259 // Do not return an element, since it will already be drawn due to defer_draw.
9260 None
9261 }
9262
9263 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9264 px(30.)
9265 }
9266
9267 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9268 if self.read_only(cx) {
9269 cx.theme().players().read_only()
9270 } else {
9271 self.style.as_ref().unwrap().local_player
9272 }
9273 }
9274
9275 fn render_edit_prediction_accept_keybind(
9276 &self,
9277 window: &mut Window,
9278 cx: &mut App,
9279 ) -> Option<AnyElement> {
9280 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9281 let accept_keystroke = accept_binding.keystroke()?;
9282
9283 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9284
9285 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9286 Color::Accent
9287 } else {
9288 Color::Muted
9289 };
9290
9291 h_flex()
9292 .px_0p5()
9293 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9294 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9295 .text_size(TextSize::XSmall.rems(cx))
9296 .child(h_flex().children(ui::render_modifiers(
9297 accept_keystroke.modifiers(),
9298 PlatformStyle::platform(),
9299 Some(modifiers_color),
9300 Some(IconSize::XSmall.rems().into()),
9301 true,
9302 )))
9303 .when(is_platform_style_mac, |parent| {
9304 parent.child(accept_keystroke.key().to_string())
9305 })
9306 .when(!is_platform_style_mac, |parent| {
9307 parent.child(
9308 Key::new(
9309 util::capitalize(accept_keystroke.key()),
9310 Some(Color::Default),
9311 )
9312 .size(Some(IconSize::XSmall.rems().into())),
9313 )
9314 })
9315 .into_any()
9316 .into()
9317 }
9318
9319 fn render_edit_prediction_line_popover(
9320 &self,
9321 label: impl Into<SharedString>,
9322 icon: Option<IconName>,
9323 window: &mut Window,
9324 cx: &mut App,
9325 ) -> Stateful<Div> {
9326 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9327
9328 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9329 let has_keybind = keybind.is_some();
9330
9331 h_flex()
9332 .id("ep-line-popover")
9333 .py_0p5()
9334 .pl_1()
9335 .pr(padding_right)
9336 .gap_1()
9337 .rounded_md()
9338 .border_1()
9339 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9340 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9341 .shadow_xs()
9342 .when(!has_keybind, |el| {
9343 let status_colors = cx.theme().status();
9344
9345 el.bg(status_colors.error_background)
9346 .border_color(status_colors.error.opacity(0.6))
9347 .pl_2()
9348 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9349 .cursor_default()
9350 .hoverable_tooltip(move |_window, cx| {
9351 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9352 })
9353 })
9354 .children(keybind)
9355 .child(
9356 Label::new(label)
9357 .size(LabelSize::Small)
9358 .when(!has_keybind, |el| {
9359 el.color(cx.theme().status().error.into()).strikethrough()
9360 }),
9361 )
9362 .when(!has_keybind, |el| {
9363 el.child(
9364 h_flex().ml_1().child(
9365 Icon::new(IconName::Info)
9366 .size(IconSize::Small)
9367 .color(cx.theme().status().error.into()),
9368 ),
9369 )
9370 })
9371 .when_some(icon, |element, icon| {
9372 element.child(
9373 div()
9374 .mt(px(1.5))
9375 .child(Icon::new(icon).size(IconSize::Small)),
9376 )
9377 })
9378 }
9379
9380 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9381 let accent_color = cx.theme().colors().text_accent;
9382 let editor_bg_color = cx.theme().colors().editor_background;
9383 editor_bg_color.blend(accent_color.opacity(0.1))
9384 }
9385
9386 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9387 let accent_color = cx.theme().colors().text_accent;
9388 let editor_bg_color = cx.theme().colors().editor_background;
9389 editor_bg_color.blend(accent_color.opacity(0.6))
9390 }
9391 fn get_prediction_provider_icon_name(
9392 provider: &Option<RegisteredEditPredictionProvider>,
9393 ) -> IconName {
9394 match provider {
9395 Some(provider) => match provider.provider.name() {
9396 "copilot" => IconName::Copilot,
9397 "supermaven" => IconName::Supermaven,
9398 _ => IconName::ZedPredict,
9399 },
9400 None => IconName::ZedPredict,
9401 }
9402 }
9403
9404 fn render_edit_prediction_cursor_popover(
9405 &self,
9406 min_width: Pixels,
9407 max_width: Pixels,
9408 cursor_point: Point,
9409 style: &EditorStyle,
9410 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9411 _window: &Window,
9412 cx: &mut Context<Editor>,
9413 ) -> Option<AnyElement> {
9414 let provider = self.edit_prediction_provider.as_ref()?;
9415 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9416
9417 let is_refreshing = provider.provider.is_refreshing(cx);
9418
9419 fn pending_completion_container(icon: IconName) -> Div {
9420 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9421 }
9422
9423 let completion = match &self.active_edit_prediction {
9424 Some(prediction) => {
9425 if !self.has_visible_completions_menu() {
9426 const RADIUS: Pixels = px(6.);
9427 const BORDER_WIDTH: Pixels = px(1.);
9428
9429 return Some(
9430 h_flex()
9431 .elevation_2(cx)
9432 .border(BORDER_WIDTH)
9433 .border_color(cx.theme().colors().border)
9434 .when(accept_keystroke.is_none(), |el| {
9435 el.border_color(cx.theme().status().error)
9436 })
9437 .rounded(RADIUS)
9438 .rounded_tl(px(0.))
9439 .overflow_hidden()
9440 .child(div().px_1p5().child(match &prediction.completion {
9441 EditPrediction::MoveWithin { target, snapshot } => {
9442 use text::ToPoint as _;
9443 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9444 {
9445 Icon::new(IconName::ZedPredictDown)
9446 } else {
9447 Icon::new(IconName::ZedPredictUp)
9448 }
9449 }
9450 EditPrediction::MoveOutside { .. } => {
9451 // TODO [zeta2] custom icon for external jump?
9452 Icon::new(provider_icon)
9453 }
9454 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9455 }))
9456 .child(
9457 h_flex()
9458 .gap_1()
9459 .py_1()
9460 .px_2()
9461 .rounded_r(RADIUS - BORDER_WIDTH)
9462 .border_l_1()
9463 .border_color(cx.theme().colors().border)
9464 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9465 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9466 el.child(
9467 Label::new("Hold")
9468 .size(LabelSize::Small)
9469 .when(accept_keystroke.is_none(), |el| {
9470 el.strikethrough()
9471 })
9472 .line_height_style(LineHeightStyle::UiLabel),
9473 )
9474 })
9475 .id("edit_prediction_cursor_popover_keybind")
9476 .when(accept_keystroke.is_none(), |el| {
9477 let status_colors = cx.theme().status();
9478
9479 el.bg(status_colors.error_background)
9480 .border_color(status_colors.error.opacity(0.6))
9481 .child(Icon::new(IconName::Info).color(Color::Error))
9482 .cursor_default()
9483 .hoverable_tooltip(move |_window, cx| {
9484 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9485 .into()
9486 })
9487 })
9488 .when_some(
9489 accept_keystroke.as_ref(),
9490 |el, accept_keystroke| {
9491 el.child(h_flex().children(ui::render_modifiers(
9492 accept_keystroke.modifiers(),
9493 PlatformStyle::platform(),
9494 Some(Color::Default),
9495 Some(IconSize::XSmall.rems().into()),
9496 false,
9497 )))
9498 },
9499 ),
9500 )
9501 .into_any(),
9502 );
9503 }
9504
9505 self.render_edit_prediction_cursor_popover_preview(
9506 prediction,
9507 cursor_point,
9508 style,
9509 cx,
9510 )?
9511 }
9512
9513 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9514 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9515 stale_completion,
9516 cursor_point,
9517 style,
9518 cx,
9519 )?,
9520
9521 None => pending_completion_container(provider_icon)
9522 .child(Label::new("...").size(LabelSize::Small)),
9523 },
9524
9525 None => pending_completion_container(provider_icon)
9526 .child(Label::new("...").size(LabelSize::Small)),
9527 };
9528
9529 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9530 completion
9531 .with_animation(
9532 "loading-completion",
9533 Animation::new(Duration::from_secs(2))
9534 .repeat()
9535 .with_easing(pulsating_between(0.4, 0.8)),
9536 |label, delta| label.opacity(delta),
9537 )
9538 .into_any_element()
9539 } else {
9540 completion.into_any_element()
9541 };
9542
9543 let has_completion = self.active_edit_prediction.is_some();
9544
9545 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9546 Some(
9547 h_flex()
9548 .min_w(min_width)
9549 .max_w(max_width)
9550 .flex_1()
9551 .elevation_2(cx)
9552 .border_color(cx.theme().colors().border)
9553 .child(
9554 div()
9555 .flex_1()
9556 .py_1()
9557 .px_2()
9558 .overflow_hidden()
9559 .child(completion),
9560 )
9561 .when_some(accept_keystroke, |el, accept_keystroke| {
9562 if !accept_keystroke.modifiers().modified() {
9563 return el;
9564 }
9565
9566 el.child(
9567 h_flex()
9568 .h_full()
9569 .border_l_1()
9570 .rounded_r_lg()
9571 .border_color(cx.theme().colors().border)
9572 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9573 .gap_1()
9574 .py_1()
9575 .px_2()
9576 .child(
9577 h_flex()
9578 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9579 .when(is_platform_style_mac, |parent| parent.gap_1())
9580 .child(h_flex().children(ui::render_modifiers(
9581 accept_keystroke.modifiers(),
9582 PlatformStyle::platform(),
9583 Some(if !has_completion {
9584 Color::Muted
9585 } else {
9586 Color::Default
9587 }),
9588 None,
9589 false,
9590 ))),
9591 )
9592 .child(Label::new("Preview").into_any_element())
9593 .opacity(if has_completion { 1.0 } else { 0.4 }),
9594 )
9595 })
9596 .into_any(),
9597 )
9598 }
9599
9600 fn render_edit_prediction_cursor_popover_preview(
9601 &self,
9602 completion: &EditPredictionState,
9603 cursor_point: Point,
9604 style: &EditorStyle,
9605 cx: &mut Context<Editor>,
9606 ) -> Option<Div> {
9607 use text::ToPoint as _;
9608
9609 fn render_relative_row_jump(
9610 prefix: impl Into<String>,
9611 current_row: u32,
9612 target_row: u32,
9613 ) -> Div {
9614 let (row_diff, arrow) = if target_row < current_row {
9615 (current_row - target_row, IconName::ArrowUp)
9616 } else {
9617 (target_row - current_row, IconName::ArrowDown)
9618 };
9619
9620 h_flex()
9621 .child(
9622 Label::new(format!("{}{}", prefix.into(), row_diff))
9623 .color(Color::Muted)
9624 .size(LabelSize::Small),
9625 )
9626 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9627 }
9628
9629 let supports_jump = self
9630 .edit_prediction_provider
9631 .as_ref()
9632 .map(|provider| provider.provider.supports_jump_to_edit())
9633 .unwrap_or(true);
9634
9635 match &completion.completion {
9636 EditPrediction::MoveWithin {
9637 target, snapshot, ..
9638 } => {
9639 if !supports_jump {
9640 return None;
9641 }
9642
9643 Some(
9644 h_flex()
9645 .px_2()
9646 .gap_2()
9647 .flex_1()
9648 .child(
9649 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9650 Icon::new(IconName::ZedPredictDown)
9651 } else {
9652 Icon::new(IconName::ZedPredictUp)
9653 },
9654 )
9655 .child(Label::new("Jump to Edit")),
9656 )
9657 }
9658 EditPrediction::MoveOutside { snapshot, .. } => {
9659 let file_name = snapshot
9660 .file()
9661 .map(|file| file.file_name(cx))
9662 .unwrap_or("untitled");
9663 Some(
9664 h_flex()
9665 .px_2()
9666 .gap_2()
9667 .flex_1()
9668 .child(Icon::new(IconName::ZedPredict))
9669 .child(Label::new(format!("Jump to {file_name}"))),
9670 )
9671 }
9672 EditPrediction::Edit {
9673 edits,
9674 edit_preview,
9675 snapshot,
9676 display_mode: _,
9677 } => {
9678 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9679
9680 let (highlighted_edits, has_more_lines) =
9681 if let Some(edit_preview) = edit_preview.as_ref() {
9682 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9683 .first_line_preview()
9684 } else {
9685 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9686 };
9687
9688 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9689 .with_default_highlights(&style.text, highlighted_edits.highlights);
9690
9691 let preview = h_flex()
9692 .gap_1()
9693 .min_w_16()
9694 .child(styled_text)
9695 .when(has_more_lines, |parent| parent.child("…"));
9696
9697 let left = if supports_jump && first_edit_row != cursor_point.row {
9698 render_relative_row_jump("", cursor_point.row, first_edit_row)
9699 .into_any_element()
9700 } else {
9701 let icon_name =
9702 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9703 Icon::new(icon_name).into_any_element()
9704 };
9705
9706 Some(
9707 h_flex()
9708 .h_full()
9709 .flex_1()
9710 .gap_2()
9711 .pr_1()
9712 .overflow_x_hidden()
9713 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9714 .child(left)
9715 .child(preview),
9716 )
9717 }
9718 }
9719 }
9720
9721 pub fn render_context_menu(
9722 &self,
9723 style: &EditorStyle,
9724 max_height_in_lines: u32,
9725 window: &mut Window,
9726 cx: &mut Context<Editor>,
9727 ) -> Option<AnyElement> {
9728 let menu = self.context_menu.borrow();
9729 let menu = menu.as_ref()?;
9730 if !menu.visible() {
9731 return None;
9732 };
9733 Some(menu.render(style, max_height_in_lines, window, cx))
9734 }
9735
9736 fn render_context_menu_aside(
9737 &mut self,
9738 max_size: Size<Pixels>,
9739 window: &mut Window,
9740 cx: &mut Context<Editor>,
9741 ) -> Option<AnyElement> {
9742 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9743 if menu.visible() {
9744 menu.render_aside(max_size, window, cx)
9745 } else {
9746 None
9747 }
9748 })
9749 }
9750
9751 fn hide_context_menu(
9752 &mut self,
9753 window: &mut Window,
9754 cx: &mut Context<Self>,
9755 ) -> Option<CodeContextMenu> {
9756 cx.notify();
9757 self.completion_tasks.clear();
9758 let context_menu = self.context_menu.borrow_mut().take();
9759 self.stale_edit_prediction_in_menu.take();
9760 self.update_visible_edit_prediction(window, cx);
9761 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9762 && let Some(completion_provider) = &self.completion_provider
9763 {
9764 completion_provider.selection_changed(None, window, cx);
9765 }
9766 context_menu
9767 }
9768
9769 fn show_snippet_choices(
9770 &mut self,
9771 choices: &Vec<String>,
9772 selection: Range<Anchor>,
9773 cx: &mut Context<Self>,
9774 ) {
9775 let Some((_, buffer, _)) = self
9776 .buffer()
9777 .read(cx)
9778 .excerpt_containing(selection.start, cx)
9779 else {
9780 return;
9781 };
9782 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9783 else {
9784 return;
9785 };
9786 if buffer != end_buffer {
9787 log::error!("expected anchor range to have matching buffer IDs");
9788 return;
9789 }
9790
9791 let id = post_inc(&mut self.next_completion_id);
9792 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9793 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9794 CompletionsMenu::new_snippet_choices(
9795 id,
9796 true,
9797 choices,
9798 selection,
9799 buffer,
9800 snippet_sort_order,
9801 ),
9802 ));
9803 }
9804
9805 pub fn insert_snippet(
9806 &mut self,
9807 insertion_ranges: &[Range<usize>],
9808 snippet: Snippet,
9809 window: &mut Window,
9810 cx: &mut Context<Self>,
9811 ) -> Result<()> {
9812 struct Tabstop<T> {
9813 is_end_tabstop: bool,
9814 ranges: Vec<Range<T>>,
9815 choices: Option<Vec<String>>,
9816 }
9817
9818 let tabstops = self.buffer.update(cx, |buffer, cx| {
9819 let snippet_text: Arc<str> = snippet.text.clone().into();
9820 let edits = insertion_ranges
9821 .iter()
9822 .cloned()
9823 .map(|range| (range, snippet_text.clone()));
9824 let autoindent_mode = AutoindentMode::Block {
9825 original_indent_columns: Vec::new(),
9826 };
9827 buffer.edit(edits, Some(autoindent_mode), cx);
9828
9829 let snapshot = &*buffer.read(cx);
9830 let snippet = &snippet;
9831 snippet
9832 .tabstops
9833 .iter()
9834 .map(|tabstop| {
9835 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9836 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9837 });
9838 let mut tabstop_ranges = tabstop
9839 .ranges
9840 .iter()
9841 .flat_map(|tabstop_range| {
9842 let mut delta = 0_isize;
9843 insertion_ranges.iter().map(move |insertion_range| {
9844 let insertion_start = insertion_range.start as isize + delta;
9845 delta +=
9846 snippet.text.len() as isize - insertion_range.len() as isize;
9847
9848 let start = ((insertion_start + tabstop_range.start) as usize)
9849 .min(snapshot.len());
9850 let end = ((insertion_start + tabstop_range.end) as usize)
9851 .min(snapshot.len());
9852 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9853 })
9854 })
9855 .collect::<Vec<_>>();
9856 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9857
9858 Tabstop {
9859 is_end_tabstop,
9860 ranges: tabstop_ranges,
9861 choices: tabstop.choices.clone(),
9862 }
9863 })
9864 .collect::<Vec<_>>()
9865 });
9866 if let Some(tabstop) = tabstops.first() {
9867 self.change_selections(Default::default(), window, cx, |s| {
9868 // Reverse order so that the first range is the newest created selection.
9869 // Completions will use it and autoscroll will prioritize it.
9870 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9871 });
9872
9873 if let Some(choices) = &tabstop.choices
9874 && let Some(selection) = tabstop.ranges.first()
9875 {
9876 self.show_snippet_choices(choices, selection.clone(), cx)
9877 }
9878
9879 // If we're already at the last tabstop and it's at the end of the snippet,
9880 // we're done, we don't need to keep the state around.
9881 if !tabstop.is_end_tabstop {
9882 let choices = tabstops
9883 .iter()
9884 .map(|tabstop| tabstop.choices.clone())
9885 .collect();
9886
9887 let ranges = tabstops
9888 .into_iter()
9889 .map(|tabstop| tabstop.ranges)
9890 .collect::<Vec<_>>();
9891
9892 self.snippet_stack.push(SnippetState {
9893 active_index: 0,
9894 ranges,
9895 choices,
9896 });
9897 }
9898
9899 // Check whether the just-entered snippet ends with an auto-closable bracket.
9900 if self.autoclose_regions.is_empty() {
9901 let snapshot = self.buffer.read(cx).snapshot(cx);
9902 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9903 let selection_head = selection.head();
9904 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9905 continue;
9906 };
9907
9908 let mut bracket_pair = None;
9909 let max_lookup_length = scope
9910 .brackets()
9911 .map(|(pair, _)| {
9912 pair.start
9913 .as_str()
9914 .chars()
9915 .count()
9916 .max(pair.end.as_str().chars().count())
9917 })
9918 .max();
9919 if let Some(max_lookup_length) = max_lookup_length {
9920 let next_text = snapshot
9921 .chars_at(selection_head)
9922 .take(max_lookup_length)
9923 .collect::<String>();
9924 let prev_text = snapshot
9925 .reversed_chars_at(selection_head)
9926 .take(max_lookup_length)
9927 .collect::<String>();
9928
9929 for (pair, enabled) in scope.brackets() {
9930 if enabled
9931 && pair.close
9932 && prev_text.starts_with(pair.start.as_str())
9933 && next_text.starts_with(pair.end.as_str())
9934 {
9935 bracket_pair = Some(pair.clone());
9936 break;
9937 }
9938 }
9939 }
9940
9941 if let Some(pair) = bracket_pair {
9942 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9943 let autoclose_enabled =
9944 self.use_autoclose && snapshot_settings.use_autoclose;
9945 if autoclose_enabled {
9946 let start = snapshot.anchor_after(selection_head);
9947 let end = snapshot.anchor_after(selection_head);
9948 self.autoclose_regions.push(AutocloseRegion {
9949 selection_id: selection.id,
9950 range: start..end,
9951 pair,
9952 });
9953 }
9954 }
9955 }
9956 }
9957 }
9958 Ok(())
9959 }
9960
9961 pub fn move_to_next_snippet_tabstop(
9962 &mut self,
9963 window: &mut Window,
9964 cx: &mut Context<Self>,
9965 ) -> bool {
9966 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9967 }
9968
9969 pub fn move_to_prev_snippet_tabstop(
9970 &mut self,
9971 window: &mut Window,
9972 cx: &mut Context<Self>,
9973 ) -> bool {
9974 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9975 }
9976
9977 pub fn move_to_snippet_tabstop(
9978 &mut self,
9979 bias: Bias,
9980 window: &mut Window,
9981 cx: &mut Context<Self>,
9982 ) -> bool {
9983 if let Some(mut snippet) = self.snippet_stack.pop() {
9984 match bias {
9985 Bias::Left => {
9986 if snippet.active_index > 0 {
9987 snippet.active_index -= 1;
9988 } else {
9989 self.snippet_stack.push(snippet);
9990 return false;
9991 }
9992 }
9993 Bias::Right => {
9994 if snippet.active_index + 1 < snippet.ranges.len() {
9995 snippet.active_index += 1;
9996 } else {
9997 self.snippet_stack.push(snippet);
9998 return false;
9999 }
10000 }
10001 }
10002 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10003 self.change_selections(Default::default(), window, cx, |s| {
10004 // Reverse order so that the first range is the newest created selection.
10005 // Completions will use it and autoscroll will prioritize it.
10006 s.select_ranges(current_ranges.iter().rev().cloned())
10007 });
10008
10009 if let Some(choices) = &snippet.choices[snippet.active_index]
10010 && let Some(selection) = current_ranges.first()
10011 {
10012 self.show_snippet_choices(choices, selection.clone(), cx);
10013 }
10014
10015 // If snippet state is not at the last tabstop, push it back on the stack
10016 if snippet.active_index + 1 < snippet.ranges.len() {
10017 self.snippet_stack.push(snippet);
10018 }
10019 return true;
10020 }
10021 }
10022
10023 false
10024 }
10025
10026 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10027 self.transact(window, cx, |this, window, cx| {
10028 this.select_all(&SelectAll, window, cx);
10029 this.insert("", window, cx);
10030 });
10031 }
10032
10033 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10034 if self.read_only(cx) {
10035 return;
10036 }
10037 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10038 self.transact(window, cx, |this, window, cx| {
10039 this.select_autoclose_pair(window, cx);
10040
10041 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10042
10043 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10044 if !this.linked_edit_ranges.is_empty() {
10045 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10046 let snapshot = this.buffer.read(cx).snapshot(cx);
10047
10048 for selection in selections.iter() {
10049 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10050 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10051 if selection_start.buffer_id != selection_end.buffer_id {
10052 continue;
10053 }
10054 if let Some(ranges) =
10055 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10056 {
10057 for (buffer, entries) in ranges {
10058 linked_ranges.entry(buffer).or_default().extend(entries);
10059 }
10060 }
10061 }
10062 }
10063
10064 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10065 for selection in &mut selections {
10066 if selection.is_empty() {
10067 let old_head = selection.head();
10068 let mut new_head =
10069 movement::left(&display_map, old_head.to_display_point(&display_map))
10070 .to_point(&display_map);
10071 if let Some((buffer, line_buffer_range)) = display_map
10072 .buffer_snapshot()
10073 .buffer_line_for_row(MultiBufferRow(old_head.row))
10074 {
10075 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10076 let indent_len = match indent_size.kind {
10077 IndentKind::Space => {
10078 buffer.settings_at(line_buffer_range.start, cx).tab_size
10079 }
10080 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10081 };
10082 if old_head.column <= indent_size.len && old_head.column > 0 {
10083 let indent_len = indent_len.get();
10084 new_head = cmp::min(
10085 new_head,
10086 MultiBufferPoint::new(
10087 old_head.row,
10088 ((old_head.column - 1) / indent_len) * indent_len,
10089 ),
10090 );
10091 }
10092 }
10093
10094 selection.set_head(new_head, SelectionGoal::None);
10095 }
10096 }
10097
10098 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10099 this.insert("", window, cx);
10100 let empty_str: Arc<str> = Arc::from("");
10101 for (buffer, edits) in linked_ranges {
10102 let snapshot = buffer.read(cx).snapshot();
10103 use text::ToPoint as TP;
10104
10105 let edits = edits
10106 .into_iter()
10107 .map(|range| {
10108 let end_point = TP::to_point(&range.end, &snapshot);
10109 let mut start_point = TP::to_point(&range.start, &snapshot);
10110
10111 if end_point == start_point {
10112 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10113 .saturating_sub(1);
10114 start_point =
10115 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10116 };
10117
10118 (start_point..end_point, empty_str.clone())
10119 })
10120 .sorted_by_key(|(range, _)| range.start)
10121 .collect::<Vec<_>>();
10122 buffer.update(cx, |this, cx| {
10123 this.edit(edits, None, cx);
10124 })
10125 }
10126 this.refresh_edit_prediction(true, false, window, cx);
10127 refresh_linked_ranges(this, window, cx);
10128 });
10129 }
10130
10131 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10132 if self.read_only(cx) {
10133 return;
10134 }
10135 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10136 self.transact(window, cx, |this, window, cx| {
10137 this.change_selections(Default::default(), window, cx, |s| {
10138 s.move_with(|map, selection| {
10139 if selection.is_empty() {
10140 let cursor = movement::right(map, selection.head());
10141 selection.end = cursor;
10142 selection.reversed = true;
10143 selection.goal = SelectionGoal::None;
10144 }
10145 })
10146 });
10147 this.insert("", window, cx);
10148 this.refresh_edit_prediction(true, false, window, cx);
10149 });
10150 }
10151
10152 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10153 if self.mode.is_single_line() {
10154 cx.propagate();
10155 return;
10156 }
10157
10158 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10159 if self.move_to_prev_snippet_tabstop(window, cx) {
10160 return;
10161 }
10162 self.outdent(&Outdent, window, cx);
10163 }
10164
10165 pub fn next_snippet_tabstop(
10166 &mut self,
10167 _: &NextSnippetTabstop,
10168 window: &mut Window,
10169 cx: &mut Context<Self>,
10170 ) {
10171 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10172 cx.propagate();
10173 return;
10174 }
10175
10176 if self.move_to_next_snippet_tabstop(window, cx) {
10177 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10178 return;
10179 }
10180 cx.propagate();
10181 }
10182
10183 pub fn previous_snippet_tabstop(
10184 &mut self,
10185 _: &PreviousSnippetTabstop,
10186 window: &mut Window,
10187 cx: &mut Context<Self>,
10188 ) {
10189 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10190 cx.propagate();
10191 return;
10192 }
10193
10194 if self.move_to_prev_snippet_tabstop(window, cx) {
10195 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10196 return;
10197 }
10198 cx.propagate();
10199 }
10200
10201 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10202 if self.mode.is_single_line() {
10203 cx.propagate();
10204 return;
10205 }
10206
10207 if self.move_to_next_snippet_tabstop(window, cx) {
10208 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10209 return;
10210 }
10211 if self.read_only(cx) {
10212 return;
10213 }
10214 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10215 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10216 let buffer = self.buffer.read(cx);
10217 let snapshot = buffer.snapshot(cx);
10218 let rows_iter = selections.iter().map(|s| s.head().row);
10219 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10220
10221 let has_some_cursor_in_whitespace = selections
10222 .iter()
10223 .filter(|selection| selection.is_empty())
10224 .any(|selection| {
10225 let cursor = selection.head();
10226 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10227 cursor.column < current_indent.len
10228 });
10229
10230 let mut edits = Vec::new();
10231 let mut prev_edited_row = 0;
10232 let mut row_delta = 0;
10233 for selection in &mut selections {
10234 if selection.start.row != prev_edited_row {
10235 row_delta = 0;
10236 }
10237 prev_edited_row = selection.end.row;
10238
10239 // If the selection is non-empty, then increase the indentation of the selected lines.
10240 if !selection.is_empty() {
10241 row_delta =
10242 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10243 continue;
10244 }
10245
10246 let cursor = selection.head();
10247 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10248 if let Some(suggested_indent) =
10249 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10250 {
10251 // Don't do anything if already at suggested indent
10252 // and there is any other cursor which is not
10253 if has_some_cursor_in_whitespace
10254 && cursor.column == current_indent.len
10255 && current_indent.len == suggested_indent.len
10256 {
10257 continue;
10258 }
10259
10260 // Adjust line and move cursor to suggested indent
10261 // if cursor is not at suggested indent
10262 if cursor.column < suggested_indent.len
10263 && cursor.column <= current_indent.len
10264 && current_indent.len <= suggested_indent.len
10265 {
10266 selection.start = Point::new(cursor.row, suggested_indent.len);
10267 selection.end = selection.start;
10268 if row_delta == 0 {
10269 edits.extend(Buffer::edit_for_indent_size_adjustment(
10270 cursor.row,
10271 current_indent,
10272 suggested_indent,
10273 ));
10274 row_delta = suggested_indent.len - current_indent.len;
10275 }
10276 continue;
10277 }
10278
10279 // If current indent is more than suggested indent
10280 // only move cursor to current indent and skip indent
10281 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10282 selection.start = Point::new(cursor.row, current_indent.len);
10283 selection.end = selection.start;
10284 continue;
10285 }
10286 }
10287
10288 // Otherwise, insert a hard or soft tab.
10289 let settings = buffer.language_settings_at(cursor, cx);
10290 let tab_size = if settings.hard_tabs {
10291 IndentSize::tab()
10292 } else {
10293 let tab_size = settings.tab_size.get();
10294 let indent_remainder = snapshot
10295 .text_for_range(Point::new(cursor.row, 0)..cursor)
10296 .flat_map(str::chars)
10297 .fold(row_delta % tab_size, |counter: u32, c| {
10298 if c == '\t' {
10299 0
10300 } else {
10301 (counter + 1) % tab_size
10302 }
10303 });
10304
10305 let chars_to_next_tab_stop = tab_size - indent_remainder;
10306 IndentSize::spaces(chars_to_next_tab_stop)
10307 };
10308 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10309 selection.end = selection.start;
10310 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10311 row_delta += tab_size.len;
10312 }
10313
10314 self.transact(window, cx, |this, window, cx| {
10315 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10316 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10317 this.refresh_edit_prediction(true, false, window, cx);
10318 });
10319 }
10320
10321 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10322 if self.read_only(cx) {
10323 return;
10324 }
10325 if self.mode.is_single_line() {
10326 cx.propagate();
10327 return;
10328 }
10329
10330 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10331 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10332 let mut prev_edited_row = 0;
10333 let mut row_delta = 0;
10334 let mut edits = Vec::new();
10335 let buffer = self.buffer.read(cx);
10336 let snapshot = buffer.snapshot(cx);
10337 for selection in &mut selections {
10338 if selection.start.row != prev_edited_row {
10339 row_delta = 0;
10340 }
10341 prev_edited_row = selection.end.row;
10342
10343 row_delta =
10344 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10345 }
10346
10347 self.transact(window, cx, |this, window, cx| {
10348 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10349 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10350 });
10351 }
10352
10353 fn indent_selection(
10354 buffer: &MultiBuffer,
10355 snapshot: &MultiBufferSnapshot,
10356 selection: &mut Selection<Point>,
10357 edits: &mut Vec<(Range<Point>, String)>,
10358 delta_for_start_row: u32,
10359 cx: &App,
10360 ) -> u32 {
10361 let settings = buffer.language_settings_at(selection.start, cx);
10362 let tab_size = settings.tab_size.get();
10363 let indent_kind = if settings.hard_tabs {
10364 IndentKind::Tab
10365 } else {
10366 IndentKind::Space
10367 };
10368 let mut start_row = selection.start.row;
10369 let mut end_row = selection.end.row + 1;
10370
10371 // If a selection ends at the beginning of a line, don't indent
10372 // that last line.
10373 if selection.end.column == 0 && selection.end.row > selection.start.row {
10374 end_row -= 1;
10375 }
10376
10377 // Avoid re-indenting a row that has already been indented by a
10378 // previous selection, but still update this selection's column
10379 // to reflect that indentation.
10380 if delta_for_start_row > 0 {
10381 start_row += 1;
10382 selection.start.column += delta_for_start_row;
10383 if selection.end.row == selection.start.row {
10384 selection.end.column += delta_for_start_row;
10385 }
10386 }
10387
10388 let mut delta_for_end_row = 0;
10389 let has_multiple_rows = start_row + 1 != end_row;
10390 for row in start_row..end_row {
10391 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10392 let indent_delta = match (current_indent.kind, indent_kind) {
10393 (IndentKind::Space, IndentKind::Space) => {
10394 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10395 IndentSize::spaces(columns_to_next_tab_stop)
10396 }
10397 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10398 (_, IndentKind::Tab) => IndentSize::tab(),
10399 };
10400
10401 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10402 0
10403 } else {
10404 selection.start.column
10405 };
10406 let row_start = Point::new(row, start);
10407 edits.push((
10408 row_start..row_start,
10409 indent_delta.chars().collect::<String>(),
10410 ));
10411
10412 // Update this selection's endpoints to reflect the indentation.
10413 if row == selection.start.row {
10414 selection.start.column += indent_delta.len;
10415 }
10416 if row == selection.end.row {
10417 selection.end.column += indent_delta.len;
10418 delta_for_end_row = indent_delta.len;
10419 }
10420 }
10421
10422 if selection.start.row == selection.end.row {
10423 delta_for_start_row + delta_for_end_row
10424 } else {
10425 delta_for_end_row
10426 }
10427 }
10428
10429 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10430 if self.read_only(cx) {
10431 return;
10432 }
10433 if self.mode.is_single_line() {
10434 cx.propagate();
10435 return;
10436 }
10437
10438 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10439 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10440 let selections = self.selections.all::<Point>(&display_map);
10441 let mut deletion_ranges = Vec::new();
10442 let mut last_outdent = None;
10443 {
10444 let buffer = self.buffer.read(cx);
10445 let snapshot = buffer.snapshot(cx);
10446 for selection in &selections {
10447 let settings = buffer.language_settings_at(selection.start, cx);
10448 let tab_size = settings.tab_size.get();
10449 let mut rows = selection.spanned_rows(false, &display_map);
10450
10451 // Avoid re-outdenting a row that has already been outdented by a
10452 // previous selection.
10453 if let Some(last_row) = last_outdent
10454 && last_row == rows.start
10455 {
10456 rows.start = rows.start.next_row();
10457 }
10458 let has_multiple_rows = rows.len() > 1;
10459 for row in rows.iter_rows() {
10460 let indent_size = snapshot.indent_size_for_line(row);
10461 if indent_size.len > 0 {
10462 let deletion_len = match indent_size.kind {
10463 IndentKind::Space => {
10464 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10465 if columns_to_prev_tab_stop == 0 {
10466 tab_size
10467 } else {
10468 columns_to_prev_tab_stop
10469 }
10470 }
10471 IndentKind::Tab => 1,
10472 };
10473 let start = if has_multiple_rows
10474 || deletion_len > selection.start.column
10475 || indent_size.len < selection.start.column
10476 {
10477 0
10478 } else {
10479 selection.start.column - deletion_len
10480 };
10481 deletion_ranges.push(
10482 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10483 );
10484 last_outdent = Some(row);
10485 }
10486 }
10487 }
10488 }
10489
10490 self.transact(window, cx, |this, window, cx| {
10491 this.buffer.update(cx, |buffer, cx| {
10492 let empty_str: Arc<str> = Arc::default();
10493 buffer.edit(
10494 deletion_ranges
10495 .into_iter()
10496 .map(|range| (range, empty_str.clone())),
10497 None,
10498 cx,
10499 );
10500 });
10501 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10502 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10503 });
10504 }
10505
10506 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10507 if self.read_only(cx) {
10508 return;
10509 }
10510 if self.mode.is_single_line() {
10511 cx.propagate();
10512 return;
10513 }
10514
10515 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10516 let selections = self
10517 .selections
10518 .all::<usize>(&self.display_snapshot(cx))
10519 .into_iter()
10520 .map(|s| s.range());
10521
10522 self.transact(window, cx, |this, window, cx| {
10523 this.buffer.update(cx, |buffer, cx| {
10524 buffer.autoindent_ranges(selections, cx);
10525 });
10526 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10527 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10528 });
10529 }
10530
10531 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10532 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10533 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10534 let selections = self.selections.all::<Point>(&display_map);
10535
10536 let mut new_cursors = Vec::new();
10537 let mut edit_ranges = Vec::new();
10538 let mut selections = selections.iter().peekable();
10539 while let Some(selection) = selections.next() {
10540 let mut rows = selection.spanned_rows(false, &display_map);
10541
10542 // Accumulate contiguous regions of rows that we want to delete.
10543 while let Some(next_selection) = selections.peek() {
10544 let next_rows = next_selection.spanned_rows(false, &display_map);
10545 if next_rows.start <= rows.end {
10546 rows.end = next_rows.end;
10547 selections.next().unwrap();
10548 } else {
10549 break;
10550 }
10551 }
10552
10553 let buffer = display_map.buffer_snapshot();
10554 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10555 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10556 // If there's a line after the range, delete the \n from the end of the row range
10557 (
10558 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10559 rows.end,
10560 )
10561 } else {
10562 // If there isn't a line after the range, delete the \n from the line before the
10563 // start of the row range
10564 edit_start = edit_start.saturating_sub(1);
10565 (buffer.len(), rows.start.previous_row())
10566 };
10567
10568 let text_layout_details = self.text_layout_details(window);
10569 let x = display_map.x_for_display_point(
10570 selection.head().to_display_point(&display_map),
10571 &text_layout_details,
10572 );
10573 let row = Point::new(target_row.0, 0)
10574 .to_display_point(&display_map)
10575 .row();
10576 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10577
10578 new_cursors.push((
10579 selection.id,
10580 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10581 SelectionGoal::None,
10582 ));
10583 edit_ranges.push(edit_start..edit_end);
10584 }
10585
10586 self.transact(window, cx, |this, window, cx| {
10587 let buffer = this.buffer.update(cx, |buffer, cx| {
10588 let empty_str: Arc<str> = Arc::default();
10589 buffer.edit(
10590 edit_ranges
10591 .into_iter()
10592 .map(|range| (range, empty_str.clone())),
10593 None,
10594 cx,
10595 );
10596 buffer.snapshot(cx)
10597 });
10598 let new_selections = new_cursors
10599 .into_iter()
10600 .map(|(id, cursor, goal)| {
10601 let cursor = cursor.to_point(&buffer);
10602 Selection {
10603 id,
10604 start: cursor,
10605 end: cursor,
10606 reversed: false,
10607 goal,
10608 }
10609 })
10610 .collect();
10611
10612 this.change_selections(Default::default(), window, cx, |s| {
10613 s.select(new_selections);
10614 });
10615 });
10616 }
10617
10618 pub fn join_lines_impl(
10619 &mut self,
10620 insert_whitespace: bool,
10621 window: &mut Window,
10622 cx: &mut Context<Self>,
10623 ) {
10624 if self.read_only(cx) {
10625 return;
10626 }
10627 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10628 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10629 let start = MultiBufferRow(selection.start.row);
10630 // Treat single line selections as if they include the next line. Otherwise this action
10631 // would do nothing for single line selections individual cursors.
10632 let end = if selection.start.row == selection.end.row {
10633 MultiBufferRow(selection.start.row + 1)
10634 } else {
10635 MultiBufferRow(selection.end.row)
10636 };
10637
10638 if let Some(last_row_range) = row_ranges.last_mut()
10639 && start <= last_row_range.end
10640 {
10641 last_row_range.end = end;
10642 continue;
10643 }
10644 row_ranges.push(start..end);
10645 }
10646
10647 let snapshot = self.buffer.read(cx).snapshot(cx);
10648 let mut cursor_positions = Vec::new();
10649 for row_range in &row_ranges {
10650 let anchor = snapshot.anchor_before(Point::new(
10651 row_range.end.previous_row().0,
10652 snapshot.line_len(row_range.end.previous_row()),
10653 ));
10654 cursor_positions.push(anchor..anchor);
10655 }
10656
10657 self.transact(window, cx, |this, window, cx| {
10658 for row_range in row_ranges.into_iter().rev() {
10659 for row in row_range.iter_rows().rev() {
10660 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10661 let next_line_row = row.next_row();
10662 let indent = snapshot.indent_size_for_line(next_line_row);
10663 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10664
10665 let replace =
10666 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10667 " "
10668 } else {
10669 ""
10670 };
10671
10672 this.buffer.update(cx, |buffer, cx| {
10673 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10674 });
10675 }
10676 }
10677
10678 this.change_selections(Default::default(), window, cx, |s| {
10679 s.select_anchor_ranges(cursor_positions)
10680 });
10681 });
10682 }
10683
10684 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10685 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10686 self.join_lines_impl(true, window, cx);
10687 }
10688
10689 pub fn sort_lines_case_sensitive(
10690 &mut self,
10691 _: &SortLinesCaseSensitive,
10692 window: &mut Window,
10693 cx: &mut Context<Self>,
10694 ) {
10695 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10696 }
10697
10698 pub fn sort_lines_by_length(
10699 &mut self,
10700 _: &SortLinesByLength,
10701 window: &mut Window,
10702 cx: &mut Context<Self>,
10703 ) {
10704 self.manipulate_immutable_lines(window, cx, |lines| {
10705 lines.sort_by_key(|&line| line.chars().count())
10706 })
10707 }
10708
10709 pub fn sort_lines_case_insensitive(
10710 &mut self,
10711 _: &SortLinesCaseInsensitive,
10712 window: &mut Window,
10713 cx: &mut Context<Self>,
10714 ) {
10715 self.manipulate_immutable_lines(window, cx, |lines| {
10716 lines.sort_by_key(|line| line.to_lowercase())
10717 })
10718 }
10719
10720 pub fn unique_lines_case_insensitive(
10721 &mut self,
10722 _: &UniqueLinesCaseInsensitive,
10723 window: &mut Window,
10724 cx: &mut Context<Self>,
10725 ) {
10726 self.manipulate_immutable_lines(window, cx, |lines| {
10727 let mut seen = HashSet::default();
10728 lines.retain(|line| seen.insert(line.to_lowercase()));
10729 })
10730 }
10731
10732 pub fn unique_lines_case_sensitive(
10733 &mut self,
10734 _: &UniqueLinesCaseSensitive,
10735 window: &mut Window,
10736 cx: &mut Context<Self>,
10737 ) {
10738 self.manipulate_immutable_lines(window, cx, |lines| {
10739 let mut seen = HashSet::default();
10740 lines.retain(|line| seen.insert(*line));
10741 })
10742 }
10743
10744 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10745 let snapshot = self.buffer.read(cx).snapshot(cx);
10746 for selection in self.selections.disjoint_anchors_arc().iter() {
10747 if snapshot
10748 .language_at(selection.start)
10749 .and_then(|lang| lang.config().wrap_characters.as_ref())
10750 .is_some()
10751 {
10752 return true;
10753 }
10754 }
10755 false
10756 }
10757
10758 fn wrap_selections_in_tag(
10759 &mut self,
10760 _: &WrapSelectionsInTag,
10761 window: &mut Window,
10762 cx: &mut Context<Self>,
10763 ) {
10764 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10765
10766 let snapshot = self.buffer.read(cx).snapshot(cx);
10767
10768 let mut edits = Vec::new();
10769 let mut boundaries = Vec::new();
10770
10771 for selection in self
10772 .selections
10773 .all_adjusted(&self.display_snapshot(cx))
10774 .iter()
10775 {
10776 let Some(wrap_config) = snapshot
10777 .language_at(selection.start)
10778 .and_then(|lang| lang.config().wrap_characters.clone())
10779 else {
10780 continue;
10781 };
10782
10783 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10784 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10785
10786 let start_before = snapshot.anchor_before(selection.start);
10787 let end_after = snapshot.anchor_after(selection.end);
10788
10789 edits.push((start_before..start_before, open_tag));
10790 edits.push((end_after..end_after, close_tag));
10791
10792 boundaries.push((
10793 start_before,
10794 end_after,
10795 wrap_config.start_prefix.len(),
10796 wrap_config.end_suffix.len(),
10797 ));
10798 }
10799
10800 if edits.is_empty() {
10801 return;
10802 }
10803
10804 self.transact(window, cx, |this, window, cx| {
10805 let buffer = this.buffer.update(cx, |buffer, cx| {
10806 buffer.edit(edits, None, cx);
10807 buffer.snapshot(cx)
10808 });
10809
10810 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10811 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10812 boundaries.into_iter()
10813 {
10814 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10815 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10816 new_selections.push(open_offset..open_offset);
10817 new_selections.push(close_offset..close_offset);
10818 }
10819
10820 this.change_selections(Default::default(), window, cx, |s| {
10821 s.select_ranges(new_selections);
10822 });
10823
10824 this.request_autoscroll(Autoscroll::fit(), cx);
10825 });
10826 }
10827
10828 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10829 let Some(project) = self.project.clone() else {
10830 return;
10831 };
10832 self.reload(project, window, cx)
10833 .detach_and_notify_err(window, cx);
10834 }
10835
10836 pub fn restore_file(
10837 &mut self,
10838 _: &::git::RestoreFile,
10839 window: &mut Window,
10840 cx: &mut Context<Self>,
10841 ) {
10842 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10843 let mut buffer_ids = HashSet::default();
10844 let snapshot = self.buffer().read(cx).snapshot(cx);
10845 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10846 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10847 }
10848
10849 let buffer = self.buffer().read(cx);
10850 let ranges = buffer_ids
10851 .into_iter()
10852 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10853 .collect::<Vec<_>>();
10854
10855 self.restore_hunks_in_ranges(ranges, window, cx);
10856 }
10857
10858 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10859 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10860 let selections = self
10861 .selections
10862 .all(&self.display_snapshot(cx))
10863 .into_iter()
10864 .map(|s| s.range())
10865 .collect();
10866 self.restore_hunks_in_ranges(selections, window, cx);
10867 }
10868
10869 pub fn restore_hunks_in_ranges(
10870 &mut self,
10871 ranges: Vec<Range<Point>>,
10872 window: &mut Window,
10873 cx: &mut Context<Editor>,
10874 ) {
10875 let mut revert_changes = HashMap::default();
10876 let chunk_by = self
10877 .snapshot(window, cx)
10878 .hunks_for_ranges(ranges)
10879 .into_iter()
10880 .chunk_by(|hunk| hunk.buffer_id);
10881 for (buffer_id, hunks) in &chunk_by {
10882 let hunks = hunks.collect::<Vec<_>>();
10883 for hunk in &hunks {
10884 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10885 }
10886 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10887 }
10888 drop(chunk_by);
10889 if !revert_changes.is_empty() {
10890 self.transact(window, cx, |editor, window, cx| {
10891 editor.restore(revert_changes, window, cx);
10892 });
10893 }
10894 }
10895
10896 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10897 if let Some(status) = self
10898 .addons
10899 .iter()
10900 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10901 {
10902 return Some(status);
10903 }
10904 self.project
10905 .as_ref()?
10906 .read(cx)
10907 .status_for_buffer_id(buffer_id, cx)
10908 }
10909
10910 pub fn open_active_item_in_terminal(
10911 &mut self,
10912 _: &OpenInTerminal,
10913 window: &mut Window,
10914 cx: &mut Context<Self>,
10915 ) {
10916 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10917 let project_path = buffer.read(cx).project_path(cx)?;
10918 let project = self.project()?.read(cx);
10919 let entry = project.entry_for_path(&project_path, cx)?;
10920 let parent = match &entry.canonical_path {
10921 Some(canonical_path) => canonical_path.to_path_buf(),
10922 None => project.absolute_path(&project_path, cx)?,
10923 }
10924 .parent()?
10925 .to_path_buf();
10926 Some(parent)
10927 }) {
10928 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10929 }
10930 }
10931
10932 fn set_breakpoint_context_menu(
10933 &mut self,
10934 display_row: DisplayRow,
10935 position: Option<Anchor>,
10936 clicked_point: gpui::Point<Pixels>,
10937 window: &mut Window,
10938 cx: &mut Context<Self>,
10939 ) {
10940 let source = self
10941 .buffer
10942 .read(cx)
10943 .snapshot(cx)
10944 .anchor_before(Point::new(display_row.0, 0u32));
10945
10946 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10947
10948 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10949 self,
10950 source,
10951 clicked_point,
10952 context_menu,
10953 window,
10954 cx,
10955 );
10956 }
10957
10958 fn add_edit_breakpoint_block(
10959 &mut self,
10960 anchor: Anchor,
10961 breakpoint: &Breakpoint,
10962 edit_action: BreakpointPromptEditAction,
10963 window: &mut Window,
10964 cx: &mut Context<Self>,
10965 ) {
10966 let weak_editor = cx.weak_entity();
10967 let bp_prompt = cx.new(|cx| {
10968 BreakpointPromptEditor::new(
10969 weak_editor,
10970 anchor,
10971 breakpoint.clone(),
10972 edit_action,
10973 window,
10974 cx,
10975 )
10976 });
10977
10978 let height = bp_prompt.update(cx, |this, cx| {
10979 this.prompt
10980 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10981 });
10982 let cloned_prompt = bp_prompt.clone();
10983 let blocks = vec![BlockProperties {
10984 style: BlockStyle::Sticky,
10985 placement: BlockPlacement::Above(anchor),
10986 height: Some(height),
10987 render: Arc::new(move |cx| {
10988 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10989 cloned_prompt.clone().into_any_element()
10990 }),
10991 priority: 0,
10992 }];
10993
10994 let focus_handle = bp_prompt.focus_handle(cx);
10995 window.focus(&focus_handle);
10996
10997 let block_ids = self.insert_blocks(blocks, None, cx);
10998 bp_prompt.update(cx, |prompt, _| {
10999 prompt.add_block_ids(block_ids);
11000 });
11001 }
11002
11003 pub(crate) fn breakpoint_at_row(
11004 &self,
11005 row: u32,
11006 window: &mut Window,
11007 cx: &mut Context<Self>,
11008 ) -> Option<(Anchor, Breakpoint)> {
11009 let snapshot = self.snapshot(window, cx);
11010 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11011
11012 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11013 }
11014
11015 pub(crate) fn breakpoint_at_anchor(
11016 &self,
11017 breakpoint_position: Anchor,
11018 snapshot: &EditorSnapshot,
11019 cx: &mut Context<Self>,
11020 ) -> Option<(Anchor, Breakpoint)> {
11021 let buffer = self
11022 .buffer
11023 .read(cx)
11024 .buffer_for_anchor(breakpoint_position, cx)?;
11025
11026 let enclosing_excerpt = breakpoint_position.excerpt_id;
11027 let buffer_snapshot = buffer.read(cx).snapshot();
11028
11029 let row = buffer_snapshot
11030 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11031 .row;
11032
11033 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11034 let anchor_end = snapshot
11035 .buffer_snapshot()
11036 .anchor_after(Point::new(row, line_len));
11037
11038 self.breakpoint_store
11039 .as_ref()?
11040 .read_with(cx, |breakpoint_store, cx| {
11041 breakpoint_store
11042 .breakpoints(
11043 &buffer,
11044 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11045 &buffer_snapshot,
11046 cx,
11047 )
11048 .next()
11049 .and_then(|(bp, _)| {
11050 let breakpoint_row = buffer_snapshot
11051 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11052 .row;
11053
11054 if breakpoint_row == row {
11055 snapshot
11056 .buffer_snapshot()
11057 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11058 .map(|position| (position, bp.bp.clone()))
11059 } else {
11060 None
11061 }
11062 })
11063 })
11064 }
11065
11066 pub fn edit_log_breakpoint(
11067 &mut self,
11068 _: &EditLogBreakpoint,
11069 window: &mut Window,
11070 cx: &mut Context<Self>,
11071 ) {
11072 if self.breakpoint_store.is_none() {
11073 return;
11074 }
11075
11076 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11077 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11078 message: None,
11079 state: BreakpointState::Enabled,
11080 condition: None,
11081 hit_condition: None,
11082 });
11083
11084 self.add_edit_breakpoint_block(
11085 anchor,
11086 &breakpoint,
11087 BreakpointPromptEditAction::Log,
11088 window,
11089 cx,
11090 );
11091 }
11092 }
11093
11094 fn breakpoints_at_cursors(
11095 &self,
11096 window: &mut Window,
11097 cx: &mut Context<Self>,
11098 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11099 let snapshot = self.snapshot(window, cx);
11100 let cursors = self
11101 .selections
11102 .disjoint_anchors_arc()
11103 .iter()
11104 .map(|selection| {
11105 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11106
11107 let breakpoint_position = self
11108 .breakpoint_at_row(cursor_position.row, window, cx)
11109 .map(|bp| bp.0)
11110 .unwrap_or_else(|| {
11111 snapshot
11112 .display_snapshot
11113 .buffer_snapshot()
11114 .anchor_after(Point::new(cursor_position.row, 0))
11115 });
11116
11117 let breakpoint = self
11118 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11119 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11120
11121 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11122 })
11123 // 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.
11124 .collect::<HashMap<Anchor, _>>();
11125
11126 cursors.into_iter().collect()
11127 }
11128
11129 pub fn enable_breakpoint(
11130 &mut self,
11131 _: &crate::actions::EnableBreakpoint,
11132 window: &mut Window,
11133 cx: &mut Context<Self>,
11134 ) {
11135 if self.breakpoint_store.is_none() {
11136 return;
11137 }
11138
11139 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11140 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11141 continue;
11142 };
11143 self.edit_breakpoint_at_anchor(
11144 anchor,
11145 breakpoint,
11146 BreakpointEditAction::InvertState,
11147 cx,
11148 );
11149 }
11150 }
11151
11152 pub fn disable_breakpoint(
11153 &mut self,
11154 _: &crate::actions::DisableBreakpoint,
11155 window: &mut Window,
11156 cx: &mut Context<Self>,
11157 ) {
11158 if self.breakpoint_store.is_none() {
11159 return;
11160 }
11161
11162 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11163 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11164 continue;
11165 };
11166 self.edit_breakpoint_at_anchor(
11167 anchor,
11168 breakpoint,
11169 BreakpointEditAction::InvertState,
11170 cx,
11171 );
11172 }
11173 }
11174
11175 pub fn toggle_breakpoint(
11176 &mut self,
11177 _: &crate::actions::ToggleBreakpoint,
11178 window: &mut Window,
11179 cx: &mut Context<Self>,
11180 ) {
11181 if self.breakpoint_store.is_none() {
11182 return;
11183 }
11184
11185 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11186 if let Some(breakpoint) = breakpoint {
11187 self.edit_breakpoint_at_anchor(
11188 anchor,
11189 breakpoint,
11190 BreakpointEditAction::Toggle,
11191 cx,
11192 );
11193 } else {
11194 self.edit_breakpoint_at_anchor(
11195 anchor,
11196 Breakpoint::new_standard(),
11197 BreakpointEditAction::Toggle,
11198 cx,
11199 );
11200 }
11201 }
11202 }
11203
11204 pub fn edit_breakpoint_at_anchor(
11205 &mut self,
11206 breakpoint_position: Anchor,
11207 breakpoint: Breakpoint,
11208 edit_action: BreakpointEditAction,
11209 cx: &mut Context<Self>,
11210 ) {
11211 let Some(breakpoint_store) = &self.breakpoint_store else {
11212 return;
11213 };
11214
11215 let Some(buffer) = self
11216 .buffer
11217 .read(cx)
11218 .buffer_for_anchor(breakpoint_position, cx)
11219 else {
11220 return;
11221 };
11222
11223 breakpoint_store.update(cx, |breakpoint_store, cx| {
11224 breakpoint_store.toggle_breakpoint(
11225 buffer,
11226 BreakpointWithPosition {
11227 position: breakpoint_position.text_anchor,
11228 bp: breakpoint,
11229 },
11230 edit_action,
11231 cx,
11232 );
11233 });
11234
11235 cx.notify();
11236 }
11237
11238 #[cfg(any(test, feature = "test-support"))]
11239 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11240 self.breakpoint_store.clone()
11241 }
11242
11243 pub fn prepare_restore_change(
11244 &self,
11245 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11246 hunk: &MultiBufferDiffHunk,
11247 cx: &mut App,
11248 ) -> Option<()> {
11249 if hunk.is_created_file() {
11250 return None;
11251 }
11252 let buffer = self.buffer.read(cx);
11253 let diff = buffer.diff_for(hunk.buffer_id)?;
11254 let buffer = buffer.buffer(hunk.buffer_id)?;
11255 let buffer = buffer.read(cx);
11256 let original_text = diff
11257 .read(cx)
11258 .base_text()
11259 .as_rope()
11260 .slice(hunk.diff_base_byte_range.clone());
11261 let buffer_snapshot = buffer.snapshot();
11262 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11263 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11264 probe
11265 .0
11266 .start
11267 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11268 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11269 }) {
11270 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11271 Some(())
11272 } else {
11273 None
11274 }
11275 }
11276
11277 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11278 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11279 }
11280
11281 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11282 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11283 }
11284
11285 fn manipulate_lines<M>(
11286 &mut self,
11287 window: &mut Window,
11288 cx: &mut Context<Self>,
11289 mut manipulate: M,
11290 ) where
11291 M: FnMut(&str) -> LineManipulationResult,
11292 {
11293 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11294
11295 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11296 let buffer = self.buffer.read(cx).snapshot(cx);
11297
11298 let mut edits = Vec::new();
11299
11300 let selections = self.selections.all::<Point>(&display_map);
11301 let mut selections = selections.iter().peekable();
11302 let mut contiguous_row_selections = Vec::new();
11303 let mut new_selections = Vec::new();
11304 let mut added_lines = 0;
11305 let mut removed_lines = 0;
11306
11307 while let Some(selection) = selections.next() {
11308 let (start_row, end_row) = consume_contiguous_rows(
11309 &mut contiguous_row_selections,
11310 selection,
11311 &display_map,
11312 &mut selections,
11313 );
11314
11315 let start_point = Point::new(start_row.0, 0);
11316 let end_point = Point::new(
11317 end_row.previous_row().0,
11318 buffer.line_len(end_row.previous_row()),
11319 );
11320 let text = buffer
11321 .text_for_range(start_point..end_point)
11322 .collect::<String>();
11323
11324 let LineManipulationResult {
11325 new_text,
11326 line_count_before,
11327 line_count_after,
11328 } = manipulate(&text);
11329
11330 edits.push((start_point..end_point, new_text));
11331
11332 // Selections must change based on added and removed line count
11333 let start_row =
11334 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11335 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11336 new_selections.push(Selection {
11337 id: selection.id,
11338 start: start_row,
11339 end: end_row,
11340 goal: SelectionGoal::None,
11341 reversed: selection.reversed,
11342 });
11343
11344 if line_count_after > line_count_before {
11345 added_lines += line_count_after - line_count_before;
11346 } else if line_count_before > line_count_after {
11347 removed_lines += line_count_before - line_count_after;
11348 }
11349 }
11350
11351 self.transact(window, cx, |this, window, cx| {
11352 let buffer = this.buffer.update(cx, |buffer, cx| {
11353 buffer.edit(edits, None, cx);
11354 buffer.snapshot(cx)
11355 });
11356
11357 // Recalculate offsets on newly edited buffer
11358 let new_selections = new_selections
11359 .iter()
11360 .map(|s| {
11361 let start_point = Point::new(s.start.0, 0);
11362 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11363 Selection {
11364 id: s.id,
11365 start: buffer.point_to_offset(start_point),
11366 end: buffer.point_to_offset(end_point),
11367 goal: s.goal,
11368 reversed: s.reversed,
11369 }
11370 })
11371 .collect();
11372
11373 this.change_selections(Default::default(), window, cx, |s| {
11374 s.select(new_selections);
11375 });
11376
11377 this.request_autoscroll(Autoscroll::fit(), cx);
11378 });
11379 }
11380
11381 fn manipulate_immutable_lines<Fn>(
11382 &mut self,
11383 window: &mut Window,
11384 cx: &mut Context<Self>,
11385 mut callback: Fn,
11386 ) where
11387 Fn: FnMut(&mut Vec<&str>),
11388 {
11389 self.manipulate_lines(window, cx, |text| {
11390 let mut lines: Vec<&str> = text.split('\n').collect();
11391 let line_count_before = lines.len();
11392
11393 callback(&mut lines);
11394
11395 LineManipulationResult {
11396 new_text: lines.join("\n"),
11397 line_count_before,
11398 line_count_after: lines.len(),
11399 }
11400 });
11401 }
11402
11403 fn manipulate_mutable_lines<Fn>(
11404 &mut self,
11405 window: &mut Window,
11406 cx: &mut Context<Self>,
11407 mut callback: Fn,
11408 ) where
11409 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11410 {
11411 self.manipulate_lines(window, cx, |text| {
11412 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11413 let line_count_before = lines.len();
11414
11415 callback(&mut lines);
11416
11417 LineManipulationResult {
11418 new_text: lines.join("\n"),
11419 line_count_before,
11420 line_count_after: lines.len(),
11421 }
11422 });
11423 }
11424
11425 pub fn convert_indentation_to_spaces(
11426 &mut self,
11427 _: &ConvertIndentationToSpaces,
11428 window: &mut Window,
11429 cx: &mut Context<Self>,
11430 ) {
11431 let settings = self.buffer.read(cx).language_settings(cx);
11432 let tab_size = settings.tab_size.get() as usize;
11433
11434 self.manipulate_mutable_lines(window, cx, |lines| {
11435 // Allocates a reasonably sized scratch buffer once for the whole loop
11436 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11437 // Avoids recomputing spaces that could be inserted many times
11438 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11439 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11440 .collect();
11441
11442 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11443 let mut chars = line.as_ref().chars();
11444 let mut col = 0;
11445 let mut changed = false;
11446
11447 for ch in chars.by_ref() {
11448 match ch {
11449 ' ' => {
11450 reindented_line.push(' ');
11451 col += 1;
11452 }
11453 '\t' => {
11454 // \t are converted to spaces depending on the current column
11455 let spaces_len = tab_size - (col % tab_size);
11456 reindented_line.extend(&space_cache[spaces_len - 1]);
11457 col += spaces_len;
11458 changed = true;
11459 }
11460 _ => {
11461 // If we dont append before break, the character is consumed
11462 reindented_line.push(ch);
11463 break;
11464 }
11465 }
11466 }
11467
11468 if !changed {
11469 reindented_line.clear();
11470 continue;
11471 }
11472 // Append the rest of the line and replace old reference with new one
11473 reindented_line.extend(chars);
11474 *line = Cow::Owned(reindented_line.clone());
11475 reindented_line.clear();
11476 }
11477 });
11478 }
11479
11480 pub fn convert_indentation_to_tabs(
11481 &mut self,
11482 _: &ConvertIndentationToTabs,
11483 window: &mut Window,
11484 cx: &mut Context<Self>,
11485 ) {
11486 let settings = self.buffer.read(cx).language_settings(cx);
11487 let tab_size = settings.tab_size.get() as usize;
11488
11489 self.manipulate_mutable_lines(window, cx, |lines| {
11490 // Allocates a reasonably sized buffer once for the whole loop
11491 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11492 // Avoids recomputing spaces that could be inserted many times
11493 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11494 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11495 .collect();
11496
11497 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11498 let mut chars = line.chars();
11499 let mut spaces_count = 0;
11500 let mut first_non_indent_char = None;
11501 let mut changed = false;
11502
11503 for ch in chars.by_ref() {
11504 match ch {
11505 ' ' => {
11506 // Keep track of spaces. Append \t when we reach tab_size
11507 spaces_count += 1;
11508 changed = true;
11509 if spaces_count == tab_size {
11510 reindented_line.push('\t');
11511 spaces_count = 0;
11512 }
11513 }
11514 '\t' => {
11515 reindented_line.push('\t');
11516 spaces_count = 0;
11517 }
11518 _ => {
11519 // Dont append it yet, we might have remaining spaces
11520 first_non_indent_char = Some(ch);
11521 break;
11522 }
11523 }
11524 }
11525
11526 if !changed {
11527 reindented_line.clear();
11528 continue;
11529 }
11530 // Remaining spaces that didn't make a full tab stop
11531 if spaces_count > 0 {
11532 reindented_line.extend(&space_cache[spaces_count - 1]);
11533 }
11534 // If we consume an extra character that was not indentation, add it back
11535 if let Some(extra_char) = first_non_indent_char {
11536 reindented_line.push(extra_char);
11537 }
11538 // Append the rest of the line and replace old reference with new one
11539 reindented_line.extend(chars);
11540 *line = Cow::Owned(reindented_line.clone());
11541 reindented_line.clear();
11542 }
11543 });
11544 }
11545
11546 pub fn convert_to_upper_case(
11547 &mut self,
11548 _: &ConvertToUpperCase,
11549 window: &mut Window,
11550 cx: &mut Context<Self>,
11551 ) {
11552 self.manipulate_text(window, cx, |text| text.to_uppercase())
11553 }
11554
11555 pub fn convert_to_lower_case(
11556 &mut self,
11557 _: &ConvertToLowerCase,
11558 window: &mut Window,
11559 cx: &mut Context<Self>,
11560 ) {
11561 self.manipulate_text(window, cx, |text| text.to_lowercase())
11562 }
11563
11564 pub fn convert_to_title_case(
11565 &mut self,
11566 _: &ConvertToTitleCase,
11567 window: &mut Window,
11568 cx: &mut Context<Self>,
11569 ) {
11570 self.manipulate_text(window, cx, |text| {
11571 text.split('\n')
11572 .map(|line| line.to_case(Case::Title))
11573 .join("\n")
11574 })
11575 }
11576
11577 pub fn convert_to_snake_case(
11578 &mut self,
11579 _: &ConvertToSnakeCase,
11580 window: &mut Window,
11581 cx: &mut Context<Self>,
11582 ) {
11583 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11584 }
11585
11586 pub fn convert_to_kebab_case(
11587 &mut self,
11588 _: &ConvertToKebabCase,
11589 window: &mut Window,
11590 cx: &mut Context<Self>,
11591 ) {
11592 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11593 }
11594
11595 pub fn convert_to_upper_camel_case(
11596 &mut self,
11597 _: &ConvertToUpperCamelCase,
11598 window: &mut Window,
11599 cx: &mut Context<Self>,
11600 ) {
11601 self.manipulate_text(window, cx, |text| {
11602 text.split('\n')
11603 .map(|line| line.to_case(Case::UpperCamel))
11604 .join("\n")
11605 })
11606 }
11607
11608 pub fn convert_to_lower_camel_case(
11609 &mut self,
11610 _: &ConvertToLowerCamelCase,
11611 window: &mut Window,
11612 cx: &mut Context<Self>,
11613 ) {
11614 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11615 }
11616
11617 pub fn convert_to_opposite_case(
11618 &mut self,
11619 _: &ConvertToOppositeCase,
11620 window: &mut Window,
11621 cx: &mut Context<Self>,
11622 ) {
11623 self.manipulate_text(window, cx, |text| {
11624 text.chars()
11625 .fold(String::with_capacity(text.len()), |mut t, c| {
11626 if c.is_uppercase() {
11627 t.extend(c.to_lowercase());
11628 } else {
11629 t.extend(c.to_uppercase());
11630 }
11631 t
11632 })
11633 })
11634 }
11635
11636 pub fn convert_to_sentence_case(
11637 &mut self,
11638 _: &ConvertToSentenceCase,
11639 window: &mut Window,
11640 cx: &mut Context<Self>,
11641 ) {
11642 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11643 }
11644
11645 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11646 self.manipulate_text(window, cx, |text| {
11647 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11648 if has_upper_case_characters {
11649 text.to_lowercase()
11650 } else {
11651 text.to_uppercase()
11652 }
11653 })
11654 }
11655
11656 pub fn convert_to_rot13(
11657 &mut self,
11658 _: &ConvertToRot13,
11659 window: &mut Window,
11660 cx: &mut Context<Self>,
11661 ) {
11662 self.manipulate_text(window, cx, |text| {
11663 text.chars()
11664 .map(|c| match c {
11665 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11666 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11667 _ => c,
11668 })
11669 .collect()
11670 })
11671 }
11672
11673 pub fn convert_to_rot47(
11674 &mut self,
11675 _: &ConvertToRot47,
11676 window: &mut Window,
11677 cx: &mut Context<Self>,
11678 ) {
11679 self.manipulate_text(window, cx, |text| {
11680 text.chars()
11681 .map(|c| {
11682 let code_point = c as u32;
11683 if code_point >= 33 && code_point <= 126 {
11684 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11685 }
11686 c
11687 })
11688 .collect()
11689 })
11690 }
11691
11692 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11693 where
11694 Fn: FnMut(&str) -> String,
11695 {
11696 let buffer = self.buffer.read(cx).snapshot(cx);
11697
11698 let mut new_selections = Vec::new();
11699 let mut edits = Vec::new();
11700 let mut selection_adjustment = 0i32;
11701
11702 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11703 let selection_is_empty = selection.is_empty();
11704
11705 let (start, end) = if selection_is_empty {
11706 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11707 (word_range.start, word_range.end)
11708 } else {
11709 (
11710 buffer.point_to_offset(selection.start),
11711 buffer.point_to_offset(selection.end),
11712 )
11713 };
11714
11715 let text = buffer.text_for_range(start..end).collect::<String>();
11716 let old_length = text.len() as i32;
11717 let text = callback(&text);
11718
11719 new_selections.push(Selection {
11720 start: (start as i32 - selection_adjustment) as usize,
11721 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11722 goal: SelectionGoal::None,
11723 id: selection.id,
11724 reversed: selection.reversed,
11725 });
11726
11727 selection_adjustment += old_length - text.len() as i32;
11728
11729 edits.push((start..end, text));
11730 }
11731
11732 self.transact(window, cx, |this, window, cx| {
11733 this.buffer.update(cx, |buffer, cx| {
11734 buffer.edit(edits, None, cx);
11735 });
11736
11737 this.change_selections(Default::default(), window, cx, |s| {
11738 s.select(new_selections);
11739 });
11740
11741 this.request_autoscroll(Autoscroll::fit(), cx);
11742 });
11743 }
11744
11745 pub fn move_selection_on_drop(
11746 &mut self,
11747 selection: &Selection<Anchor>,
11748 target: DisplayPoint,
11749 is_cut: bool,
11750 window: &mut Window,
11751 cx: &mut Context<Self>,
11752 ) {
11753 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11754 let buffer = display_map.buffer_snapshot();
11755 let mut edits = Vec::new();
11756 let insert_point = display_map
11757 .clip_point(target, Bias::Left)
11758 .to_point(&display_map);
11759 let text = buffer
11760 .text_for_range(selection.start..selection.end)
11761 .collect::<String>();
11762 if is_cut {
11763 edits.push(((selection.start..selection.end), String::new()));
11764 }
11765 let insert_anchor = buffer.anchor_before(insert_point);
11766 edits.push(((insert_anchor..insert_anchor), text));
11767 let last_edit_start = insert_anchor.bias_left(buffer);
11768 let last_edit_end = insert_anchor.bias_right(buffer);
11769 self.transact(window, cx, |this, window, cx| {
11770 this.buffer.update(cx, |buffer, cx| {
11771 buffer.edit(edits, None, cx);
11772 });
11773 this.change_selections(Default::default(), window, cx, |s| {
11774 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11775 });
11776 });
11777 }
11778
11779 pub fn clear_selection_drag_state(&mut self) {
11780 self.selection_drag_state = SelectionDragState::None;
11781 }
11782
11783 pub fn duplicate(
11784 &mut self,
11785 upwards: bool,
11786 whole_lines: bool,
11787 window: &mut Window,
11788 cx: &mut Context<Self>,
11789 ) {
11790 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11791
11792 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11793 let buffer = display_map.buffer_snapshot();
11794 let selections = self.selections.all::<Point>(&display_map);
11795
11796 let mut edits = Vec::new();
11797 let mut selections_iter = selections.iter().peekable();
11798 while let Some(selection) = selections_iter.next() {
11799 let mut rows = selection.spanned_rows(false, &display_map);
11800 // duplicate line-wise
11801 if whole_lines || selection.start == selection.end {
11802 // Avoid duplicating the same lines twice.
11803 while let Some(next_selection) = selections_iter.peek() {
11804 let next_rows = next_selection.spanned_rows(false, &display_map);
11805 if next_rows.start < rows.end {
11806 rows.end = next_rows.end;
11807 selections_iter.next().unwrap();
11808 } else {
11809 break;
11810 }
11811 }
11812
11813 // Copy the text from the selected row region and splice it either at the start
11814 // or end of the region.
11815 let start = Point::new(rows.start.0, 0);
11816 let end = Point::new(
11817 rows.end.previous_row().0,
11818 buffer.line_len(rows.end.previous_row()),
11819 );
11820
11821 let mut text = buffer.text_for_range(start..end).collect::<String>();
11822
11823 let insert_location = if upwards {
11824 // When duplicating upward, we need to insert before the current line.
11825 // If we're on the last line and it doesn't end with a newline,
11826 // we need to add a newline before the duplicated content.
11827 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11828 && buffer.max_point().column > 0
11829 && !text.ends_with('\n');
11830
11831 if needs_leading_newline {
11832 text.insert(0, '\n');
11833 end
11834 } else {
11835 text.push('\n');
11836 Point::new(rows.start.0, 0)
11837 }
11838 } else {
11839 text.push('\n');
11840 start
11841 };
11842 edits.push((insert_location..insert_location, text));
11843 } else {
11844 // duplicate character-wise
11845 let start = selection.start;
11846 let end = selection.end;
11847 let text = buffer.text_for_range(start..end).collect::<String>();
11848 edits.push((selection.end..selection.end, text));
11849 }
11850 }
11851
11852 self.transact(window, cx, |this, window, cx| {
11853 this.buffer.update(cx, |buffer, cx| {
11854 buffer.edit(edits, None, cx);
11855 });
11856
11857 // When duplicating upward with whole lines, move the cursor to the duplicated line
11858 if upwards && whole_lines {
11859 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11860
11861 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11862 let mut new_ranges = Vec::new();
11863 let selections = s.all::<Point>(&display_map);
11864 let mut selections_iter = selections.iter().peekable();
11865
11866 while let Some(first_selection) = selections_iter.next() {
11867 // Group contiguous selections together to find the total row span
11868 let mut group_selections = vec![first_selection];
11869 let mut rows = first_selection.spanned_rows(false, &display_map);
11870
11871 while let Some(next_selection) = selections_iter.peek() {
11872 let next_rows = next_selection.spanned_rows(false, &display_map);
11873 if next_rows.start < rows.end {
11874 rows.end = next_rows.end;
11875 group_selections.push(selections_iter.next().unwrap());
11876 } else {
11877 break;
11878 }
11879 }
11880
11881 let row_count = rows.end.0 - rows.start.0;
11882
11883 // Move all selections in this group up by the total number of duplicated rows
11884 for selection in group_selections {
11885 let new_start = Point::new(
11886 selection.start.row.saturating_sub(row_count),
11887 selection.start.column,
11888 );
11889
11890 let new_end = Point::new(
11891 selection.end.row.saturating_sub(row_count),
11892 selection.end.column,
11893 );
11894
11895 new_ranges.push(new_start..new_end);
11896 }
11897 }
11898
11899 s.select_ranges(new_ranges);
11900 });
11901 }
11902
11903 this.request_autoscroll(Autoscroll::fit(), cx);
11904 });
11905 }
11906
11907 pub fn duplicate_line_up(
11908 &mut self,
11909 _: &DuplicateLineUp,
11910 window: &mut Window,
11911 cx: &mut Context<Self>,
11912 ) {
11913 self.duplicate(true, true, window, cx);
11914 }
11915
11916 pub fn duplicate_line_down(
11917 &mut self,
11918 _: &DuplicateLineDown,
11919 window: &mut Window,
11920 cx: &mut Context<Self>,
11921 ) {
11922 self.duplicate(false, true, window, cx);
11923 }
11924
11925 pub fn duplicate_selection(
11926 &mut self,
11927 _: &DuplicateSelection,
11928 window: &mut Window,
11929 cx: &mut Context<Self>,
11930 ) {
11931 self.duplicate(false, false, window, cx);
11932 }
11933
11934 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11935 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11936 if self.mode.is_single_line() {
11937 cx.propagate();
11938 return;
11939 }
11940
11941 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11942 let buffer = self.buffer.read(cx).snapshot(cx);
11943
11944 let mut edits = Vec::new();
11945 let mut unfold_ranges = Vec::new();
11946 let mut refold_creases = Vec::new();
11947
11948 let selections = self.selections.all::<Point>(&display_map);
11949 let mut selections = selections.iter().peekable();
11950 let mut contiguous_row_selections = Vec::new();
11951 let mut new_selections = Vec::new();
11952
11953 while let Some(selection) = selections.next() {
11954 // Find all the selections that span a contiguous row range
11955 let (start_row, end_row) = consume_contiguous_rows(
11956 &mut contiguous_row_selections,
11957 selection,
11958 &display_map,
11959 &mut selections,
11960 );
11961
11962 // Move the text spanned by the row range to be before the line preceding the row range
11963 if start_row.0 > 0 {
11964 let range_to_move = Point::new(
11965 start_row.previous_row().0,
11966 buffer.line_len(start_row.previous_row()),
11967 )
11968 ..Point::new(
11969 end_row.previous_row().0,
11970 buffer.line_len(end_row.previous_row()),
11971 );
11972 let insertion_point = display_map
11973 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11974 .0;
11975
11976 // Don't move lines across excerpts
11977 if buffer
11978 .excerpt_containing(insertion_point..range_to_move.end)
11979 .is_some()
11980 {
11981 let text = buffer
11982 .text_for_range(range_to_move.clone())
11983 .flat_map(|s| s.chars())
11984 .skip(1)
11985 .chain(['\n'])
11986 .collect::<String>();
11987
11988 edits.push((
11989 buffer.anchor_after(range_to_move.start)
11990 ..buffer.anchor_before(range_to_move.end),
11991 String::new(),
11992 ));
11993 let insertion_anchor = buffer.anchor_after(insertion_point);
11994 edits.push((insertion_anchor..insertion_anchor, text));
11995
11996 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11997
11998 // Move selections up
11999 new_selections.extend(contiguous_row_selections.drain(..).map(
12000 |mut selection| {
12001 selection.start.row -= row_delta;
12002 selection.end.row -= row_delta;
12003 selection
12004 },
12005 ));
12006
12007 // Move folds up
12008 unfold_ranges.push(range_to_move.clone());
12009 for fold in display_map.folds_in_range(
12010 buffer.anchor_before(range_to_move.start)
12011 ..buffer.anchor_after(range_to_move.end),
12012 ) {
12013 let mut start = fold.range.start.to_point(&buffer);
12014 let mut end = fold.range.end.to_point(&buffer);
12015 start.row -= row_delta;
12016 end.row -= row_delta;
12017 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12018 }
12019 }
12020 }
12021
12022 // If we didn't move line(s), preserve the existing selections
12023 new_selections.append(&mut contiguous_row_selections);
12024 }
12025
12026 self.transact(window, cx, |this, window, cx| {
12027 this.unfold_ranges(&unfold_ranges, true, true, cx);
12028 this.buffer.update(cx, |buffer, cx| {
12029 for (range, text) in edits {
12030 buffer.edit([(range, text)], None, cx);
12031 }
12032 });
12033 this.fold_creases(refold_creases, true, window, cx);
12034 this.change_selections(Default::default(), window, cx, |s| {
12035 s.select(new_selections);
12036 })
12037 });
12038 }
12039
12040 pub fn move_line_down(
12041 &mut self,
12042 _: &MoveLineDown,
12043 window: &mut Window,
12044 cx: &mut Context<Self>,
12045 ) {
12046 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12047 if self.mode.is_single_line() {
12048 cx.propagate();
12049 return;
12050 }
12051
12052 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12053 let buffer = self.buffer.read(cx).snapshot(cx);
12054
12055 let mut edits = Vec::new();
12056 let mut unfold_ranges = Vec::new();
12057 let mut refold_creases = Vec::new();
12058
12059 let selections = self.selections.all::<Point>(&display_map);
12060 let mut selections = selections.iter().peekable();
12061 let mut contiguous_row_selections = Vec::new();
12062 let mut new_selections = Vec::new();
12063
12064 while let Some(selection) = selections.next() {
12065 // Find all the selections that span a contiguous row range
12066 let (start_row, end_row) = consume_contiguous_rows(
12067 &mut contiguous_row_selections,
12068 selection,
12069 &display_map,
12070 &mut selections,
12071 );
12072
12073 // Move the text spanned by the row range to be after the last line of the row range
12074 if end_row.0 <= buffer.max_point().row {
12075 let range_to_move =
12076 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12077 let insertion_point = display_map
12078 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12079 .0;
12080
12081 // Don't move lines across excerpt boundaries
12082 if buffer
12083 .excerpt_containing(range_to_move.start..insertion_point)
12084 .is_some()
12085 {
12086 let mut text = String::from("\n");
12087 text.extend(buffer.text_for_range(range_to_move.clone()));
12088 text.pop(); // Drop trailing newline
12089 edits.push((
12090 buffer.anchor_after(range_to_move.start)
12091 ..buffer.anchor_before(range_to_move.end),
12092 String::new(),
12093 ));
12094 let insertion_anchor = buffer.anchor_after(insertion_point);
12095 edits.push((insertion_anchor..insertion_anchor, text));
12096
12097 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12098
12099 // Move selections down
12100 new_selections.extend(contiguous_row_selections.drain(..).map(
12101 |mut selection| {
12102 selection.start.row += row_delta;
12103 selection.end.row += row_delta;
12104 selection
12105 },
12106 ));
12107
12108 // Move folds down
12109 unfold_ranges.push(range_to_move.clone());
12110 for fold in display_map.folds_in_range(
12111 buffer.anchor_before(range_to_move.start)
12112 ..buffer.anchor_after(range_to_move.end),
12113 ) {
12114 let mut start = fold.range.start.to_point(&buffer);
12115 let mut end = fold.range.end.to_point(&buffer);
12116 start.row += row_delta;
12117 end.row += row_delta;
12118 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12119 }
12120 }
12121 }
12122
12123 // If we didn't move line(s), preserve the existing selections
12124 new_selections.append(&mut contiguous_row_selections);
12125 }
12126
12127 self.transact(window, cx, |this, window, cx| {
12128 this.unfold_ranges(&unfold_ranges, true, true, cx);
12129 this.buffer.update(cx, |buffer, cx| {
12130 for (range, text) in edits {
12131 buffer.edit([(range, text)], None, cx);
12132 }
12133 });
12134 this.fold_creases(refold_creases, true, window, cx);
12135 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12136 });
12137 }
12138
12139 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12140 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12141 let text_layout_details = &self.text_layout_details(window);
12142 self.transact(window, cx, |this, window, cx| {
12143 let edits = this.change_selections(Default::default(), window, cx, |s| {
12144 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12145 s.move_with(|display_map, selection| {
12146 if !selection.is_empty() {
12147 return;
12148 }
12149
12150 let mut head = selection.head();
12151 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12152 if head.column() == display_map.line_len(head.row()) {
12153 transpose_offset = display_map
12154 .buffer_snapshot()
12155 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12156 }
12157
12158 if transpose_offset == 0 {
12159 return;
12160 }
12161
12162 *head.column_mut() += 1;
12163 head = display_map.clip_point(head, Bias::Right);
12164 let goal = SelectionGoal::HorizontalPosition(
12165 display_map
12166 .x_for_display_point(head, text_layout_details)
12167 .into(),
12168 );
12169 selection.collapse_to(head, goal);
12170
12171 let transpose_start = display_map
12172 .buffer_snapshot()
12173 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12174 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12175 let transpose_end = display_map
12176 .buffer_snapshot()
12177 .clip_offset(transpose_offset + 1, Bias::Right);
12178 if let Some(ch) = display_map
12179 .buffer_snapshot()
12180 .chars_at(transpose_start)
12181 .next()
12182 {
12183 edits.push((transpose_start..transpose_offset, String::new()));
12184 edits.push((transpose_end..transpose_end, ch.to_string()));
12185 }
12186 }
12187 });
12188 edits
12189 });
12190 this.buffer
12191 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12192 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12193 this.change_selections(Default::default(), window, cx, |s| {
12194 s.select(selections);
12195 });
12196 });
12197 }
12198
12199 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12200 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12201 if self.mode.is_single_line() {
12202 cx.propagate();
12203 return;
12204 }
12205
12206 self.rewrap_impl(RewrapOptions::default(), cx)
12207 }
12208
12209 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12210 let buffer = self.buffer.read(cx).snapshot(cx);
12211 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12212
12213 #[derive(Clone, Debug, PartialEq)]
12214 enum CommentFormat {
12215 /// single line comment, with prefix for line
12216 Line(String),
12217 /// single line within a block comment, with prefix for line
12218 BlockLine(String),
12219 /// a single line of a block comment that includes the initial delimiter
12220 BlockCommentWithStart(BlockCommentConfig),
12221 /// a single line of a block comment that includes the ending delimiter
12222 BlockCommentWithEnd(BlockCommentConfig),
12223 }
12224
12225 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12226 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12227 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12228 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12229 .peekable();
12230
12231 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12232 row
12233 } else {
12234 return Vec::new();
12235 };
12236
12237 let language_settings = buffer.language_settings_at(selection.head(), cx);
12238 let language_scope = buffer.language_scope_at(selection.head());
12239
12240 let indent_and_prefix_for_row =
12241 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12242 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12243 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12244 &language_scope
12245 {
12246 let indent_end = Point::new(row, indent.len);
12247 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12248 let line_text_after_indent = buffer
12249 .text_for_range(indent_end..line_end)
12250 .collect::<String>();
12251
12252 let is_within_comment_override = buffer
12253 .language_scope_at(indent_end)
12254 .is_some_and(|scope| scope.override_name() == Some("comment"));
12255 let comment_delimiters = if is_within_comment_override {
12256 // we are within a comment syntax node, but we don't
12257 // yet know what kind of comment: block, doc or line
12258 match (
12259 language_scope.documentation_comment(),
12260 language_scope.block_comment(),
12261 ) {
12262 (Some(config), _) | (_, Some(config))
12263 if buffer.contains_str_at(indent_end, &config.start) =>
12264 {
12265 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12266 }
12267 (Some(config), _) | (_, Some(config))
12268 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12269 {
12270 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12271 }
12272 (Some(config), _) | (_, Some(config))
12273 if buffer.contains_str_at(indent_end, &config.prefix) =>
12274 {
12275 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12276 }
12277 (_, _) => language_scope
12278 .line_comment_prefixes()
12279 .iter()
12280 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12281 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12282 }
12283 } else {
12284 // we not in an overridden comment node, but we may
12285 // be within a non-overridden line comment node
12286 language_scope
12287 .line_comment_prefixes()
12288 .iter()
12289 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12290 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12291 };
12292
12293 let rewrap_prefix = language_scope
12294 .rewrap_prefixes()
12295 .iter()
12296 .find_map(|prefix_regex| {
12297 prefix_regex.find(&line_text_after_indent).map(|mat| {
12298 if mat.start() == 0 {
12299 Some(mat.as_str().to_string())
12300 } else {
12301 None
12302 }
12303 })
12304 })
12305 .flatten();
12306 (comment_delimiters, rewrap_prefix)
12307 } else {
12308 (None, None)
12309 };
12310 (indent, comment_prefix, rewrap_prefix)
12311 };
12312
12313 let mut ranges = Vec::new();
12314 let from_empty_selection = selection.is_empty();
12315
12316 let mut current_range_start = first_row;
12317 let mut prev_row = first_row;
12318 let (
12319 mut current_range_indent,
12320 mut current_range_comment_delimiters,
12321 mut current_range_rewrap_prefix,
12322 ) = indent_and_prefix_for_row(first_row);
12323
12324 for row in non_blank_rows_iter.skip(1) {
12325 let has_paragraph_break = row > prev_row + 1;
12326
12327 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12328 indent_and_prefix_for_row(row);
12329
12330 let has_indent_change = row_indent != current_range_indent;
12331 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12332
12333 let has_boundary_change = has_comment_change
12334 || row_rewrap_prefix.is_some()
12335 || (has_indent_change && current_range_comment_delimiters.is_some());
12336
12337 if has_paragraph_break || has_boundary_change {
12338 ranges.push((
12339 language_settings.clone(),
12340 Point::new(current_range_start, 0)
12341 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12342 current_range_indent,
12343 current_range_comment_delimiters.clone(),
12344 current_range_rewrap_prefix.clone(),
12345 from_empty_selection,
12346 ));
12347 current_range_start = row;
12348 current_range_indent = row_indent;
12349 current_range_comment_delimiters = row_comment_delimiters;
12350 current_range_rewrap_prefix = row_rewrap_prefix;
12351 }
12352 prev_row = row;
12353 }
12354
12355 ranges.push((
12356 language_settings.clone(),
12357 Point::new(current_range_start, 0)
12358 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12359 current_range_indent,
12360 current_range_comment_delimiters,
12361 current_range_rewrap_prefix,
12362 from_empty_selection,
12363 ));
12364
12365 ranges
12366 });
12367
12368 let mut edits = Vec::new();
12369 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12370
12371 for (
12372 language_settings,
12373 wrap_range,
12374 mut indent_size,
12375 comment_prefix,
12376 rewrap_prefix,
12377 from_empty_selection,
12378 ) in wrap_ranges
12379 {
12380 let mut start_row = wrap_range.start.row;
12381 let mut end_row = wrap_range.end.row;
12382
12383 // Skip selections that overlap with a range that has already been rewrapped.
12384 let selection_range = start_row..end_row;
12385 if rewrapped_row_ranges
12386 .iter()
12387 .any(|range| range.overlaps(&selection_range))
12388 {
12389 continue;
12390 }
12391
12392 let tab_size = language_settings.tab_size;
12393
12394 let (line_prefix, inside_comment) = match &comment_prefix {
12395 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12396 (Some(prefix.as_str()), true)
12397 }
12398 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12399 (Some(prefix.as_ref()), true)
12400 }
12401 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12402 start: _,
12403 end: _,
12404 prefix,
12405 tab_size,
12406 })) => {
12407 indent_size.len += tab_size;
12408 (Some(prefix.as_ref()), true)
12409 }
12410 None => (None, false),
12411 };
12412 let indent_prefix = indent_size.chars().collect::<String>();
12413 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12414
12415 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12416 RewrapBehavior::InComments => inside_comment,
12417 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12418 RewrapBehavior::Anywhere => true,
12419 };
12420
12421 let should_rewrap = options.override_language_settings
12422 || allow_rewrap_based_on_language
12423 || self.hard_wrap.is_some();
12424 if !should_rewrap {
12425 continue;
12426 }
12427
12428 if from_empty_selection {
12429 'expand_upwards: while start_row > 0 {
12430 let prev_row = start_row - 1;
12431 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12432 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12433 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12434 {
12435 start_row = prev_row;
12436 } else {
12437 break 'expand_upwards;
12438 }
12439 }
12440
12441 'expand_downwards: while end_row < buffer.max_point().row {
12442 let next_row = end_row + 1;
12443 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12444 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12445 && !buffer.is_line_blank(MultiBufferRow(next_row))
12446 {
12447 end_row = next_row;
12448 } else {
12449 break 'expand_downwards;
12450 }
12451 }
12452 }
12453
12454 let start = Point::new(start_row, 0);
12455 let start_offset = ToOffset::to_offset(&start, &buffer);
12456 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12457 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12458 let mut first_line_delimiter = None;
12459 let mut last_line_delimiter = None;
12460 let Some(lines_without_prefixes) = selection_text
12461 .lines()
12462 .enumerate()
12463 .map(|(ix, line)| {
12464 let line_trimmed = line.trim_start();
12465 if rewrap_prefix.is_some() && ix > 0 {
12466 Ok(line_trimmed)
12467 } else if let Some(
12468 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12469 start,
12470 prefix,
12471 end,
12472 tab_size,
12473 })
12474 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12475 start,
12476 prefix,
12477 end,
12478 tab_size,
12479 }),
12480 ) = &comment_prefix
12481 {
12482 let line_trimmed = line_trimmed
12483 .strip_prefix(start.as_ref())
12484 .map(|s| {
12485 let mut indent_size = indent_size;
12486 indent_size.len -= tab_size;
12487 let indent_prefix: String = indent_size.chars().collect();
12488 first_line_delimiter = Some((indent_prefix, start));
12489 s.trim_start()
12490 })
12491 .unwrap_or(line_trimmed);
12492 let line_trimmed = line_trimmed
12493 .strip_suffix(end.as_ref())
12494 .map(|s| {
12495 last_line_delimiter = Some(end);
12496 s.trim_end()
12497 })
12498 .unwrap_or(line_trimmed);
12499 let line_trimmed = line_trimmed
12500 .strip_prefix(prefix.as_ref())
12501 .unwrap_or(line_trimmed);
12502 Ok(line_trimmed)
12503 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12504 line_trimmed.strip_prefix(prefix).with_context(|| {
12505 format!("line did not start with prefix {prefix:?}: {line:?}")
12506 })
12507 } else {
12508 line_trimmed
12509 .strip_prefix(&line_prefix.trim_start())
12510 .with_context(|| {
12511 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12512 })
12513 }
12514 })
12515 .collect::<Result<Vec<_>, _>>()
12516 .log_err()
12517 else {
12518 continue;
12519 };
12520
12521 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12522 buffer
12523 .language_settings_at(Point::new(start_row, 0), cx)
12524 .preferred_line_length as usize
12525 });
12526
12527 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12528 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12529 } else {
12530 line_prefix.clone()
12531 };
12532
12533 let wrapped_text = {
12534 let mut wrapped_text = wrap_with_prefix(
12535 line_prefix,
12536 subsequent_lines_prefix,
12537 lines_without_prefixes.join("\n"),
12538 wrap_column,
12539 tab_size,
12540 options.preserve_existing_whitespace,
12541 );
12542
12543 if let Some((indent, delimiter)) = first_line_delimiter {
12544 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12545 }
12546 if let Some(last_line) = last_line_delimiter {
12547 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12548 }
12549
12550 wrapped_text
12551 };
12552
12553 // TODO: should always use char-based diff while still supporting cursor behavior that
12554 // matches vim.
12555 let mut diff_options = DiffOptions::default();
12556 if options.override_language_settings {
12557 diff_options.max_word_diff_len = 0;
12558 diff_options.max_word_diff_line_count = 0;
12559 } else {
12560 diff_options.max_word_diff_len = usize::MAX;
12561 diff_options.max_word_diff_line_count = usize::MAX;
12562 }
12563
12564 for (old_range, new_text) in
12565 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12566 {
12567 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12568 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12569 edits.push((edit_start..edit_end, new_text));
12570 }
12571
12572 rewrapped_row_ranges.push(start_row..=end_row);
12573 }
12574
12575 self.buffer
12576 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12577 }
12578
12579 pub fn cut_common(
12580 &mut self,
12581 cut_no_selection_line: bool,
12582 window: &mut Window,
12583 cx: &mut Context<Self>,
12584 ) -> ClipboardItem {
12585 let mut text = String::new();
12586 let buffer = self.buffer.read(cx).snapshot(cx);
12587 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12588 let mut clipboard_selections = Vec::with_capacity(selections.len());
12589 {
12590 let max_point = buffer.max_point();
12591 let mut is_first = true;
12592 let mut prev_selection_was_entire_line = false;
12593 for selection in &mut selections {
12594 let is_entire_line =
12595 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12596 if is_entire_line {
12597 selection.start = Point::new(selection.start.row, 0);
12598 if !selection.is_empty() && selection.end.column == 0 {
12599 selection.end = cmp::min(max_point, selection.end);
12600 } else {
12601 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12602 }
12603 selection.goal = SelectionGoal::None;
12604 }
12605 if is_first {
12606 is_first = false;
12607 } else if !prev_selection_was_entire_line {
12608 text += "\n";
12609 }
12610 prev_selection_was_entire_line = is_entire_line;
12611 let mut len = 0;
12612 for chunk in buffer.text_for_range(selection.start..selection.end) {
12613 text.push_str(chunk);
12614 len += chunk.len();
12615 }
12616 clipboard_selections.push(ClipboardSelection {
12617 len,
12618 is_entire_line,
12619 first_line_indent: buffer
12620 .indent_size_for_line(MultiBufferRow(selection.start.row))
12621 .len,
12622 });
12623 }
12624 }
12625
12626 self.transact(window, cx, |this, window, cx| {
12627 this.change_selections(Default::default(), window, cx, |s| {
12628 s.select(selections);
12629 });
12630 this.insert("", window, cx);
12631 });
12632 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12633 }
12634
12635 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12636 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12637 let item = self.cut_common(true, window, cx);
12638 cx.write_to_clipboard(item);
12639 }
12640
12641 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12642 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12643 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12644 s.move_with(|snapshot, sel| {
12645 if sel.is_empty() {
12646 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12647 }
12648 if sel.is_empty() {
12649 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12650 }
12651 });
12652 });
12653 let item = self.cut_common(false, window, cx);
12654 cx.set_global(KillRing(item))
12655 }
12656
12657 pub fn kill_ring_yank(
12658 &mut self,
12659 _: &KillRingYank,
12660 window: &mut Window,
12661 cx: &mut Context<Self>,
12662 ) {
12663 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12664 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12665 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12666 (kill_ring.text().to_string(), kill_ring.metadata_json())
12667 } else {
12668 return;
12669 }
12670 } else {
12671 return;
12672 };
12673 self.do_paste(&text, metadata, false, window, cx);
12674 }
12675
12676 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12677 self.do_copy(true, cx);
12678 }
12679
12680 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12681 self.do_copy(false, cx);
12682 }
12683
12684 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12685 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12686 let buffer = self.buffer.read(cx).read(cx);
12687 let mut text = String::new();
12688
12689 let mut clipboard_selections = Vec::with_capacity(selections.len());
12690 {
12691 let max_point = buffer.max_point();
12692 let mut is_first = true;
12693 let mut prev_selection_was_entire_line = false;
12694 for selection in &selections {
12695 let mut start = selection.start;
12696 let mut end = selection.end;
12697 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12698 let mut add_trailing_newline = false;
12699 if is_entire_line {
12700 start = Point::new(start.row, 0);
12701 let next_line_start = Point::new(end.row + 1, 0);
12702 if next_line_start <= max_point {
12703 end = next_line_start;
12704 } else {
12705 // We're on the last line without a trailing newline.
12706 // Copy to the end of the line and add a newline afterwards.
12707 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12708 add_trailing_newline = true;
12709 }
12710 }
12711
12712 let mut trimmed_selections = Vec::new();
12713 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12714 let row = MultiBufferRow(start.row);
12715 let first_indent = buffer.indent_size_for_line(row);
12716 if first_indent.len == 0 || start.column > first_indent.len {
12717 trimmed_selections.push(start..end);
12718 } else {
12719 trimmed_selections.push(
12720 Point::new(row.0, first_indent.len)
12721 ..Point::new(row.0, buffer.line_len(row)),
12722 );
12723 for row in start.row + 1..=end.row {
12724 let mut line_len = buffer.line_len(MultiBufferRow(row));
12725 if row == end.row {
12726 line_len = end.column;
12727 }
12728 if line_len == 0 {
12729 trimmed_selections
12730 .push(Point::new(row, 0)..Point::new(row, line_len));
12731 continue;
12732 }
12733 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12734 if row_indent_size.len >= first_indent.len {
12735 trimmed_selections.push(
12736 Point::new(row, first_indent.len)..Point::new(row, line_len),
12737 );
12738 } else {
12739 trimmed_selections.clear();
12740 trimmed_selections.push(start..end);
12741 break;
12742 }
12743 }
12744 }
12745 } else {
12746 trimmed_selections.push(start..end);
12747 }
12748
12749 for trimmed_range in trimmed_selections {
12750 if is_first {
12751 is_first = false;
12752 } else if !prev_selection_was_entire_line {
12753 text += "\n";
12754 }
12755 prev_selection_was_entire_line = is_entire_line;
12756 let mut len = 0;
12757 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12758 text.push_str(chunk);
12759 len += chunk.len();
12760 }
12761 if add_trailing_newline {
12762 text.push('\n');
12763 len += 1;
12764 }
12765 clipboard_selections.push(ClipboardSelection {
12766 len,
12767 is_entire_line,
12768 first_line_indent: buffer
12769 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12770 .len,
12771 });
12772 }
12773 }
12774 }
12775
12776 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12777 text,
12778 clipboard_selections,
12779 ));
12780 }
12781
12782 pub fn do_paste(
12783 &mut self,
12784 text: &String,
12785 clipboard_selections: Option<Vec<ClipboardSelection>>,
12786 handle_entire_lines: bool,
12787 window: &mut Window,
12788 cx: &mut Context<Self>,
12789 ) {
12790 if self.read_only(cx) {
12791 return;
12792 }
12793
12794 let clipboard_text = Cow::Borrowed(text.as_str());
12795
12796 self.transact(window, cx, |this, window, cx| {
12797 let had_active_edit_prediction = this.has_active_edit_prediction();
12798 let display_map = this.display_snapshot(cx);
12799 let old_selections = this.selections.all::<usize>(&display_map);
12800 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12801
12802 if let Some(mut clipboard_selections) = clipboard_selections {
12803 let all_selections_were_entire_line =
12804 clipboard_selections.iter().all(|s| s.is_entire_line);
12805 let first_selection_indent_column =
12806 clipboard_selections.first().map(|s| s.first_line_indent);
12807 if clipboard_selections.len() != old_selections.len() {
12808 clipboard_selections.drain(..);
12809 }
12810 let mut auto_indent_on_paste = true;
12811
12812 this.buffer.update(cx, |buffer, cx| {
12813 let snapshot = buffer.read(cx);
12814 auto_indent_on_paste = snapshot
12815 .language_settings_at(cursor_offset, cx)
12816 .auto_indent_on_paste;
12817
12818 let mut start_offset = 0;
12819 let mut edits = Vec::new();
12820 let mut original_indent_columns = Vec::new();
12821 for (ix, selection) in old_selections.iter().enumerate() {
12822 let to_insert;
12823 let entire_line;
12824 let original_indent_column;
12825 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12826 let end_offset = start_offset + clipboard_selection.len;
12827 to_insert = &clipboard_text[start_offset..end_offset];
12828 entire_line = clipboard_selection.is_entire_line;
12829 start_offset = if entire_line {
12830 end_offset
12831 } else {
12832 end_offset + 1
12833 };
12834 original_indent_column = Some(clipboard_selection.first_line_indent);
12835 } else {
12836 to_insert = &*clipboard_text;
12837 entire_line = all_selections_were_entire_line;
12838 original_indent_column = first_selection_indent_column
12839 }
12840
12841 let (range, to_insert) =
12842 if selection.is_empty() && handle_entire_lines && entire_line {
12843 // If the corresponding selection was empty when this slice of the
12844 // clipboard text was written, then the entire line containing the
12845 // selection was copied. If this selection is also currently empty,
12846 // then paste the line before the current line of the buffer.
12847 let column = selection.start.to_point(&snapshot).column as usize;
12848 let line_start = selection.start - column;
12849 (line_start..line_start, Cow::Borrowed(to_insert))
12850 } else {
12851 let language = snapshot.language_at(selection.head());
12852 let range = selection.range();
12853 if let Some(language) = language
12854 && language.name() == "Markdown".into()
12855 {
12856 edit_for_markdown_paste(
12857 &snapshot,
12858 range,
12859 to_insert,
12860 url::Url::parse(to_insert).ok(),
12861 )
12862 } else {
12863 (range, Cow::Borrowed(to_insert))
12864 }
12865 };
12866
12867 edits.push((range, to_insert));
12868 original_indent_columns.push(original_indent_column);
12869 }
12870 drop(snapshot);
12871
12872 buffer.edit(
12873 edits,
12874 if auto_indent_on_paste {
12875 Some(AutoindentMode::Block {
12876 original_indent_columns,
12877 })
12878 } else {
12879 None
12880 },
12881 cx,
12882 );
12883 });
12884
12885 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12886 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12887 } else {
12888 let url = url::Url::parse(&clipboard_text).ok();
12889
12890 let auto_indent_mode = if !clipboard_text.is_empty() {
12891 Some(AutoindentMode::Block {
12892 original_indent_columns: Vec::new(),
12893 })
12894 } else {
12895 None
12896 };
12897
12898 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12899 let snapshot = buffer.snapshot(cx);
12900
12901 let anchors = old_selections
12902 .iter()
12903 .map(|s| {
12904 let anchor = snapshot.anchor_after(s.head());
12905 s.map(|_| anchor)
12906 })
12907 .collect::<Vec<_>>();
12908
12909 let mut edits = Vec::new();
12910
12911 for selection in old_selections.iter() {
12912 let language = snapshot.language_at(selection.head());
12913 let range = selection.range();
12914
12915 let (edit_range, edit_text) = if let Some(language) = language
12916 && language.name() == "Markdown".into()
12917 {
12918 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12919 } else {
12920 (range, clipboard_text.clone())
12921 };
12922
12923 edits.push((edit_range, edit_text));
12924 }
12925
12926 drop(snapshot);
12927 buffer.edit(edits, auto_indent_mode, cx);
12928
12929 anchors
12930 });
12931
12932 this.change_selections(Default::default(), window, cx, |s| {
12933 s.select_anchors(selection_anchors);
12934 });
12935 }
12936
12937 // 🤔 | .. | show_in_menu |
12938 // | .. | true true
12939 // | had_edit_prediction | false true
12940
12941 let trigger_in_words =
12942 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12943
12944 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12945 });
12946 }
12947
12948 pub fn diff_clipboard_with_selection(
12949 &mut self,
12950 _: &DiffClipboardWithSelection,
12951 window: &mut Window,
12952 cx: &mut Context<Self>,
12953 ) {
12954 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12955
12956 if selections.is_empty() {
12957 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12958 return;
12959 };
12960
12961 let clipboard_text = match cx.read_from_clipboard() {
12962 Some(item) => match item.entries().first() {
12963 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12964 _ => None,
12965 },
12966 None => None,
12967 };
12968
12969 let Some(clipboard_text) = clipboard_text else {
12970 log::warn!("Clipboard doesn't contain text.");
12971 return;
12972 };
12973
12974 window.dispatch_action(
12975 Box::new(DiffClipboardWithSelectionData {
12976 clipboard_text,
12977 editor: cx.entity(),
12978 }),
12979 cx,
12980 );
12981 }
12982
12983 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12984 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12985 if let Some(item) = cx.read_from_clipboard() {
12986 let entries = item.entries();
12987
12988 match entries.first() {
12989 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12990 // of all the pasted entries.
12991 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12992 .do_paste(
12993 clipboard_string.text(),
12994 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12995 true,
12996 window,
12997 cx,
12998 ),
12999 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13000 }
13001 }
13002 }
13003
13004 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13005 if self.read_only(cx) {
13006 return;
13007 }
13008
13009 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13010
13011 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13012 if let Some((selections, _)) =
13013 self.selection_history.transaction(transaction_id).cloned()
13014 {
13015 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13016 s.select_anchors(selections.to_vec());
13017 });
13018 } else {
13019 log::error!(
13020 "No entry in selection_history found for undo. \
13021 This may correspond to a bug where undo does not update the selection. \
13022 If this is occurring, please add details to \
13023 https://github.com/zed-industries/zed/issues/22692"
13024 );
13025 }
13026 self.request_autoscroll(Autoscroll::fit(), cx);
13027 self.unmark_text(window, cx);
13028 self.refresh_edit_prediction(true, false, window, cx);
13029 cx.emit(EditorEvent::Edited { transaction_id });
13030 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13031 }
13032 }
13033
13034 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13035 if self.read_only(cx) {
13036 return;
13037 }
13038
13039 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13040
13041 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13042 if let Some((_, Some(selections))) =
13043 self.selection_history.transaction(transaction_id).cloned()
13044 {
13045 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13046 s.select_anchors(selections.to_vec());
13047 });
13048 } else {
13049 log::error!(
13050 "No entry in selection_history found for redo. \
13051 This may correspond to a bug where undo does not update the selection. \
13052 If this is occurring, please add details to \
13053 https://github.com/zed-industries/zed/issues/22692"
13054 );
13055 }
13056 self.request_autoscroll(Autoscroll::fit(), cx);
13057 self.unmark_text(window, cx);
13058 self.refresh_edit_prediction(true, false, window, cx);
13059 cx.emit(EditorEvent::Edited { transaction_id });
13060 }
13061 }
13062
13063 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13064 self.buffer
13065 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13066 }
13067
13068 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13069 self.buffer
13070 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13071 }
13072
13073 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13074 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13075 self.change_selections(Default::default(), window, cx, |s| {
13076 s.move_with(|map, selection| {
13077 let cursor = if selection.is_empty() {
13078 movement::left(map, selection.start)
13079 } else {
13080 selection.start
13081 };
13082 selection.collapse_to(cursor, SelectionGoal::None);
13083 });
13084 })
13085 }
13086
13087 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13088 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13089 self.change_selections(Default::default(), window, cx, |s| {
13090 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13091 })
13092 }
13093
13094 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13095 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13096 self.change_selections(Default::default(), window, cx, |s| {
13097 s.move_with(|map, selection| {
13098 let cursor = if selection.is_empty() {
13099 movement::right(map, selection.end)
13100 } else {
13101 selection.end
13102 };
13103 selection.collapse_to(cursor, SelectionGoal::None)
13104 });
13105 })
13106 }
13107
13108 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13109 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13110 self.change_selections(Default::default(), window, cx, |s| {
13111 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13112 });
13113 }
13114
13115 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13116 if self.take_rename(true, window, cx).is_some() {
13117 return;
13118 }
13119
13120 if self.mode.is_single_line() {
13121 cx.propagate();
13122 return;
13123 }
13124
13125 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13126
13127 let text_layout_details = &self.text_layout_details(window);
13128 let selection_count = self.selections.count();
13129 let first_selection = self.selections.first_anchor();
13130
13131 self.change_selections(Default::default(), window, cx, |s| {
13132 s.move_with(|map, selection| {
13133 if !selection.is_empty() {
13134 selection.goal = SelectionGoal::None;
13135 }
13136 let (cursor, goal) = movement::up(
13137 map,
13138 selection.start,
13139 selection.goal,
13140 false,
13141 text_layout_details,
13142 );
13143 selection.collapse_to(cursor, goal);
13144 });
13145 });
13146
13147 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13148 {
13149 cx.propagate();
13150 }
13151 }
13152
13153 pub fn move_up_by_lines(
13154 &mut self,
13155 action: &MoveUpByLines,
13156 window: &mut Window,
13157 cx: &mut Context<Self>,
13158 ) {
13159 if self.take_rename(true, window, cx).is_some() {
13160 return;
13161 }
13162
13163 if self.mode.is_single_line() {
13164 cx.propagate();
13165 return;
13166 }
13167
13168 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13169
13170 let text_layout_details = &self.text_layout_details(window);
13171
13172 self.change_selections(Default::default(), window, cx, |s| {
13173 s.move_with(|map, selection| {
13174 if !selection.is_empty() {
13175 selection.goal = SelectionGoal::None;
13176 }
13177 let (cursor, goal) = movement::up_by_rows(
13178 map,
13179 selection.start,
13180 action.lines,
13181 selection.goal,
13182 false,
13183 text_layout_details,
13184 );
13185 selection.collapse_to(cursor, goal);
13186 });
13187 })
13188 }
13189
13190 pub fn move_down_by_lines(
13191 &mut self,
13192 action: &MoveDownByLines,
13193 window: &mut Window,
13194 cx: &mut Context<Self>,
13195 ) {
13196 if self.take_rename(true, window, cx).is_some() {
13197 return;
13198 }
13199
13200 if self.mode.is_single_line() {
13201 cx.propagate();
13202 return;
13203 }
13204
13205 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13206
13207 let text_layout_details = &self.text_layout_details(window);
13208
13209 self.change_selections(Default::default(), window, cx, |s| {
13210 s.move_with(|map, selection| {
13211 if !selection.is_empty() {
13212 selection.goal = SelectionGoal::None;
13213 }
13214 let (cursor, goal) = movement::down_by_rows(
13215 map,
13216 selection.start,
13217 action.lines,
13218 selection.goal,
13219 false,
13220 text_layout_details,
13221 );
13222 selection.collapse_to(cursor, goal);
13223 });
13224 })
13225 }
13226
13227 pub fn select_down_by_lines(
13228 &mut self,
13229 action: &SelectDownByLines,
13230 window: &mut Window,
13231 cx: &mut Context<Self>,
13232 ) {
13233 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13234 let text_layout_details = &self.text_layout_details(window);
13235 self.change_selections(Default::default(), window, cx, |s| {
13236 s.move_heads_with(|map, head, goal| {
13237 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13238 })
13239 })
13240 }
13241
13242 pub fn select_up_by_lines(
13243 &mut self,
13244 action: &SelectUpByLines,
13245 window: &mut Window,
13246 cx: &mut Context<Self>,
13247 ) {
13248 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13249 let text_layout_details = &self.text_layout_details(window);
13250 self.change_selections(Default::default(), window, cx, |s| {
13251 s.move_heads_with(|map, head, goal| {
13252 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13253 })
13254 })
13255 }
13256
13257 pub fn select_page_up(
13258 &mut self,
13259 _: &SelectPageUp,
13260 window: &mut Window,
13261 cx: &mut Context<Self>,
13262 ) {
13263 let Some(row_count) = self.visible_row_count() else {
13264 return;
13265 };
13266
13267 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13268
13269 let text_layout_details = &self.text_layout_details(window);
13270
13271 self.change_selections(Default::default(), window, cx, |s| {
13272 s.move_heads_with(|map, head, goal| {
13273 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13274 })
13275 })
13276 }
13277
13278 pub fn move_page_up(
13279 &mut self,
13280 action: &MovePageUp,
13281 window: &mut Window,
13282 cx: &mut Context<Self>,
13283 ) {
13284 if self.take_rename(true, window, cx).is_some() {
13285 return;
13286 }
13287
13288 if self
13289 .context_menu
13290 .borrow_mut()
13291 .as_mut()
13292 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13293 .unwrap_or(false)
13294 {
13295 return;
13296 }
13297
13298 if matches!(self.mode, EditorMode::SingleLine) {
13299 cx.propagate();
13300 return;
13301 }
13302
13303 let Some(row_count) = self.visible_row_count() else {
13304 return;
13305 };
13306
13307 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13308
13309 let effects = if action.center_cursor {
13310 SelectionEffects::scroll(Autoscroll::center())
13311 } else {
13312 SelectionEffects::default()
13313 };
13314
13315 let text_layout_details = &self.text_layout_details(window);
13316
13317 self.change_selections(effects, window, cx, |s| {
13318 s.move_with(|map, selection| {
13319 if !selection.is_empty() {
13320 selection.goal = SelectionGoal::None;
13321 }
13322 let (cursor, goal) = movement::up_by_rows(
13323 map,
13324 selection.end,
13325 row_count,
13326 selection.goal,
13327 false,
13328 text_layout_details,
13329 );
13330 selection.collapse_to(cursor, goal);
13331 });
13332 });
13333 }
13334
13335 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13336 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13337 let text_layout_details = &self.text_layout_details(window);
13338 self.change_selections(Default::default(), window, cx, |s| {
13339 s.move_heads_with(|map, head, goal| {
13340 movement::up(map, head, goal, false, text_layout_details)
13341 })
13342 })
13343 }
13344
13345 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13346 self.take_rename(true, window, cx);
13347
13348 if self.mode.is_single_line() {
13349 cx.propagate();
13350 return;
13351 }
13352
13353 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13354
13355 let text_layout_details = &self.text_layout_details(window);
13356 let selection_count = self.selections.count();
13357 let first_selection = self.selections.first_anchor();
13358
13359 self.change_selections(Default::default(), window, cx, |s| {
13360 s.move_with(|map, selection| {
13361 if !selection.is_empty() {
13362 selection.goal = SelectionGoal::None;
13363 }
13364 let (cursor, goal) = movement::down(
13365 map,
13366 selection.end,
13367 selection.goal,
13368 false,
13369 text_layout_details,
13370 );
13371 selection.collapse_to(cursor, goal);
13372 });
13373 });
13374
13375 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13376 {
13377 cx.propagate();
13378 }
13379 }
13380
13381 pub fn select_page_down(
13382 &mut self,
13383 _: &SelectPageDown,
13384 window: &mut Window,
13385 cx: &mut Context<Self>,
13386 ) {
13387 let Some(row_count) = self.visible_row_count() else {
13388 return;
13389 };
13390
13391 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13392
13393 let text_layout_details = &self.text_layout_details(window);
13394
13395 self.change_selections(Default::default(), window, cx, |s| {
13396 s.move_heads_with(|map, head, goal| {
13397 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13398 })
13399 })
13400 }
13401
13402 pub fn move_page_down(
13403 &mut self,
13404 action: &MovePageDown,
13405 window: &mut Window,
13406 cx: &mut Context<Self>,
13407 ) {
13408 if self.take_rename(true, window, cx).is_some() {
13409 return;
13410 }
13411
13412 if self
13413 .context_menu
13414 .borrow_mut()
13415 .as_mut()
13416 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13417 .unwrap_or(false)
13418 {
13419 return;
13420 }
13421
13422 if matches!(self.mode, EditorMode::SingleLine) {
13423 cx.propagate();
13424 return;
13425 }
13426
13427 let Some(row_count) = self.visible_row_count() else {
13428 return;
13429 };
13430
13431 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13432
13433 let effects = if action.center_cursor {
13434 SelectionEffects::scroll(Autoscroll::center())
13435 } else {
13436 SelectionEffects::default()
13437 };
13438
13439 let text_layout_details = &self.text_layout_details(window);
13440 self.change_selections(effects, window, cx, |s| {
13441 s.move_with(|map, selection| {
13442 if !selection.is_empty() {
13443 selection.goal = SelectionGoal::None;
13444 }
13445 let (cursor, goal) = movement::down_by_rows(
13446 map,
13447 selection.end,
13448 row_count,
13449 selection.goal,
13450 false,
13451 text_layout_details,
13452 );
13453 selection.collapse_to(cursor, goal);
13454 });
13455 });
13456 }
13457
13458 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13459 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13460 let text_layout_details = &self.text_layout_details(window);
13461 self.change_selections(Default::default(), window, cx, |s| {
13462 s.move_heads_with(|map, head, goal| {
13463 movement::down(map, head, goal, false, text_layout_details)
13464 })
13465 });
13466 }
13467
13468 pub fn context_menu_first(
13469 &mut self,
13470 _: &ContextMenuFirst,
13471 window: &mut Window,
13472 cx: &mut Context<Self>,
13473 ) {
13474 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13475 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13476 }
13477 }
13478
13479 pub fn context_menu_prev(
13480 &mut self,
13481 _: &ContextMenuPrevious,
13482 window: &mut Window,
13483 cx: &mut Context<Self>,
13484 ) {
13485 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13486 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13487 }
13488 }
13489
13490 pub fn context_menu_next(
13491 &mut self,
13492 _: &ContextMenuNext,
13493 window: &mut Window,
13494 cx: &mut Context<Self>,
13495 ) {
13496 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13497 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13498 }
13499 }
13500
13501 pub fn context_menu_last(
13502 &mut self,
13503 _: &ContextMenuLast,
13504 window: &mut Window,
13505 cx: &mut Context<Self>,
13506 ) {
13507 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13508 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13509 }
13510 }
13511
13512 pub fn signature_help_prev(
13513 &mut self,
13514 _: &SignatureHelpPrevious,
13515 _: &mut Window,
13516 cx: &mut Context<Self>,
13517 ) {
13518 if let Some(popover) = self.signature_help_state.popover_mut() {
13519 if popover.current_signature == 0 {
13520 popover.current_signature = popover.signatures.len() - 1;
13521 } else {
13522 popover.current_signature -= 1;
13523 }
13524 cx.notify();
13525 }
13526 }
13527
13528 pub fn signature_help_next(
13529 &mut self,
13530 _: &SignatureHelpNext,
13531 _: &mut Window,
13532 cx: &mut Context<Self>,
13533 ) {
13534 if let Some(popover) = self.signature_help_state.popover_mut() {
13535 if popover.current_signature + 1 == popover.signatures.len() {
13536 popover.current_signature = 0;
13537 } else {
13538 popover.current_signature += 1;
13539 }
13540 cx.notify();
13541 }
13542 }
13543
13544 pub fn move_to_previous_word_start(
13545 &mut self,
13546 _: &MoveToPreviousWordStart,
13547 window: &mut Window,
13548 cx: &mut Context<Self>,
13549 ) {
13550 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13551 self.change_selections(Default::default(), window, cx, |s| {
13552 s.move_cursors_with(|map, head, _| {
13553 (
13554 movement::previous_word_start(map, head),
13555 SelectionGoal::None,
13556 )
13557 });
13558 })
13559 }
13560
13561 pub fn move_to_previous_subword_start(
13562 &mut self,
13563 _: &MoveToPreviousSubwordStart,
13564 window: &mut Window,
13565 cx: &mut Context<Self>,
13566 ) {
13567 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13568 self.change_selections(Default::default(), window, cx, |s| {
13569 s.move_cursors_with(|map, head, _| {
13570 (
13571 movement::previous_subword_start(map, head),
13572 SelectionGoal::None,
13573 )
13574 });
13575 })
13576 }
13577
13578 pub fn select_to_previous_word_start(
13579 &mut self,
13580 _: &SelectToPreviousWordStart,
13581 window: &mut Window,
13582 cx: &mut Context<Self>,
13583 ) {
13584 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13585 self.change_selections(Default::default(), window, cx, |s| {
13586 s.move_heads_with(|map, head, _| {
13587 (
13588 movement::previous_word_start(map, head),
13589 SelectionGoal::None,
13590 )
13591 });
13592 })
13593 }
13594
13595 pub fn select_to_previous_subword_start(
13596 &mut self,
13597 _: &SelectToPreviousSubwordStart,
13598 window: &mut Window,
13599 cx: &mut Context<Self>,
13600 ) {
13601 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13602 self.change_selections(Default::default(), window, cx, |s| {
13603 s.move_heads_with(|map, head, _| {
13604 (
13605 movement::previous_subword_start(map, head),
13606 SelectionGoal::None,
13607 )
13608 });
13609 })
13610 }
13611
13612 pub fn delete_to_previous_word_start(
13613 &mut self,
13614 action: &DeleteToPreviousWordStart,
13615 window: &mut Window,
13616 cx: &mut Context<Self>,
13617 ) {
13618 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13619 self.transact(window, cx, |this, window, cx| {
13620 this.select_autoclose_pair(window, cx);
13621 this.change_selections(Default::default(), window, cx, |s| {
13622 s.move_with(|map, selection| {
13623 if selection.is_empty() {
13624 let mut cursor = if action.ignore_newlines {
13625 movement::previous_word_start(map, selection.head())
13626 } else {
13627 movement::previous_word_start_or_newline(map, selection.head())
13628 };
13629 cursor = movement::adjust_greedy_deletion(
13630 map,
13631 selection.head(),
13632 cursor,
13633 action.ignore_brackets,
13634 );
13635 selection.set_head(cursor, SelectionGoal::None);
13636 }
13637 });
13638 });
13639 this.insert("", window, cx);
13640 });
13641 }
13642
13643 pub fn delete_to_previous_subword_start(
13644 &mut self,
13645 _: &DeleteToPreviousSubwordStart,
13646 window: &mut Window,
13647 cx: &mut Context<Self>,
13648 ) {
13649 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13650 self.transact(window, cx, |this, window, cx| {
13651 this.select_autoclose_pair(window, cx);
13652 this.change_selections(Default::default(), window, cx, |s| {
13653 s.move_with(|map, selection| {
13654 if selection.is_empty() {
13655 let mut cursor = movement::previous_subword_start(map, selection.head());
13656 cursor =
13657 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13658 selection.set_head(cursor, SelectionGoal::None);
13659 }
13660 });
13661 });
13662 this.insert("", window, cx);
13663 });
13664 }
13665
13666 pub fn move_to_next_word_end(
13667 &mut self,
13668 _: &MoveToNextWordEnd,
13669 window: &mut Window,
13670 cx: &mut Context<Self>,
13671 ) {
13672 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13673 self.change_selections(Default::default(), window, cx, |s| {
13674 s.move_cursors_with(|map, head, _| {
13675 (movement::next_word_end(map, head), SelectionGoal::None)
13676 });
13677 })
13678 }
13679
13680 pub fn move_to_next_subword_end(
13681 &mut self,
13682 _: &MoveToNextSubwordEnd,
13683 window: &mut Window,
13684 cx: &mut Context<Self>,
13685 ) {
13686 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13687 self.change_selections(Default::default(), window, cx, |s| {
13688 s.move_cursors_with(|map, head, _| {
13689 (movement::next_subword_end(map, head), SelectionGoal::None)
13690 });
13691 })
13692 }
13693
13694 pub fn select_to_next_word_end(
13695 &mut self,
13696 _: &SelectToNextWordEnd,
13697 window: &mut Window,
13698 cx: &mut Context<Self>,
13699 ) {
13700 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13701 self.change_selections(Default::default(), window, cx, |s| {
13702 s.move_heads_with(|map, head, _| {
13703 (movement::next_word_end(map, head), SelectionGoal::None)
13704 });
13705 })
13706 }
13707
13708 pub fn select_to_next_subword_end(
13709 &mut self,
13710 _: &SelectToNextSubwordEnd,
13711 window: &mut Window,
13712 cx: &mut Context<Self>,
13713 ) {
13714 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13715 self.change_selections(Default::default(), window, cx, |s| {
13716 s.move_heads_with(|map, head, _| {
13717 (movement::next_subword_end(map, head), SelectionGoal::None)
13718 });
13719 })
13720 }
13721
13722 pub fn delete_to_next_word_end(
13723 &mut self,
13724 action: &DeleteToNextWordEnd,
13725 window: &mut Window,
13726 cx: &mut Context<Self>,
13727 ) {
13728 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13729 self.transact(window, cx, |this, window, cx| {
13730 this.change_selections(Default::default(), window, cx, |s| {
13731 s.move_with(|map, selection| {
13732 if selection.is_empty() {
13733 let mut cursor = if action.ignore_newlines {
13734 movement::next_word_end(map, selection.head())
13735 } else {
13736 movement::next_word_end_or_newline(map, selection.head())
13737 };
13738 cursor = movement::adjust_greedy_deletion(
13739 map,
13740 selection.head(),
13741 cursor,
13742 action.ignore_brackets,
13743 );
13744 selection.set_head(cursor, SelectionGoal::None);
13745 }
13746 });
13747 });
13748 this.insert("", window, cx);
13749 });
13750 }
13751
13752 pub fn delete_to_next_subword_end(
13753 &mut self,
13754 _: &DeleteToNextSubwordEnd,
13755 window: &mut Window,
13756 cx: &mut Context<Self>,
13757 ) {
13758 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13759 self.transact(window, cx, |this, window, cx| {
13760 this.change_selections(Default::default(), window, cx, |s| {
13761 s.move_with(|map, selection| {
13762 if selection.is_empty() {
13763 let mut cursor = movement::next_subword_end(map, selection.head());
13764 cursor =
13765 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13766 selection.set_head(cursor, SelectionGoal::None);
13767 }
13768 });
13769 });
13770 this.insert("", window, cx);
13771 });
13772 }
13773
13774 pub fn move_to_beginning_of_line(
13775 &mut self,
13776 action: &MoveToBeginningOfLine,
13777 window: &mut Window,
13778 cx: &mut Context<Self>,
13779 ) {
13780 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13781 self.change_selections(Default::default(), window, cx, |s| {
13782 s.move_cursors_with(|map, head, _| {
13783 (
13784 movement::indented_line_beginning(
13785 map,
13786 head,
13787 action.stop_at_soft_wraps,
13788 action.stop_at_indent,
13789 ),
13790 SelectionGoal::None,
13791 )
13792 });
13793 })
13794 }
13795
13796 pub fn select_to_beginning_of_line(
13797 &mut self,
13798 action: &SelectToBeginningOfLine,
13799 window: &mut Window,
13800 cx: &mut Context<Self>,
13801 ) {
13802 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13803 self.change_selections(Default::default(), window, cx, |s| {
13804 s.move_heads_with(|map, head, _| {
13805 (
13806 movement::indented_line_beginning(
13807 map,
13808 head,
13809 action.stop_at_soft_wraps,
13810 action.stop_at_indent,
13811 ),
13812 SelectionGoal::None,
13813 )
13814 });
13815 });
13816 }
13817
13818 pub fn delete_to_beginning_of_line(
13819 &mut self,
13820 action: &DeleteToBeginningOfLine,
13821 window: &mut Window,
13822 cx: &mut Context<Self>,
13823 ) {
13824 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13825 self.transact(window, cx, |this, window, cx| {
13826 this.change_selections(Default::default(), window, cx, |s| {
13827 s.move_with(|_, selection| {
13828 selection.reversed = true;
13829 });
13830 });
13831
13832 this.select_to_beginning_of_line(
13833 &SelectToBeginningOfLine {
13834 stop_at_soft_wraps: false,
13835 stop_at_indent: action.stop_at_indent,
13836 },
13837 window,
13838 cx,
13839 );
13840 this.backspace(&Backspace, window, cx);
13841 });
13842 }
13843
13844 pub fn move_to_end_of_line(
13845 &mut self,
13846 action: &MoveToEndOfLine,
13847 window: &mut Window,
13848 cx: &mut Context<Self>,
13849 ) {
13850 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13851 self.change_selections(Default::default(), window, cx, |s| {
13852 s.move_cursors_with(|map, head, _| {
13853 (
13854 movement::line_end(map, head, action.stop_at_soft_wraps),
13855 SelectionGoal::None,
13856 )
13857 });
13858 })
13859 }
13860
13861 pub fn select_to_end_of_line(
13862 &mut self,
13863 action: &SelectToEndOfLine,
13864 window: &mut Window,
13865 cx: &mut Context<Self>,
13866 ) {
13867 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13868 self.change_selections(Default::default(), window, cx, |s| {
13869 s.move_heads_with(|map, head, _| {
13870 (
13871 movement::line_end(map, head, action.stop_at_soft_wraps),
13872 SelectionGoal::None,
13873 )
13874 });
13875 })
13876 }
13877
13878 pub fn delete_to_end_of_line(
13879 &mut self,
13880 _: &DeleteToEndOfLine,
13881 window: &mut Window,
13882 cx: &mut Context<Self>,
13883 ) {
13884 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13885 self.transact(window, cx, |this, window, cx| {
13886 this.select_to_end_of_line(
13887 &SelectToEndOfLine {
13888 stop_at_soft_wraps: false,
13889 },
13890 window,
13891 cx,
13892 );
13893 this.delete(&Delete, window, cx);
13894 });
13895 }
13896
13897 pub fn cut_to_end_of_line(
13898 &mut self,
13899 action: &CutToEndOfLine,
13900 window: &mut Window,
13901 cx: &mut Context<Self>,
13902 ) {
13903 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13904 self.transact(window, cx, |this, window, cx| {
13905 this.select_to_end_of_line(
13906 &SelectToEndOfLine {
13907 stop_at_soft_wraps: false,
13908 },
13909 window,
13910 cx,
13911 );
13912 if !action.stop_at_newlines {
13913 this.change_selections(Default::default(), window, cx, |s| {
13914 s.move_with(|_, sel| {
13915 if sel.is_empty() {
13916 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13917 }
13918 });
13919 });
13920 }
13921 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13922 let item = this.cut_common(false, window, cx);
13923 cx.write_to_clipboard(item);
13924 });
13925 }
13926
13927 pub fn move_to_start_of_paragraph(
13928 &mut self,
13929 _: &MoveToStartOfParagraph,
13930 window: &mut Window,
13931 cx: &mut Context<Self>,
13932 ) {
13933 if matches!(self.mode, EditorMode::SingleLine) {
13934 cx.propagate();
13935 return;
13936 }
13937 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13938 self.change_selections(Default::default(), window, cx, |s| {
13939 s.move_with(|map, selection| {
13940 selection.collapse_to(
13941 movement::start_of_paragraph(map, selection.head(), 1),
13942 SelectionGoal::None,
13943 )
13944 });
13945 })
13946 }
13947
13948 pub fn move_to_end_of_paragraph(
13949 &mut self,
13950 _: &MoveToEndOfParagraph,
13951 window: &mut Window,
13952 cx: &mut Context<Self>,
13953 ) {
13954 if matches!(self.mode, EditorMode::SingleLine) {
13955 cx.propagate();
13956 return;
13957 }
13958 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13959 self.change_selections(Default::default(), window, cx, |s| {
13960 s.move_with(|map, selection| {
13961 selection.collapse_to(
13962 movement::end_of_paragraph(map, selection.head(), 1),
13963 SelectionGoal::None,
13964 )
13965 });
13966 })
13967 }
13968
13969 pub fn select_to_start_of_paragraph(
13970 &mut self,
13971 _: &SelectToStartOfParagraph,
13972 window: &mut Window,
13973 cx: &mut Context<Self>,
13974 ) {
13975 if matches!(self.mode, EditorMode::SingleLine) {
13976 cx.propagate();
13977 return;
13978 }
13979 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13980 self.change_selections(Default::default(), window, cx, |s| {
13981 s.move_heads_with(|map, head, _| {
13982 (
13983 movement::start_of_paragraph(map, head, 1),
13984 SelectionGoal::None,
13985 )
13986 });
13987 })
13988 }
13989
13990 pub fn select_to_end_of_paragraph(
13991 &mut self,
13992 _: &SelectToEndOfParagraph,
13993 window: &mut Window,
13994 cx: &mut Context<Self>,
13995 ) {
13996 if matches!(self.mode, EditorMode::SingleLine) {
13997 cx.propagate();
13998 return;
13999 }
14000 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14001 self.change_selections(Default::default(), window, cx, |s| {
14002 s.move_heads_with(|map, head, _| {
14003 (
14004 movement::end_of_paragraph(map, head, 1),
14005 SelectionGoal::None,
14006 )
14007 });
14008 })
14009 }
14010
14011 pub fn move_to_start_of_excerpt(
14012 &mut self,
14013 _: &MoveToStartOfExcerpt,
14014 window: &mut Window,
14015 cx: &mut Context<Self>,
14016 ) {
14017 if matches!(self.mode, EditorMode::SingleLine) {
14018 cx.propagate();
14019 return;
14020 }
14021 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14022 self.change_selections(Default::default(), window, cx, |s| {
14023 s.move_with(|map, selection| {
14024 selection.collapse_to(
14025 movement::start_of_excerpt(
14026 map,
14027 selection.head(),
14028 workspace::searchable::Direction::Prev,
14029 ),
14030 SelectionGoal::None,
14031 )
14032 });
14033 })
14034 }
14035
14036 pub fn move_to_start_of_next_excerpt(
14037 &mut self,
14038 _: &MoveToStartOfNextExcerpt,
14039 window: &mut Window,
14040 cx: &mut Context<Self>,
14041 ) {
14042 if matches!(self.mode, EditorMode::SingleLine) {
14043 cx.propagate();
14044 return;
14045 }
14046
14047 self.change_selections(Default::default(), window, cx, |s| {
14048 s.move_with(|map, selection| {
14049 selection.collapse_to(
14050 movement::start_of_excerpt(
14051 map,
14052 selection.head(),
14053 workspace::searchable::Direction::Next,
14054 ),
14055 SelectionGoal::None,
14056 )
14057 });
14058 })
14059 }
14060
14061 pub fn move_to_end_of_excerpt(
14062 &mut self,
14063 _: &MoveToEndOfExcerpt,
14064 window: &mut Window,
14065 cx: &mut Context<Self>,
14066 ) {
14067 if matches!(self.mode, EditorMode::SingleLine) {
14068 cx.propagate();
14069 return;
14070 }
14071 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14072 self.change_selections(Default::default(), window, cx, |s| {
14073 s.move_with(|map, selection| {
14074 selection.collapse_to(
14075 movement::end_of_excerpt(
14076 map,
14077 selection.head(),
14078 workspace::searchable::Direction::Next,
14079 ),
14080 SelectionGoal::None,
14081 )
14082 });
14083 })
14084 }
14085
14086 pub fn move_to_end_of_previous_excerpt(
14087 &mut self,
14088 _: &MoveToEndOfPreviousExcerpt,
14089 window: &mut Window,
14090 cx: &mut Context<Self>,
14091 ) {
14092 if matches!(self.mode, EditorMode::SingleLine) {
14093 cx.propagate();
14094 return;
14095 }
14096 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14097 self.change_selections(Default::default(), window, cx, |s| {
14098 s.move_with(|map, selection| {
14099 selection.collapse_to(
14100 movement::end_of_excerpt(
14101 map,
14102 selection.head(),
14103 workspace::searchable::Direction::Prev,
14104 ),
14105 SelectionGoal::None,
14106 )
14107 });
14108 })
14109 }
14110
14111 pub fn select_to_start_of_excerpt(
14112 &mut self,
14113 _: &SelectToStartOfExcerpt,
14114 window: &mut Window,
14115 cx: &mut Context<Self>,
14116 ) {
14117 if matches!(self.mode, EditorMode::SingleLine) {
14118 cx.propagate();
14119 return;
14120 }
14121 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14122 self.change_selections(Default::default(), window, cx, |s| {
14123 s.move_heads_with(|map, head, _| {
14124 (
14125 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14126 SelectionGoal::None,
14127 )
14128 });
14129 })
14130 }
14131
14132 pub fn select_to_start_of_next_excerpt(
14133 &mut self,
14134 _: &SelectToStartOfNextExcerpt,
14135 window: &mut Window,
14136 cx: &mut Context<Self>,
14137 ) {
14138 if matches!(self.mode, EditorMode::SingleLine) {
14139 cx.propagate();
14140 return;
14141 }
14142 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14143 self.change_selections(Default::default(), window, cx, |s| {
14144 s.move_heads_with(|map, head, _| {
14145 (
14146 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14147 SelectionGoal::None,
14148 )
14149 });
14150 })
14151 }
14152
14153 pub fn select_to_end_of_excerpt(
14154 &mut self,
14155 _: &SelectToEndOfExcerpt,
14156 window: &mut Window,
14157 cx: &mut Context<Self>,
14158 ) {
14159 if matches!(self.mode, EditorMode::SingleLine) {
14160 cx.propagate();
14161 return;
14162 }
14163 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14164 self.change_selections(Default::default(), window, cx, |s| {
14165 s.move_heads_with(|map, head, _| {
14166 (
14167 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14168 SelectionGoal::None,
14169 )
14170 });
14171 })
14172 }
14173
14174 pub fn select_to_end_of_previous_excerpt(
14175 &mut self,
14176 _: &SelectToEndOfPreviousExcerpt,
14177 window: &mut Window,
14178 cx: &mut Context<Self>,
14179 ) {
14180 if matches!(self.mode, EditorMode::SingleLine) {
14181 cx.propagate();
14182 return;
14183 }
14184 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14185 self.change_selections(Default::default(), window, cx, |s| {
14186 s.move_heads_with(|map, head, _| {
14187 (
14188 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14189 SelectionGoal::None,
14190 )
14191 });
14192 })
14193 }
14194
14195 pub fn move_to_beginning(
14196 &mut self,
14197 _: &MoveToBeginning,
14198 window: &mut Window,
14199 cx: &mut Context<Self>,
14200 ) {
14201 if matches!(self.mode, EditorMode::SingleLine) {
14202 cx.propagate();
14203 return;
14204 }
14205 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14206 self.change_selections(Default::default(), window, cx, |s| {
14207 s.select_ranges(vec![0..0]);
14208 });
14209 }
14210
14211 pub fn select_to_beginning(
14212 &mut self,
14213 _: &SelectToBeginning,
14214 window: &mut Window,
14215 cx: &mut Context<Self>,
14216 ) {
14217 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14218 selection.set_head(Point::zero(), SelectionGoal::None);
14219 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14220 self.change_selections(Default::default(), window, cx, |s| {
14221 s.select(vec![selection]);
14222 });
14223 }
14224
14225 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14226 if matches!(self.mode, EditorMode::SingleLine) {
14227 cx.propagate();
14228 return;
14229 }
14230 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14231 let cursor = self.buffer.read(cx).read(cx).len();
14232 self.change_selections(Default::default(), window, cx, |s| {
14233 s.select_ranges(vec![cursor..cursor])
14234 });
14235 }
14236
14237 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14238 self.nav_history = nav_history;
14239 }
14240
14241 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14242 self.nav_history.as_ref()
14243 }
14244
14245 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14246 self.push_to_nav_history(
14247 self.selections.newest_anchor().head(),
14248 None,
14249 false,
14250 true,
14251 cx,
14252 );
14253 }
14254
14255 fn push_to_nav_history(
14256 &mut self,
14257 cursor_anchor: Anchor,
14258 new_position: Option<Point>,
14259 is_deactivate: bool,
14260 always: bool,
14261 cx: &mut Context<Self>,
14262 ) {
14263 if let Some(nav_history) = self.nav_history.as_mut() {
14264 let buffer = self.buffer.read(cx).read(cx);
14265 let cursor_position = cursor_anchor.to_point(&buffer);
14266 let scroll_state = self.scroll_manager.anchor();
14267 let scroll_top_row = scroll_state.top_row(&buffer);
14268 drop(buffer);
14269
14270 if let Some(new_position) = new_position {
14271 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14272 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14273 return;
14274 }
14275 }
14276
14277 nav_history.push(
14278 Some(NavigationData {
14279 cursor_anchor,
14280 cursor_position,
14281 scroll_anchor: scroll_state,
14282 scroll_top_row,
14283 }),
14284 cx,
14285 );
14286 cx.emit(EditorEvent::PushedToNavHistory {
14287 anchor: cursor_anchor,
14288 is_deactivate,
14289 })
14290 }
14291 }
14292
14293 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14294 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14295 let buffer = self.buffer.read(cx).snapshot(cx);
14296 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14297 selection.set_head(buffer.len(), SelectionGoal::None);
14298 self.change_selections(Default::default(), window, cx, |s| {
14299 s.select(vec![selection]);
14300 });
14301 }
14302
14303 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14304 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14305 let end = self.buffer.read(cx).read(cx).len();
14306 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14307 s.select_ranges(vec![0..end]);
14308 });
14309 }
14310
14311 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14312 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14313 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14314 let mut selections = self.selections.all::<Point>(&display_map);
14315 let max_point = display_map.buffer_snapshot().max_point();
14316 for selection in &mut selections {
14317 let rows = selection.spanned_rows(true, &display_map);
14318 selection.start = Point::new(rows.start.0, 0);
14319 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14320 selection.reversed = false;
14321 }
14322 self.change_selections(Default::default(), window, cx, |s| {
14323 s.select(selections);
14324 });
14325 }
14326
14327 pub fn split_selection_into_lines(
14328 &mut self,
14329 action: &SplitSelectionIntoLines,
14330 window: &mut Window,
14331 cx: &mut Context<Self>,
14332 ) {
14333 let selections = self
14334 .selections
14335 .all::<Point>(&self.display_snapshot(cx))
14336 .into_iter()
14337 .map(|selection| selection.start..selection.end)
14338 .collect::<Vec<_>>();
14339 self.unfold_ranges(&selections, true, true, cx);
14340
14341 let mut new_selection_ranges = Vec::new();
14342 {
14343 let buffer = self.buffer.read(cx).read(cx);
14344 for selection in selections {
14345 for row in selection.start.row..selection.end.row {
14346 let line_start = Point::new(row, 0);
14347 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14348
14349 if action.keep_selections {
14350 // Keep the selection range for each line
14351 let selection_start = if row == selection.start.row {
14352 selection.start
14353 } else {
14354 line_start
14355 };
14356 new_selection_ranges.push(selection_start..line_end);
14357 } else {
14358 // Collapse to cursor at end of line
14359 new_selection_ranges.push(line_end..line_end);
14360 }
14361 }
14362
14363 let is_multiline_selection = selection.start.row != selection.end.row;
14364 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14365 // so this action feels more ergonomic when paired with other selection operations
14366 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14367 if !should_skip_last {
14368 if action.keep_selections {
14369 if is_multiline_selection {
14370 let line_start = Point::new(selection.end.row, 0);
14371 new_selection_ranges.push(line_start..selection.end);
14372 } else {
14373 new_selection_ranges.push(selection.start..selection.end);
14374 }
14375 } else {
14376 new_selection_ranges.push(selection.end..selection.end);
14377 }
14378 }
14379 }
14380 }
14381 self.change_selections(Default::default(), window, cx, |s| {
14382 s.select_ranges(new_selection_ranges);
14383 });
14384 }
14385
14386 pub fn add_selection_above(
14387 &mut self,
14388 action: &AddSelectionAbove,
14389 window: &mut Window,
14390 cx: &mut Context<Self>,
14391 ) {
14392 self.add_selection(true, action.skip_soft_wrap, window, cx);
14393 }
14394
14395 pub fn add_selection_below(
14396 &mut self,
14397 action: &AddSelectionBelow,
14398 window: &mut Window,
14399 cx: &mut Context<Self>,
14400 ) {
14401 self.add_selection(false, action.skip_soft_wrap, window, cx);
14402 }
14403
14404 fn add_selection(
14405 &mut self,
14406 above: bool,
14407 skip_soft_wrap: bool,
14408 window: &mut Window,
14409 cx: &mut Context<Self>,
14410 ) {
14411 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14412
14413 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14414 let all_selections = self.selections.all::<Point>(&display_map);
14415 let text_layout_details = self.text_layout_details(window);
14416
14417 let (mut columnar_selections, new_selections_to_columnarize) = {
14418 if let Some(state) = self.add_selections_state.as_ref() {
14419 let columnar_selection_ids: HashSet<_> = state
14420 .groups
14421 .iter()
14422 .flat_map(|group| group.stack.iter())
14423 .copied()
14424 .collect();
14425
14426 all_selections
14427 .into_iter()
14428 .partition(|s| columnar_selection_ids.contains(&s.id))
14429 } else {
14430 (Vec::new(), all_selections)
14431 }
14432 };
14433
14434 let mut state = self
14435 .add_selections_state
14436 .take()
14437 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14438
14439 for selection in new_selections_to_columnarize {
14440 let range = selection.display_range(&display_map).sorted();
14441 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14442 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14443 let positions = start_x.min(end_x)..start_x.max(end_x);
14444 let mut stack = Vec::new();
14445 for row in range.start.row().0..=range.end.row().0 {
14446 if let Some(selection) = self.selections.build_columnar_selection(
14447 &display_map,
14448 DisplayRow(row),
14449 &positions,
14450 selection.reversed,
14451 &text_layout_details,
14452 ) {
14453 stack.push(selection.id);
14454 columnar_selections.push(selection);
14455 }
14456 }
14457 if !stack.is_empty() {
14458 if above {
14459 stack.reverse();
14460 }
14461 state.groups.push(AddSelectionsGroup { above, stack });
14462 }
14463 }
14464
14465 let mut final_selections = Vec::new();
14466 let end_row = if above {
14467 DisplayRow(0)
14468 } else {
14469 display_map.max_point().row()
14470 };
14471
14472 let mut last_added_item_per_group = HashMap::default();
14473 for group in state.groups.iter_mut() {
14474 if let Some(last_id) = group.stack.last() {
14475 last_added_item_per_group.insert(*last_id, group);
14476 }
14477 }
14478
14479 for selection in columnar_selections {
14480 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14481 if above == group.above {
14482 let range = selection.display_range(&display_map).sorted();
14483 debug_assert_eq!(range.start.row(), range.end.row());
14484 let mut row = range.start.row();
14485 let positions =
14486 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14487 Pixels::from(start)..Pixels::from(end)
14488 } else {
14489 let start_x =
14490 display_map.x_for_display_point(range.start, &text_layout_details);
14491 let end_x =
14492 display_map.x_for_display_point(range.end, &text_layout_details);
14493 start_x.min(end_x)..start_x.max(end_x)
14494 };
14495
14496 let mut maybe_new_selection = None;
14497 let direction = if above { -1 } else { 1 };
14498
14499 while row != end_row {
14500 if skip_soft_wrap {
14501 row = display_map
14502 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14503 .row();
14504 } else if above {
14505 row.0 -= 1;
14506 } else {
14507 row.0 += 1;
14508 }
14509
14510 if let Some(new_selection) = self.selections.build_columnar_selection(
14511 &display_map,
14512 row,
14513 &positions,
14514 selection.reversed,
14515 &text_layout_details,
14516 ) {
14517 maybe_new_selection = Some(new_selection);
14518 break;
14519 }
14520 }
14521
14522 if let Some(new_selection) = maybe_new_selection {
14523 group.stack.push(new_selection.id);
14524 if above {
14525 final_selections.push(new_selection);
14526 final_selections.push(selection);
14527 } else {
14528 final_selections.push(selection);
14529 final_selections.push(new_selection);
14530 }
14531 } else {
14532 final_selections.push(selection);
14533 }
14534 } else {
14535 group.stack.pop();
14536 }
14537 } else {
14538 final_selections.push(selection);
14539 }
14540 }
14541
14542 self.change_selections(Default::default(), window, cx, |s| {
14543 s.select(final_selections);
14544 });
14545
14546 let final_selection_ids: HashSet<_> = self
14547 .selections
14548 .all::<Point>(&display_map)
14549 .iter()
14550 .map(|s| s.id)
14551 .collect();
14552 state.groups.retain_mut(|group| {
14553 // selections might get merged above so we remove invalid items from stacks
14554 group.stack.retain(|id| final_selection_ids.contains(id));
14555
14556 // single selection in stack can be treated as initial state
14557 group.stack.len() > 1
14558 });
14559
14560 if !state.groups.is_empty() {
14561 self.add_selections_state = Some(state);
14562 }
14563 }
14564
14565 fn select_match_ranges(
14566 &mut self,
14567 range: Range<usize>,
14568 reversed: bool,
14569 replace_newest: bool,
14570 auto_scroll: Option<Autoscroll>,
14571 window: &mut Window,
14572 cx: &mut Context<Editor>,
14573 ) {
14574 self.unfold_ranges(
14575 std::slice::from_ref(&range),
14576 false,
14577 auto_scroll.is_some(),
14578 cx,
14579 );
14580 let effects = if let Some(scroll) = auto_scroll {
14581 SelectionEffects::scroll(scroll)
14582 } else {
14583 SelectionEffects::no_scroll()
14584 };
14585 self.change_selections(effects, window, cx, |s| {
14586 if replace_newest {
14587 s.delete(s.newest_anchor().id);
14588 }
14589 if reversed {
14590 s.insert_range(range.end..range.start);
14591 } else {
14592 s.insert_range(range);
14593 }
14594 });
14595 }
14596
14597 pub fn select_next_match_internal(
14598 &mut self,
14599 display_map: &DisplaySnapshot,
14600 replace_newest: bool,
14601 autoscroll: Option<Autoscroll>,
14602 window: &mut Window,
14603 cx: &mut Context<Self>,
14604 ) -> Result<()> {
14605 let buffer = display_map.buffer_snapshot();
14606 let mut selections = self.selections.all::<usize>(&display_map);
14607 if let Some(mut select_next_state) = self.select_next_state.take() {
14608 let query = &select_next_state.query;
14609 if !select_next_state.done {
14610 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14611 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14612 let mut next_selected_range = None;
14613
14614 let bytes_after_last_selection =
14615 buffer.bytes_in_range(last_selection.end..buffer.len());
14616 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14617 let query_matches = query
14618 .stream_find_iter(bytes_after_last_selection)
14619 .map(|result| (last_selection.end, result))
14620 .chain(
14621 query
14622 .stream_find_iter(bytes_before_first_selection)
14623 .map(|result| (0, result)),
14624 );
14625
14626 for (start_offset, query_match) in query_matches {
14627 let query_match = query_match.unwrap(); // can only fail due to I/O
14628 let offset_range =
14629 start_offset + query_match.start()..start_offset + query_match.end();
14630
14631 if !select_next_state.wordwise
14632 || (!buffer.is_inside_word(offset_range.start, None)
14633 && !buffer.is_inside_word(offset_range.end, None))
14634 {
14635 let idx = selections
14636 .partition_point(|selection| selection.end <= offset_range.start);
14637 let overlaps = selections
14638 .get(idx)
14639 .map_or(false, |selection| selection.start < offset_range.end);
14640
14641 if !overlaps {
14642 next_selected_range = Some(offset_range);
14643 break;
14644 }
14645 }
14646 }
14647
14648 if let Some(next_selected_range) = next_selected_range {
14649 self.select_match_ranges(
14650 next_selected_range,
14651 last_selection.reversed,
14652 replace_newest,
14653 autoscroll,
14654 window,
14655 cx,
14656 );
14657 } else {
14658 select_next_state.done = true;
14659 }
14660 }
14661
14662 self.select_next_state = Some(select_next_state);
14663 } else {
14664 let mut only_carets = true;
14665 let mut same_text_selected = true;
14666 let mut selected_text = None;
14667
14668 let mut selections_iter = selections.iter().peekable();
14669 while let Some(selection) = selections_iter.next() {
14670 if selection.start != selection.end {
14671 only_carets = false;
14672 }
14673
14674 if same_text_selected {
14675 if selected_text.is_none() {
14676 selected_text =
14677 Some(buffer.text_for_range(selection.range()).collect::<String>());
14678 }
14679
14680 if let Some(next_selection) = selections_iter.peek() {
14681 if next_selection.range().len() == selection.range().len() {
14682 let next_selected_text = buffer
14683 .text_for_range(next_selection.range())
14684 .collect::<String>();
14685 if Some(next_selected_text) != selected_text {
14686 same_text_selected = false;
14687 selected_text = None;
14688 }
14689 } else {
14690 same_text_selected = false;
14691 selected_text = None;
14692 }
14693 }
14694 }
14695 }
14696
14697 if only_carets {
14698 for selection in &mut selections {
14699 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14700 selection.start = word_range.start;
14701 selection.end = word_range.end;
14702 selection.goal = SelectionGoal::None;
14703 selection.reversed = false;
14704 self.select_match_ranges(
14705 selection.start..selection.end,
14706 selection.reversed,
14707 replace_newest,
14708 autoscroll,
14709 window,
14710 cx,
14711 );
14712 }
14713
14714 if selections.len() == 1 {
14715 let selection = selections
14716 .last()
14717 .expect("ensured that there's only one selection");
14718 let query = buffer
14719 .text_for_range(selection.start..selection.end)
14720 .collect::<String>();
14721 let is_empty = query.is_empty();
14722 let select_state = SelectNextState {
14723 query: self.build_query(&[query], cx)?,
14724 wordwise: true,
14725 done: is_empty,
14726 };
14727 self.select_next_state = Some(select_state);
14728 } else {
14729 self.select_next_state = None;
14730 }
14731 } else if let Some(selected_text) = selected_text {
14732 self.select_next_state = Some(SelectNextState {
14733 query: self.build_query(&[selected_text], cx)?,
14734 wordwise: false,
14735 done: false,
14736 });
14737 self.select_next_match_internal(
14738 display_map,
14739 replace_newest,
14740 autoscroll,
14741 window,
14742 cx,
14743 )?;
14744 }
14745 }
14746 Ok(())
14747 }
14748
14749 pub fn select_all_matches(
14750 &mut self,
14751 _action: &SelectAllMatches,
14752 window: &mut Window,
14753 cx: &mut Context<Self>,
14754 ) -> Result<()> {
14755 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14756
14757 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14758
14759 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14760 let Some(select_next_state) = self.select_next_state.as_mut() else {
14761 return Ok(());
14762 };
14763 if select_next_state.done {
14764 return Ok(());
14765 }
14766
14767 let mut new_selections = Vec::new();
14768
14769 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14770 let buffer = display_map.buffer_snapshot();
14771 let query_matches = select_next_state
14772 .query
14773 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14774
14775 for query_match in query_matches.into_iter() {
14776 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14777 let offset_range = if reversed {
14778 query_match.end()..query_match.start()
14779 } else {
14780 query_match.start()..query_match.end()
14781 };
14782
14783 if !select_next_state.wordwise
14784 || (!buffer.is_inside_word(offset_range.start, None)
14785 && !buffer.is_inside_word(offset_range.end, None))
14786 {
14787 new_selections.push(offset_range.start..offset_range.end);
14788 }
14789 }
14790
14791 select_next_state.done = true;
14792
14793 if new_selections.is_empty() {
14794 log::error!("bug: new_selections is empty in select_all_matches");
14795 return Ok(());
14796 }
14797
14798 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14799 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14800 selections.select_ranges(new_selections)
14801 });
14802
14803 Ok(())
14804 }
14805
14806 pub fn select_next(
14807 &mut self,
14808 action: &SelectNext,
14809 window: &mut Window,
14810 cx: &mut Context<Self>,
14811 ) -> Result<()> {
14812 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14813 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14814 self.select_next_match_internal(
14815 &display_map,
14816 action.replace_newest,
14817 Some(Autoscroll::newest()),
14818 window,
14819 cx,
14820 )?;
14821 Ok(())
14822 }
14823
14824 pub fn select_previous(
14825 &mut self,
14826 action: &SelectPrevious,
14827 window: &mut Window,
14828 cx: &mut Context<Self>,
14829 ) -> Result<()> {
14830 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14831 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14832 let buffer = display_map.buffer_snapshot();
14833 let mut selections = self.selections.all::<usize>(&display_map);
14834 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14835 let query = &select_prev_state.query;
14836 if !select_prev_state.done {
14837 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14838 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14839 let mut next_selected_range = None;
14840 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14841 let bytes_before_last_selection =
14842 buffer.reversed_bytes_in_range(0..last_selection.start);
14843 let bytes_after_first_selection =
14844 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14845 let query_matches = query
14846 .stream_find_iter(bytes_before_last_selection)
14847 .map(|result| (last_selection.start, result))
14848 .chain(
14849 query
14850 .stream_find_iter(bytes_after_first_selection)
14851 .map(|result| (buffer.len(), result)),
14852 );
14853 for (end_offset, query_match) in query_matches {
14854 let query_match = query_match.unwrap(); // can only fail due to I/O
14855 let offset_range =
14856 end_offset - query_match.end()..end_offset - query_match.start();
14857
14858 if !select_prev_state.wordwise
14859 || (!buffer.is_inside_word(offset_range.start, None)
14860 && !buffer.is_inside_word(offset_range.end, None))
14861 {
14862 next_selected_range = Some(offset_range);
14863 break;
14864 }
14865 }
14866
14867 if let Some(next_selected_range) = next_selected_range {
14868 self.select_match_ranges(
14869 next_selected_range,
14870 last_selection.reversed,
14871 action.replace_newest,
14872 Some(Autoscroll::newest()),
14873 window,
14874 cx,
14875 );
14876 } else {
14877 select_prev_state.done = true;
14878 }
14879 }
14880
14881 self.select_prev_state = Some(select_prev_state);
14882 } else {
14883 let mut only_carets = true;
14884 let mut same_text_selected = true;
14885 let mut selected_text = None;
14886
14887 let mut selections_iter = selections.iter().peekable();
14888 while let Some(selection) = selections_iter.next() {
14889 if selection.start != selection.end {
14890 only_carets = false;
14891 }
14892
14893 if same_text_selected {
14894 if selected_text.is_none() {
14895 selected_text =
14896 Some(buffer.text_for_range(selection.range()).collect::<String>());
14897 }
14898
14899 if let Some(next_selection) = selections_iter.peek() {
14900 if next_selection.range().len() == selection.range().len() {
14901 let next_selected_text = buffer
14902 .text_for_range(next_selection.range())
14903 .collect::<String>();
14904 if Some(next_selected_text) != selected_text {
14905 same_text_selected = false;
14906 selected_text = None;
14907 }
14908 } else {
14909 same_text_selected = false;
14910 selected_text = None;
14911 }
14912 }
14913 }
14914 }
14915
14916 if only_carets {
14917 for selection in &mut selections {
14918 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14919 selection.start = word_range.start;
14920 selection.end = word_range.end;
14921 selection.goal = SelectionGoal::None;
14922 selection.reversed = false;
14923 self.select_match_ranges(
14924 selection.start..selection.end,
14925 selection.reversed,
14926 action.replace_newest,
14927 Some(Autoscroll::newest()),
14928 window,
14929 cx,
14930 );
14931 }
14932 if selections.len() == 1 {
14933 let selection = selections
14934 .last()
14935 .expect("ensured that there's only one selection");
14936 let query = buffer
14937 .text_for_range(selection.start..selection.end)
14938 .collect::<String>();
14939 let is_empty = query.is_empty();
14940 let select_state = SelectNextState {
14941 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
14942 wordwise: true,
14943 done: is_empty,
14944 };
14945 self.select_prev_state = Some(select_state);
14946 } else {
14947 self.select_prev_state = None;
14948 }
14949 } else if let Some(selected_text) = selected_text {
14950 self.select_prev_state = Some(SelectNextState {
14951 query: self
14952 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
14953 wordwise: false,
14954 done: false,
14955 });
14956 self.select_previous(action, window, cx)?;
14957 }
14958 }
14959 Ok(())
14960 }
14961
14962 /// Builds an `AhoCorasick` automaton from the provided patterns, while
14963 /// setting the case sensitivity based on the global
14964 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
14965 /// editor's settings.
14966 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
14967 where
14968 I: IntoIterator<Item = P>,
14969 P: AsRef<[u8]>,
14970 {
14971 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
14972 || EditorSettings::get_global(cx).search.case_sensitive,
14973 |value| value,
14974 );
14975
14976 let mut builder = AhoCorasickBuilder::new();
14977 builder.ascii_case_insensitive(!case_sensitive);
14978 builder.build(patterns)
14979 }
14980
14981 pub fn find_next_match(
14982 &mut self,
14983 _: &FindNextMatch,
14984 window: &mut Window,
14985 cx: &mut Context<Self>,
14986 ) -> Result<()> {
14987 let selections = self.selections.disjoint_anchors_arc();
14988 match selections.first() {
14989 Some(first) if selections.len() >= 2 => {
14990 self.change_selections(Default::default(), window, cx, |s| {
14991 s.select_ranges([first.range()]);
14992 });
14993 }
14994 _ => self.select_next(
14995 &SelectNext {
14996 replace_newest: true,
14997 },
14998 window,
14999 cx,
15000 )?,
15001 }
15002 Ok(())
15003 }
15004
15005 pub fn find_previous_match(
15006 &mut self,
15007 _: &FindPreviousMatch,
15008 window: &mut Window,
15009 cx: &mut Context<Self>,
15010 ) -> Result<()> {
15011 let selections = self.selections.disjoint_anchors_arc();
15012 match selections.last() {
15013 Some(last) if selections.len() >= 2 => {
15014 self.change_selections(Default::default(), window, cx, |s| {
15015 s.select_ranges([last.range()]);
15016 });
15017 }
15018 _ => self.select_previous(
15019 &SelectPrevious {
15020 replace_newest: true,
15021 },
15022 window,
15023 cx,
15024 )?,
15025 }
15026 Ok(())
15027 }
15028
15029 pub fn toggle_comments(
15030 &mut self,
15031 action: &ToggleComments,
15032 window: &mut Window,
15033 cx: &mut Context<Self>,
15034 ) {
15035 if self.read_only(cx) {
15036 return;
15037 }
15038 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15039 let text_layout_details = &self.text_layout_details(window);
15040 self.transact(window, cx, |this, window, cx| {
15041 let mut selections = this
15042 .selections
15043 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15044 let mut edits = Vec::new();
15045 let mut selection_edit_ranges = Vec::new();
15046 let mut last_toggled_row = None;
15047 let snapshot = this.buffer.read(cx).read(cx);
15048 let empty_str: Arc<str> = Arc::default();
15049 let mut suffixes_inserted = Vec::new();
15050 let ignore_indent = action.ignore_indent;
15051
15052 fn comment_prefix_range(
15053 snapshot: &MultiBufferSnapshot,
15054 row: MultiBufferRow,
15055 comment_prefix: &str,
15056 comment_prefix_whitespace: &str,
15057 ignore_indent: bool,
15058 ) -> Range<Point> {
15059 let indent_size = if ignore_indent {
15060 0
15061 } else {
15062 snapshot.indent_size_for_line(row).len
15063 };
15064
15065 let start = Point::new(row.0, indent_size);
15066
15067 let mut line_bytes = snapshot
15068 .bytes_in_range(start..snapshot.max_point())
15069 .flatten()
15070 .copied();
15071
15072 // If this line currently begins with the line comment prefix, then record
15073 // the range containing the prefix.
15074 if line_bytes
15075 .by_ref()
15076 .take(comment_prefix.len())
15077 .eq(comment_prefix.bytes())
15078 {
15079 // Include any whitespace that matches the comment prefix.
15080 let matching_whitespace_len = line_bytes
15081 .zip(comment_prefix_whitespace.bytes())
15082 .take_while(|(a, b)| a == b)
15083 .count() as u32;
15084 let end = Point::new(
15085 start.row,
15086 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15087 );
15088 start..end
15089 } else {
15090 start..start
15091 }
15092 }
15093
15094 fn comment_suffix_range(
15095 snapshot: &MultiBufferSnapshot,
15096 row: MultiBufferRow,
15097 comment_suffix: &str,
15098 comment_suffix_has_leading_space: bool,
15099 ) -> Range<Point> {
15100 let end = Point::new(row.0, snapshot.line_len(row));
15101 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15102
15103 let mut line_end_bytes = snapshot
15104 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15105 .flatten()
15106 .copied();
15107
15108 let leading_space_len = if suffix_start_column > 0
15109 && line_end_bytes.next() == Some(b' ')
15110 && comment_suffix_has_leading_space
15111 {
15112 1
15113 } else {
15114 0
15115 };
15116
15117 // If this line currently begins with the line comment prefix, then record
15118 // the range containing the prefix.
15119 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15120 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15121 start..end
15122 } else {
15123 end..end
15124 }
15125 }
15126
15127 // TODO: Handle selections that cross excerpts
15128 for selection in &mut selections {
15129 let start_column = snapshot
15130 .indent_size_for_line(MultiBufferRow(selection.start.row))
15131 .len;
15132 let language = if let Some(language) =
15133 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15134 {
15135 language
15136 } else {
15137 continue;
15138 };
15139
15140 selection_edit_ranges.clear();
15141
15142 // If multiple selections contain a given row, avoid processing that
15143 // row more than once.
15144 let mut start_row = MultiBufferRow(selection.start.row);
15145 if last_toggled_row == Some(start_row) {
15146 start_row = start_row.next_row();
15147 }
15148 let end_row =
15149 if selection.end.row > selection.start.row && selection.end.column == 0 {
15150 MultiBufferRow(selection.end.row - 1)
15151 } else {
15152 MultiBufferRow(selection.end.row)
15153 };
15154 last_toggled_row = Some(end_row);
15155
15156 if start_row > end_row {
15157 continue;
15158 }
15159
15160 // If the language has line comments, toggle those.
15161 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15162
15163 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15164 if ignore_indent {
15165 full_comment_prefixes = full_comment_prefixes
15166 .into_iter()
15167 .map(|s| Arc::from(s.trim_end()))
15168 .collect();
15169 }
15170
15171 if !full_comment_prefixes.is_empty() {
15172 let first_prefix = full_comment_prefixes
15173 .first()
15174 .expect("prefixes is non-empty");
15175 let prefix_trimmed_lengths = full_comment_prefixes
15176 .iter()
15177 .map(|p| p.trim_end_matches(' ').len())
15178 .collect::<SmallVec<[usize; 4]>>();
15179
15180 let mut all_selection_lines_are_comments = true;
15181
15182 for row in start_row.0..=end_row.0 {
15183 let row = MultiBufferRow(row);
15184 if start_row < end_row && snapshot.is_line_blank(row) {
15185 continue;
15186 }
15187
15188 let prefix_range = full_comment_prefixes
15189 .iter()
15190 .zip(prefix_trimmed_lengths.iter().copied())
15191 .map(|(prefix, trimmed_prefix_len)| {
15192 comment_prefix_range(
15193 snapshot.deref(),
15194 row,
15195 &prefix[..trimmed_prefix_len],
15196 &prefix[trimmed_prefix_len..],
15197 ignore_indent,
15198 )
15199 })
15200 .max_by_key(|range| range.end.column - range.start.column)
15201 .expect("prefixes is non-empty");
15202
15203 if prefix_range.is_empty() {
15204 all_selection_lines_are_comments = false;
15205 }
15206
15207 selection_edit_ranges.push(prefix_range);
15208 }
15209
15210 if all_selection_lines_are_comments {
15211 edits.extend(
15212 selection_edit_ranges
15213 .iter()
15214 .cloned()
15215 .map(|range| (range, empty_str.clone())),
15216 );
15217 } else {
15218 let min_column = selection_edit_ranges
15219 .iter()
15220 .map(|range| range.start.column)
15221 .min()
15222 .unwrap_or(0);
15223 edits.extend(selection_edit_ranges.iter().map(|range| {
15224 let position = Point::new(range.start.row, min_column);
15225 (position..position, first_prefix.clone())
15226 }));
15227 }
15228 } else if let Some(BlockCommentConfig {
15229 start: full_comment_prefix,
15230 end: comment_suffix,
15231 ..
15232 }) = language.block_comment()
15233 {
15234 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15235 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15236 let prefix_range = comment_prefix_range(
15237 snapshot.deref(),
15238 start_row,
15239 comment_prefix,
15240 comment_prefix_whitespace,
15241 ignore_indent,
15242 );
15243 let suffix_range = comment_suffix_range(
15244 snapshot.deref(),
15245 end_row,
15246 comment_suffix.trim_start_matches(' '),
15247 comment_suffix.starts_with(' '),
15248 );
15249
15250 if prefix_range.is_empty() || suffix_range.is_empty() {
15251 edits.push((
15252 prefix_range.start..prefix_range.start,
15253 full_comment_prefix.clone(),
15254 ));
15255 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15256 suffixes_inserted.push((end_row, comment_suffix.len()));
15257 } else {
15258 edits.push((prefix_range, empty_str.clone()));
15259 edits.push((suffix_range, empty_str.clone()));
15260 }
15261 } else {
15262 continue;
15263 }
15264 }
15265
15266 drop(snapshot);
15267 this.buffer.update(cx, |buffer, cx| {
15268 buffer.edit(edits, None, cx);
15269 });
15270
15271 // Adjust selections so that they end before any comment suffixes that
15272 // were inserted.
15273 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15274 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15275 let snapshot = this.buffer.read(cx).read(cx);
15276 for selection in &mut selections {
15277 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15278 match row.cmp(&MultiBufferRow(selection.end.row)) {
15279 Ordering::Less => {
15280 suffixes_inserted.next();
15281 continue;
15282 }
15283 Ordering::Greater => break,
15284 Ordering::Equal => {
15285 if selection.end.column == snapshot.line_len(row) {
15286 if selection.is_empty() {
15287 selection.start.column -= suffix_len as u32;
15288 }
15289 selection.end.column -= suffix_len as u32;
15290 }
15291 break;
15292 }
15293 }
15294 }
15295 }
15296
15297 drop(snapshot);
15298 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15299
15300 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15301 let selections_on_single_row = selections.windows(2).all(|selections| {
15302 selections[0].start.row == selections[1].start.row
15303 && selections[0].end.row == selections[1].end.row
15304 && selections[0].start.row == selections[0].end.row
15305 });
15306 let selections_selecting = selections
15307 .iter()
15308 .any(|selection| selection.start != selection.end);
15309 let advance_downwards = action.advance_downwards
15310 && selections_on_single_row
15311 && !selections_selecting
15312 && !matches!(this.mode, EditorMode::SingleLine);
15313
15314 if advance_downwards {
15315 let snapshot = this.buffer.read(cx).snapshot(cx);
15316
15317 this.change_selections(Default::default(), window, cx, |s| {
15318 s.move_cursors_with(|display_snapshot, display_point, _| {
15319 let mut point = display_point.to_point(display_snapshot);
15320 point.row += 1;
15321 point = snapshot.clip_point(point, Bias::Left);
15322 let display_point = point.to_display_point(display_snapshot);
15323 let goal = SelectionGoal::HorizontalPosition(
15324 display_snapshot
15325 .x_for_display_point(display_point, text_layout_details)
15326 .into(),
15327 );
15328 (display_point, goal)
15329 })
15330 });
15331 }
15332 });
15333 }
15334
15335 pub fn select_enclosing_symbol(
15336 &mut self,
15337 _: &SelectEnclosingSymbol,
15338 window: &mut Window,
15339 cx: &mut Context<Self>,
15340 ) {
15341 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15342
15343 let buffer = self.buffer.read(cx).snapshot(cx);
15344 let old_selections = self
15345 .selections
15346 .all::<usize>(&self.display_snapshot(cx))
15347 .into_boxed_slice();
15348
15349 fn update_selection(
15350 selection: &Selection<usize>,
15351 buffer_snap: &MultiBufferSnapshot,
15352 ) -> Option<Selection<usize>> {
15353 let cursor = selection.head();
15354 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15355 for symbol in symbols.iter().rev() {
15356 let start = symbol.range.start.to_offset(buffer_snap);
15357 let end = symbol.range.end.to_offset(buffer_snap);
15358 let new_range = start..end;
15359 if start < selection.start || end > selection.end {
15360 return Some(Selection {
15361 id: selection.id,
15362 start: new_range.start,
15363 end: new_range.end,
15364 goal: SelectionGoal::None,
15365 reversed: selection.reversed,
15366 });
15367 }
15368 }
15369 None
15370 }
15371
15372 let mut selected_larger_symbol = false;
15373 let new_selections = old_selections
15374 .iter()
15375 .map(|selection| match update_selection(selection, &buffer) {
15376 Some(new_selection) => {
15377 if new_selection.range() != selection.range() {
15378 selected_larger_symbol = true;
15379 }
15380 new_selection
15381 }
15382 None => selection.clone(),
15383 })
15384 .collect::<Vec<_>>();
15385
15386 if selected_larger_symbol {
15387 self.change_selections(Default::default(), window, cx, |s| {
15388 s.select(new_selections);
15389 });
15390 }
15391 }
15392
15393 pub fn select_larger_syntax_node(
15394 &mut self,
15395 _: &SelectLargerSyntaxNode,
15396 window: &mut Window,
15397 cx: &mut Context<Self>,
15398 ) {
15399 let Some(visible_row_count) = self.visible_row_count() else {
15400 return;
15401 };
15402 let old_selections: Box<[_]> = self
15403 .selections
15404 .all::<usize>(&self.display_snapshot(cx))
15405 .into();
15406 if old_selections.is_empty() {
15407 return;
15408 }
15409
15410 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15411
15412 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15413 let buffer = self.buffer.read(cx).snapshot(cx);
15414
15415 let mut selected_larger_node = false;
15416 let mut new_selections = old_selections
15417 .iter()
15418 .map(|selection| {
15419 let old_range = selection.start..selection.end;
15420
15421 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15422 // manually select word at selection
15423 if ["string_content", "inline"].contains(&node.kind()) {
15424 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15425 // ignore if word is already selected
15426 if !word_range.is_empty() && old_range != word_range {
15427 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15428 // only select word if start and end point belongs to same word
15429 if word_range == last_word_range {
15430 selected_larger_node = true;
15431 return Selection {
15432 id: selection.id,
15433 start: word_range.start,
15434 end: word_range.end,
15435 goal: SelectionGoal::None,
15436 reversed: selection.reversed,
15437 };
15438 }
15439 }
15440 }
15441 }
15442
15443 let mut new_range = old_range.clone();
15444 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15445 new_range = range;
15446 if !node.is_named() {
15447 continue;
15448 }
15449 if !display_map.intersects_fold(new_range.start)
15450 && !display_map.intersects_fold(new_range.end)
15451 {
15452 break;
15453 }
15454 }
15455
15456 selected_larger_node |= new_range != old_range;
15457 Selection {
15458 id: selection.id,
15459 start: new_range.start,
15460 end: new_range.end,
15461 goal: SelectionGoal::None,
15462 reversed: selection.reversed,
15463 }
15464 })
15465 .collect::<Vec<_>>();
15466
15467 if !selected_larger_node {
15468 return; // don't put this call in the history
15469 }
15470
15471 // scroll based on transformation done to the last selection created by the user
15472 let (last_old, last_new) = old_selections
15473 .last()
15474 .zip(new_selections.last().cloned())
15475 .expect("old_selections isn't empty");
15476
15477 // revert selection
15478 let is_selection_reversed = {
15479 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15480 new_selections.last_mut().expect("checked above").reversed =
15481 should_newest_selection_be_reversed;
15482 should_newest_selection_be_reversed
15483 };
15484
15485 if selected_larger_node {
15486 self.select_syntax_node_history.disable_clearing = true;
15487 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15488 s.select(new_selections.clone());
15489 });
15490 self.select_syntax_node_history.disable_clearing = false;
15491 }
15492
15493 let start_row = last_new.start.to_display_point(&display_map).row().0;
15494 let end_row = last_new.end.to_display_point(&display_map).row().0;
15495 let selection_height = end_row - start_row + 1;
15496 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15497
15498 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15499 let scroll_behavior = if fits_on_the_screen {
15500 self.request_autoscroll(Autoscroll::fit(), cx);
15501 SelectSyntaxNodeScrollBehavior::FitSelection
15502 } else if is_selection_reversed {
15503 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15504 SelectSyntaxNodeScrollBehavior::CursorTop
15505 } else {
15506 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15507 SelectSyntaxNodeScrollBehavior::CursorBottom
15508 };
15509
15510 self.select_syntax_node_history.push((
15511 old_selections,
15512 scroll_behavior,
15513 is_selection_reversed,
15514 ));
15515 }
15516
15517 pub fn select_smaller_syntax_node(
15518 &mut self,
15519 _: &SelectSmallerSyntaxNode,
15520 window: &mut Window,
15521 cx: &mut Context<Self>,
15522 ) {
15523 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15524
15525 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15526 self.select_syntax_node_history.pop()
15527 {
15528 if let Some(selection) = selections.last_mut() {
15529 selection.reversed = is_selection_reversed;
15530 }
15531
15532 self.select_syntax_node_history.disable_clearing = true;
15533 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15534 s.select(selections.to_vec());
15535 });
15536 self.select_syntax_node_history.disable_clearing = false;
15537
15538 match scroll_behavior {
15539 SelectSyntaxNodeScrollBehavior::CursorTop => {
15540 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15541 }
15542 SelectSyntaxNodeScrollBehavior::FitSelection => {
15543 self.request_autoscroll(Autoscroll::fit(), cx);
15544 }
15545 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15546 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15547 }
15548 }
15549 }
15550 }
15551
15552 pub fn unwrap_syntax_node(
15553 &mut self,
15554 _: &UnwrapSyntaxNode,
15555 window: &mut Window,
15556 cx: &mut Context<Self>,
15557 ) {
15558 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15559
15560 let buffer = self.buffer.read(cx).snapshot(cx);
15561 let selections = self
15562 .selections
15563 .all::<usize>(&self.display_snapshot(cx))
15564 .into_iter()
15565 // subtracting the offset requires sorting
15566 .sorted_by_key(|i| i.start);
15567
15568 let full_edits = selections
15569 .into_iter()
15570 .filter_map(|selection| {
15571 let child = if selection.is_empty()
15572 && let Some((_, ancestor_range)) =
15573 buffer.syntax_ancestor(selection.start..selection.end)
15574 {
15575 ancestor_range
15576 } else {
15577 selection.range()
15578 };
15579
15580 let mut parent = child.clone();
15581 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15582 parent = ancestor_range;
15583 if parent.start < child.start || parent.end > child.end {
15584 break;
15585 }
15586 }
15587
15588 if parent == child {
15589 return None;
15590 }
15591 let text = buffer.text_for_range(child).collect::<String>();
15592 Some((selection.id, parent, text))
15593 })
15594 .collect::<Vec<_>>();
15595 if full_edits.is_empty() {
15596 return;
15597 }
15598
15599 self.transact(window, cx, |this, window, cx| {
15600 this.buffer.update(cx, |buffer, cx| {
15601 buffer.edit(
15602 full_edits
15603 .iter()
15604 .map(|(_, p, t)| (p.clone(), t.clone()))
15605 .collect::<Vec<_>>(),
15606 None,
15607 cx,
15608 );
15609 });
15610 this.change_selections(Default::default(), window, cx, |s| {
15611 let mut offset = 0;
15612 let mut selections = vec![];
15613 for (id, parent, text) in full_edits {
15614 let start = parent.start - offset;
15615 offset += parent.len() - text.len();
15616 selections.push(Selection {
15617 id,
15618 start,
15619 end: start + text.len(),
15620 reversed: false,
15621 goal: Default::default(),
15622 });
15623 }
15624 s.select(selections);
15625 });
15626 });
15627 }
15628
15629 pub fn select_next_syntax_node(
15630 &mut self,
15631 _: &SelectNextSyntaxNode,
15632 window: &mut Window,
15633 cx: &mut Context<Self>,
15634 ) {
15635 let old_selections: Box<[_]> = self
15636 .selections
15637 .all::<usize>(&self.display_snapshot(cx))
15638 .into();
15639 if old_selections.is_empty() {
15640 return;
15641 }
15642
15643 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15644
15645 let buffer = self.buffer.read(cx).snapshot(cx);
15646 let mut selected_sibling = false;
15647
15648 let new_selections = old_selections
15649 .iter()
15650 .map(|selection| {
15651 let old_range = selection.start..selection.end;
15652
15653 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15654 let new_range = node.byte_range();
15655 selected_sibling = true;
15656 Selection {
15657 id: selection.id,
15658 start: new_range.start,
15659 end: new_range.end,
15660 goal: SelectionGoal::None,
15661 reversed: selection.reversed,
15662 }
15663 } else {
15664 selection.clone()
15665 }
15666 })
15667 .collect::<Vec<_>>();
15668
15669 if selected_sibling {
15670 self.change_selections(
15671 SelectionEffects::scroll(Autoscroll::fit()),
15672 window,
15673 cx,
15674 |s| {
15675 s.select(new_selections);
15676 },
15677 );
15678 }
15679 }
15680
15681 pub fn select_prev_syntax_node(
15682 &mut self,
15683 _: &SelectPreviousSyntaxNode,
15684 window: &mut Window,
15685 cx: &mut Context<Self>,
15686 ) {
15687 let old_selections: Box<[_]> = self
15688 .selections
15689 .all::<usize>(&self.display_snapshot(cx))
15690 .into();
15691 if old_selections.is_empty() {
15692 return;
15693 }
15694
15695 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15696
15697 let buffer = self.buffer.read(cx).snapshot(cx);
15698 let mut selected_sibling = false;
15699
15700 let new_selections = old_selections
15701 .iter()
15702 .map(|selection| {
15703 let old_range = selection.start..selection.end;
15704
15705 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15706 let new_range = node.byte_range();
15707 selected_sibling = true;
15708 Selection {
15709 id: selection.id,
15710 start: new_range.start,
15711 end: new_range.end,
15712 goal: SelectionGoal::None,
15713 reversed: selection.reversed,
15714 }
15715 } else {
15716 selection.clone()
15717 }
15718 })
15719 .collect::<Vec<_>>();
15720
15721 if selected_sibling {
15722 self.change_selections(
15723 SelectionEffects::scroll(Autoscroll::fit()),
15724 window,
15725 cx,
15726 |s| {
15727 s.select(new_selections);
15728 },
15729 );
15730 }
15731 }
15732
15733 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15734 if !EditorSettings::get_global(cx).gutter.runnables {
15735 self.clear_tasks();
15736 return Task::ready(());
15737 }
15738 let project = self.project().map(Entity::downgrade);
15739 let task_sources = self.lsp_task_sources(cx);
15740 let multi_buffer = self.buffer.downgrade();
15741 cx.spawn_in(window, async move |editor, cx| {
15742 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15743 let Some(project) = project.and_then(|p| p.upgrade()) else {
15744 return;
15745 };
15746 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15747 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15748 }) else {
15749 return;
15750 };
15751
15752 let hide_runnables = project
15753 .update(cx, |project, _| project.is_via_collab())
15754 .unwrap_or(true);
15755 if hide_runnables {
15756 return;
15757 }
15758 let new_rows =
15759 cx.background_spawn({
15760 let snapshot = display_snapshot.clone();
15761 async move {
15762 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15763 }
15764 })
15765 .await;
15766 let Ok(lsp_tasks) =
15767 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15768 else {
15769 return;
15770 };
15771 let lsp_tasks = lsp_tasks.await;
15772
15773 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15774 lsp_tasks
15775 .into_iter()
15776 .flat_map(|(kind, tasks)| {
15777 tasks.into_iter().filter_map(move |(location, task)| {
15778 Some((kind.clone(), location?, task))
15779 })
15780 })
15781 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15782 let buffer = location.target.buffer;
15783 let buffer_snapshot = buffer.read(cx).snapshot();
15784 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15785 |(excerpt_id, snapshot, _)| {
15786 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15787 display_snapshot
15788 .buffer_snapshot()
15789 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15790 } else {
15791 None
15792 }
15793 },
15794 );
15795 if let Some(offset) = offset {
15796 let task_buffer_range =
15797 location.target.range.to_point(&buffer_snapshot);
15798 let context_buffer_range =
15799 task_buffer_range.to_offset(&buffer_snapshot);
15800 let context_range = BufferOffset(context_buffer_range.start)
15801 ..BufferOffset(context_buffer_range.end);
15802
15803 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15804 .or_insert_with(|| RunnableTasks {
15805 templates: Vec::new(),
15806 offset,
15807 column: task_buffer_range.start.column,
15808 extra_variables: HashMap::default(),
15809 context_range,
15810 })
15811 .templates
15812 .push((kind, task.original_task().clone()));
15813 }
15814
15815 acc
15816 })
15817 }) else {
15818 return;
15819 };
15820
15821 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15822 buffer.language_settings(cx).tasks.prefer_lsp
15823 }) else {
15824 return;
15825 };
15826
15827 let rows = Self::runnable_rows(
15828 project,
15829 display_snapshot,
15830 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15831 new_rows,
15832 cx.clone(),
15833 )
15834 .await;
15835 editor
15836 .update(cx, |editor, _| {
15837 editor.clear_tasks();
15838 for (key, mut value) in rows {
15839 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15840 value.templates.extend(lsp_tasks.templates);
15841 }
15842
15843 editor.insert_tasks(key, value);
15844 }
15845 for (key, value) in lsp_tasks_by_rows {
15846 editor.insert_tasks(key, value);
15847 }
15848 })
15849 .ok();
15850 })
15851 }
15852 fn fetch_runnable_ranges(
15853 snapshot: &DisplaySnapshot,
15854 range: Range<Anchor>,
15855 ) -> Vec<language::RunnableRange> {
15856 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15857 }
15858
15859 fn runnable_rows(
15860 project: Entity<Project>,
15861 snapshot: DisplaySnapshot,
15862 prefer_lsp: bool,
15863 runnable_ranges: Vec<RunnableRange>,
15864 cx: AsyncWindowContext,
15865 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15866 cx.spawn(async move |cx| {
15867 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15868 for mut runnable in runnable_ranges {
15869 let Some(tasks) = cx
15870 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15871 .ok()
15872 else {
15873 continue;
15874 };
15875 let mut tasks = tasks.await;
15876
15877 if prefer_lsp {
15878 tasks.retain(|(task_kind, _)| {
15879 !matches!(task_kind, TaskSourceKind::Language { .. })
15880 });
15881 }
15882 if tasks.is_empty() {
15883 continue;
15884 }
15885
15886 let point = runnable
15887 .run_range
15888 .start
15889 .to_point(&snapshot.buffer_snapshot());
15890 let Some(row) = snapshot
15891 .buffer_snapshot()
15892 .buffer_line_for_row(MultiBufferRow(point.row))
15893 .map(|(_, range)| range.start.row)
15894 else {
15895 continue;
15896 };
15897
15898 let context_range =
15899 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15900 runnable_rows.push((
15901 (runnable.buffer_id, row),
15902 RunnableTasks {
15903 templates: tasks,
15904 offset: snapshot
15905 .buffer_snapshot()
15906 .anchor_before(runnable.run_range.start),
15907 context_range,
15908 column: point.column,
15909 extra_variables: runnable.extra_captures,
15910 },
15911 ));
15912 }
15913 runnable_rows
15914 })
15915 }
15916
15917 fn templates_with_tags(
15918 project: &Entity<Project>,
15919 runnable: &mut Runnable,
15920 cx: &mut App,
15921 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15922 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15923 let (worktree_id, file) = project
15924 .buffer_for_id(runnable.buffer, cx)
15925 .and_then(|buffer| buffer.read(cx).file())
15926 .map(|file| (file.worktree_id(cx), file.clone()))
15927 .unzip();
15928
15929 (
15930 project.task_store().read(cx).task_inventory().cloned(),
15931 worktree_id,
15932 file,
15933 )
15934 });
15935
15936 let tags = mem::take(&mut runnable.tags);
15937 let language = runnable.language.clone();
15938 cx.spawn(async move |cx| {
15939 let mut templates_with_tags = Vec::new();
15940 if let Some(inventory) = inventory {
15941 for RunnableTag(tag) in tags {
15942 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15943 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15944 }) else {
15945 return templates_with_tags;
15946 };
15947 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15948 move |(_, template)| {
15949 template.tags.iter().any(|source_tag| source_tag == &tag)
15950 },
15951 ));
15952 }
15953 }
15954 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15955
15956 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15957 // Strongest source wins; if we have worktree tag binding, prefer that to
15958 // global and language bindings;
15959 // if we have a global binding, prefer that to language binding.
15960 let first_mismatch = templates_with_tags
15961 .iter()
15962 .position(|(tag_source, _)| tag_source != leading_tag_source);
15963 if let Some(index) = first_mismatch {
15964 templates_with_tags.truncate(index);
15965 }
15966 }
15967
15968 templates_with_tags
15969 })
15970 }
15971
15972 pub fn move_to_enclosing_bracket(
15973 &mut self,
15974 _: &MoveToEnclosingBracket,
15975 window: &mut Window,
15976 cx: &mut Context<Self>,
15977 ) {
15978 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15979 self.change_selections(Default::default(), window, cx, |s| {
15980 s.move_offsets_with(|snapshot, selection| {
15981 let Some(enclosing_bracket_ranges) =
15982 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15983 else {
15984 return;
15985 };
15986
15987 let mut best_length = usize::MAX;
15988 let mut best_inside = false;
15989 let mut best_in_bracket_range = false;
15990 let mut best_destination = None;
15991 for (open, close) in enclosing_bracket_ranges {
15992 let close = close.to_inclusive();
15993 let length = close.end() - open.start;
15994 let inside = selection.start >= open.end && selection.end <= *close.start();
15995 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15996 || close.contains(&selection.head());
15997
15998 // If best is next to a bracket and current isn't, skip
15999 if !in_bracket_range && best_in_bracket_range {
16000 continue;
16001 }
16002
16003 // Prefer smaller lengths unless best is inside and current isn't
16004 if length > best_length && (best_inside || !inside) {
16005 continue;
16006 }
16007
16008 best_length = length;
16009 best_inside = inside;
16010 best_in_bracket_range = in_bracket_range;
16011 best_destination = Some(
16012 if close.contains(&selection.start) && close.contains(&selection.end) {
16013 if inside { open.end } else { open.start }
16014 } else if inside {
16015 *close.start()
16016 } else {
16017 *close.end()
16018 },
16019 );
16020 }
16021
16022 if let Some(destination) = best_destination {
16023 selection.collapse_to(destination, SelectionGoal::None);
16024 }
16025 })
16026 });
16027 }
16028
16029 pub fn undo_selection(
16030 &mut self,
16031 _: &UndoSelection,
16032 window: &mut Window,
16033 cx: &mut Context<Self>,
16034 ) {
16035 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16036 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16037 self.selection_history.mode = SelectionHistoryMode::Undoing;
16038 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16039 this.end_selection(window, cx);
16040 this.change_selections(
16041 SelectionEffects::scroll(Autoscroll::newest()),
16042 window,
16043 cx,
16044 |s| s.select_anchors(entry.selections.to_vec()),
16045 );
16046 });
16047 self.selection_history.mode = SelectionHistoryMode::Normal;
16048
16049 self.select_next_state = entry.select_next_state;
16050 self.select_prev_state = entry.select_prev_state;
16051 self.add_selections_state = entry.add_selections_state;
16052 }
16053 }
16054
16055 pub fn redo_selection(
16056 &mut self,
16057 _: &RedoSelection,
16058 window: &mut Window,
16059 cx: &mut Context<Self>,
16060 ) {
16061 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16062 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16063 self.selection_history.mode = SelectionHistoryMode::Redoing;
16064 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16065 this.end_selection(window, cx);
16066 this.change_selections(
16067 SelectionEffects::scroll(Autoscroll::newest()),
16068 window,
16069 cx,
16070 |s| s.select_anchors(entry.selections.to_vec()),
16071 );
16072 });
16073 self.selection_history.mode = SelectionHistoryMode::Normal;
16074
16075 self.select_next_state = entry.select_next_state;
16076 self.select_prev_state = entry.select_prev_state;
16077 self.add_selections_state = entry.add_selections_state;
16078 }
16079 }
16080
16081 pub fn expand_excerpts(
16082 &mut self,
16083 action: &ExpandExcerpts,
16084 _: &mut Window,
16085 cx: &mut Context<Self>,
16086 ) {
16087 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16088 }
16089
16090 pub fn expand_excerpts_down(
16091 &mut self,
16092 action: &ExpandExcerptsDown,
16093 _: &mut Window,
16094 cx: &mut Context<Self>,
16095 ) {
16096 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16097 }
16098
16099 pub fn expand_excerpts_up(
16100 &mut self,
16101 action: &ExpandExcerptsUp,
16102 _: &mut Window,
16103 cx: &mut Context<Self>,
16104 ) {
16105 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16106 }
16107
16108 pub fn expand_excerpts_for_direction(
16109 &mut self,
16110 lines: u32,
16111 direction: ExpandExcerptDirection,
16112
16113 cx: &mut Context<Self>,
16114 ) {
16115 let selections = self.selections.disjoint_anchors_arc();
16116
16117 let lines = if lines == 0 {
16118 EditorSettings::get_global(cx).expand_excerpt_lines
16119 } else {
16120 lines
16121 };
16122
16123 self.buffer.update(cx, |buffer, cx| {
16124 let snapshot = buffer.snapshot(cx);
16125 let mut excerpt_ids = selections
16126 .iter()
16127 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16128 .collect::<Vec<_>>();
16129 excerpt_ids.sort();
16130 excerpt_ids.dedup();
16131 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16132 })
16133 }
16134
16135 pub fn expand_excerpt(
16136 &mut self,
16137 excerpt: ExcerptId,
16138 direction: ExpandExcerptDirection,
16139 window: &mut Window,
16140 cx: &mut Context<Self>,
16141 ) {
16142 let current_scroll_position = self.scroll_position(cx);
16143 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16144 let mut scroll = None;
16145
16146 if direction == ExpandExcerptDirection::Down {
16147 let multi_buffer = self.buffer.read(cx);
16148 let snapshot = multi_buffer.snapshot(cx);
16149 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16150 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16151 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16152 {
16153 let buffer_snapshot = buffer.read(cx).snapshot();
16154 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16155 let last_row = buffer_snapshot.max_point().row;
16156 let lines_below = last_row.saturating_sub(excerpt_end_row);
16157 if lines_below >= lines_to_expand {
16158 scroll = Some(
16159 current_scroll_position
16160 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16161 );
16162 }
16163 }
16164 }
16165 if direction == ExpandExcerptDirection::Up
16166 && self
16167 .buffer
16168 .read(cx)
16169 .snapshot(cx)
16170 .excerpt_before(excerpt)
16171 .is_none()
16172 {
16173 scroll = Some(current_scroll_position);
16174 }
16175
16176 self.buffer.update(cx, |buffer, cx| {
16177 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16178 });
16179
16180 if let Some(new_scroll_position) = scroll {
16181 self.set_scroll_position(new_scroll_position, window, cx);
16182 }
16183 }
16184
16185 pub fn go_to_singleton_buffer_point(
16186 &mut self,
16187 point: Point,
16188 window: &mut Window,
16189 cx: &mut Context<Self>,
16190 ) {
16191 self.go_to_singleton_buffer_range(point..point, window, cx);
16192 }
16193
16194 pub fn go_to_singleton_buffer_range(
16195 &mut self,
16196 range: Range<Point>,
16197 window: &mut Window,
16198 cx: &mut Context<Self>,
16199 ) {
16200 let multibuffer = self.buffer().read(cx);
16201 let Some(buffer) = multibuffer.as_singleton() else {
16202 return;
16203 };
16204 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16205 return;
16206 };
16207 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16208 return;
16209 };
16210 self.change_selections(
16211 SelectionEffects::default().nav_history(true),
16212 window,
16213 cx,
16214 |s| s.select_anchor_ranges([start..end]),
16215 );
16216 }
16217
16218 pub fn go_to_diagnostic(
16219 &mut self,
16220 action: &GoToDiagnostic,
16221 window: &mut Window,
16222 cx: &mut Context<Self>,
16223 ) {
16224 if !self.diagnostics_enabled() {
16225 return;
16226 }
16227 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16228 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16229 }
16230
16231 pub fn go_to_prev_diagnostic(
16232 &mut self,
16233 action: &GoToPreviousDiagnostic,
16234 window: &mut Window,
16235 cx: &mut Context<Self>,
16236 ) {
16237 if !self.diagnostics_enabled() {
16238 return;
16239 }
16240 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16241 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16242 }
16243
16244 pub fn go_to_diagnostic_impl(
16245 &mut self,
16246 direction: Direction,
16247 severity: GoToDiagnosticSeverityFilter,
16248 window: &mut Window,
16249 cx: &mut Context<Self>,
16250 ) {
16251 let buffer = self.buffer.read(cx).snapshot(cx);
16252 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16253
16254 let mut active_group_id = None;
16255 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16256 && active_group.active_range.start.to_offset(&buffer) == selection.start
16257 {
16258 active_group_id = Some(active_group.group_id);
16259 }
16260
16261 fn filtered<'a>(
16262 severity: GoToDiagnosticSeverityFilter,
16263 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16264 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16265 diagnostics
16266 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16267 .filter(|entry| entry.range.start != entry.range.end)
16268 .filter(|entry| !entry.diagnostic.is_unnecessary)
16269 }
16270
16271 let before = filtered(
16272 severity,
16273 buffer
16274 .diagnostics_in_range(0..selection.start)
16275 .filter(|entry| entry.range.start <= selection.start),
16276 );
16277 let after = filtered(
16278 severity,
16279 buffer
16280 .diagnostics_in_range(selection.start..buffer.len())
16281 .filter(|entry| entry.range.start >= selection.start),
16282 );
16283
16284 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16285 if direction == Direction::Prev {
16286 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16287 {
16288 for diagnostic in prev_diagnostics.into_iter().rev() {
16289 if diagnostic.range.start != selection.start
16290 || active_group_id
16291 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16292 {
16293 found = Some(diagnostic);
16294 break 'outer;
16295 }
16296 }
16297 }
16298 } else {
16299 for diagnostic in after.chain(before) {
16300 if diagnostic.range.start != selection.start
16301 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16302 {
16303 found = Some(diagnostic);
16304 break;
16305 }
16306 }
16307 }
16308 let Some(next_diagnostic) = found else {
16309 return;
16310 };
16311
16312 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16313 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16314 return;
16315 };
16316 let snapshot = self.snapshot(window, cx);
16317 if snapshot.intersects_fold(next_diagnostic.range.start) {
16318 self.unfold_ranges(
16319 std::slice::from_ref(&next_diagnostic.range),
16320 true,
16321 false,
16322 cx,
16323 );
16324 }
16325 self.change_selections(Default::default(), window, cx, |s| {
16326 s.select_ranges(vec![
16327 next_diagnostic.range.start..next_diagnostic.range.start,
16328 ])
16329 });
16330 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16331 self.refresh_edit_prediction(false, true, window, cx);
16332 }
16333
16334 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16335 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16336 let snapshot = self.snapshot(window, cx);
16337 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16338 self.go_to_hunk_before_or_after_position(
16339 &snapshot,
16340 selection.head(),
16341 Direction::Next,
16342 window,
16343 cx,
16344 );
16345 }
16346
16347 pub fn go_to_hunk_before_or_after_position(
16348 &mut self,
16349 snapshot: &EditorSnapshot,
16350 position: Point,
16351 direction: Direction,
16352 window: &mut Window,
16353 cx: &mut Context<Editor>,
16354 ) {
16355 let row = if direction == Direction::Next {
16356 self.hunk_after_position(snapshot, position)
16357 .map(|hunk| hunk.row_range.start)
16358 } else {
16359 self.hunk_before_position(snapshot, position)
16360 };
16361
16362 if let Some(row) = row {
16363 let destination = Point::new(row.0, 0);
16364 let autoscroll = Autoscroll::center();
16365
16366 self.unfold_ranges(&[destination..destination], false, false, cx);
16367 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16368 s.select_ranges([destination..destination]);
16369 });
16370 }
16371 }
16372
16373 fn hunk_after_position(
16374 &mut self,
16375 snapshot: &EditorSnapshot,
16376 position: Point,
16377 ) -> Option<MultiBufferDiffHunk> {
16378 snapshot
16379 .buffer_snapshot()
16380 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16381 .find(|hunk| hunk.row_range.start.0 > position.row)
16382 .or_else(|| {
16383 snapshot
16384 .buffer_snapshot()
16385 .diff_hunks_in_range(Point::zero()..position)
16386 .find(|hunk| hunk.row_range.end.0 < position.row)
16387 })
16388 }
16389
16390 fn go_to_prev_hunk(
16391 &mut self,
16392 _: &GoToPreviousHunk,
16393 window: &mut Window,
16394 cx: &mut Context<Self>,
16395 ) {
16396 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16397 let snapshot = self.snapshot(window, cx);
16398 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16399 self.go_to_hunk_before_or_after_position(
16400 &snapshot,
16401 selection.head(),
16402 Direction::Prev,
16403 window,
16404 cx,
16405 );
16406 }
16407
16408 fn hunk_before_position(
16409 &mut self,
16410 snapshot: &EditorSnapshot,
16411 position: Point,
16412 ) -> Option<MultiBufferRow> {
16413 snapshot
16414 .buffer_snapshot()
16415 .diff_hunk_before(position)
16416 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16417 }
16418
16419 fn go_to_next_change(
16420 &mut self,
16421 _: &GoToNextChange,
16422 window: &mut Window,
16423 cx: &mut Context<Self>,
16424 ) {
16425 if let Some(selections) = self
16426 .change_list
16427 .next_change(1, Direction::Next)
16428 .map(|s| s.to_vec())
16429 {
16430 self.change_selections(Default::default(), window, cx, |s| {
16431 let map = s.display_snapshot();
16432 s.select_display_ranges(selections.iter().map(|a| {
16433 let point = a.to_display_point(&map);
16434 point..point
16435 }))
16436 })
16437 }
16438 }
16439
16440 fn go_to_previous_change(
16441 &mut self,
16442 _: &GoToPreviousChange,
16443 window: &mut Window,
16444 cx: &mut Context<Self>,
16445 ) {
16446 if let Some(selections) = self
16447 .change_list
16448 .next_change(1, Direction::Prev)
16449 .map(|s| s.to_vec())
16450 {
16451 self.change_selections(Default::default(), window, cx, |s| {
16452 let map = s.display_snapshot();
16453 s.select_display_ranges(selections.iter().map(|a| {
16454 let point = a.to_display_point(&map);
16455 point..point
16456 }))
16457 })
16458 }
16459 }
16460
16461 pub fn go_to_next_document_highlight(
16462 &mut self,
16463 _: &GoToNextDocumentHighlight,
16464 window: &mut Window,
16465 cx: &mut Context<Self>,
16466 ) {
16467 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16468 }
16469
16470 pub fn go_to_prev_document_highlight(
16471 &mut self,
16472 _: &GoToPreviousDocumentHighlight,
16473 window: &mut Window,
16474 cx: &mut Context<Self>,
16475 ) {
16476 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16477 }
16478
16479 pub fn go_to_document_highlight_before_or_after_position(
16480 &mut self,
16481 direction: Direction,
16482 window: &mut Window,
16483 cx: &mut Context<Editor>,
16484 ) {
16485 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16486 let snapshot = self.snapshot(window, cx);
16487 let buffer = &snapshot.buffer_snapshot();
16488 let position = self
16489 .selections
16490 .newest::<Point>(&snapshot.display_snapshot)
16491 .head();
16492 let anchor_position = buffer.anchor_after(position);
16493
16494 // Get all document highlights (both read and write)
16495 let mut all_highlights = Vec::new();
16496
16497 if let Some((_, read_highlights)) = self
16498 .background_highlights
16499 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16500 {
16501 all_highlights.extend(read_highlights.iter());
16502 }
16503
16504 if let Some((_, write_highlights)) = self
16505 .background_highlights
16506 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16507 {
16508 all_highlights.extend(write_highlights.iter());
16509 }
16510
16511 if all_highlights.is_empty() {
16512 return;
16513 }
16514
16515 // Sort highlights by position
16516 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16517
16518 let target_highlight = match direction {
16519 Direction::Next => {
16520 // Find the first highlight after the current position
16521 all_highlights
16522 .iter()
16523 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16524 }
16525 Direction::Prev => {
16526 // Find the last highlight before the current position
16527 all_highlights
16528 .iter()
16529 .rev()
16530 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16531 }
16532 };
16533
16534 if let Some(highlight) = target_highlight {
16535 let destination = highlight.start.to_point(buffer);
16536 let autoscroll = Autoscroll::center();
16537
16538 self.unfold_ranges(&[destination..destination], false, false, cx);
16539 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16540 s.select_ranges([destination..destination]);
16541 });
16542 }
16543 }
16544
16545 fn go_to_line<T: 'static>(
16546 &mut self,
16547 position: Anchor,
16548 highlight_color: Option<Hsla>,
16549 window: &mut Window,
16550 cx: &mut Context<Self>,
16551 ) {
16552 let snapshot = self.snapshot(window, cx).display_snapshot;
16553 let position = position.to_point(&snapshot.buffer_snapshot());
16554 let start = snapshot
16555 .buffer_snapshot()
16556 .clip_point(Point::new(position.row, 0), Bias::Left);
16557 let end = start + Point::new(1, 0);
16558 let start = snapshot.buffer_snapshot().anchor_before(start);
16559 let end = snapshot.buffer_snapshot().anchor_before(end);
16560
16561 self.highlight_rows::<T>(
16562 start..end,
16563 highlight_color
16564 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16565 Default::default(),
16566 cx,
16567 );
16568
16569 if self.buffer.read(cx).is_singleton() {
16570 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16571 }
16572 }
16573
16574 pub fn go_to_definition(
16575 &mut self,
16576 _: &GoToDefinition,
16577 window: &mut Window,
16578 cx: &mut Context<Self>,
16579 ) -> Task<Result<Navigated>> {
16580 let definition =
16581 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16582 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16583 cx.spawn_in(window, async move |editor, cx| {
16584 if definition.await? == Navigated::Yes {
16585 return Ok(Navigated::Yes);
16586 }
16587 match fallback_strategy {
16588 GoToDefinitionFallback::None => Ok(Navigated::No),
16589 GoToDefinitionFallback::FindAllReferences => {
16590 match editor.update_in(cx, |editor, window, cx| {
16591 editor.find_all_references(&FindAllReferences, window, cx)
16592 })? {
16593 Some(references) => references.await,
16594 None => Ok(Navigated::No),
16595 }
16596 }
16597 }
16598 })
16599 }
16600
16601 pub fn go_to_declaration(
16602 &mut self,
16603 _: &GoToDeclaration,
16604 window: &mut Window,
16605 cx: &mut Context<Self>,
16606 ) -> Task<Result<Navigated>> {
16607 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16608 }
16609
16610 pub fn go_to_declaration_split(
16611 &mut self,
16612 _: &GoToDeclaration,
16613 window: &mut Window,
16614 cx: &mut Context<Self>,
16615 ) -> Task<Result<Navigated>> {
16616 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16617 }
16618
16619 pub fn go_to_implementation(
16620 &mut self,
16621 _: &GoToImplementation,
16622 window: &mut Window,
16623 cx: &mut Context<Self>,
16624 ) -> Task<Result<Navigated>> {
16625 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16626 }
16627
16628 pub fn go_to_implementation_split(
16629 &mut self,
16630 _: &GoToImplementationSplit,
16631 window: &mut Window,
16632 cx: &mut Context<Self>,
16633 ) -> Task<Result<Navigated>> {
16634 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16635 }
16636
16637 pub fn go_to_type_definition(
16638 &mut self,
16639 _: &GoToTypeDefinition,
16640 window: &mut Window,
16641 cx: &mut Context<Self>,
16642 ) -> Task<Result<Navigated>> {
16643 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16644 }
16645
16646 pub fn go_to_definition_split(
16647 &mut self,
16648 _: &GoToDefinitionSplit,
16649 window: &mut Window,
16650 cx: &mut Context<Self>,
16651 ) -> Task<Result<Navigated>> {
16652 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16653 }
16654
16655 pub fn go_to_type_definition_split(
16656 &mut self,
16657 _: &GoToTypeDefinitionSplit,
16658 window: &mut Window,
16659 cx: &mut Context<Self>,
16660 ) -> Task<Result<Navigated>> {
16661 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16662 }
16663
16664 fn go_to_definition_of_kind(
16665 &mut self,
16666 kind: GotoDefinitionKind,
16667 split: bool,
16668 window: &mut Window,
16669 cx: &mut Context<Self>,
16670 ) -> Task<Result<Navigated>> {
16671 let Some(provider) = self.semantics_provider.clone() else {
16672 return Task::ready(Ok(Navigated::No));
16673 };
16674 let head = self
16675 .selections
16676 .newest::<usize>(&self.display_snapshot(cx))
16677 .head();
16678 let buffer = self.buffer.read(cx);
16679 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16680 return Task::ready(Ok(Navigated::No));
16681 };
16682 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16683 return Task::ready(Ok(Navigated::No));
16684 };
16685
16686 cx.spawn_in(window, async move |editor, cx| {
16687 let Some(definitions) = definitions.await? else {
16688 return Ok(Navigated::No);
16689 };
16690 let navigated = editor
16691 .update_in(cx, |editor, window, cx| {
16692 editor.navigate_to_hover_links(
16693 Some(kind),
16694 definitions
16695 .into_iter()
16696 .filter(|location| {
16697 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16698 })
16699 .map(HoverLink::Text)
16700 .collect::<Vec<_>>(),
16701 split,
16702 window,
16703 cx,
16704 )
16705 })?
16706 .await?;
16707 anyhow::Ok(navigated)
16708 })
16709 }
16710
16711 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16712 let selection = self.selections.newest_anchor();
16713 let head = selection.head();
16714 let tail = selection.tail();
16715
16716 let Some((buffer, start_position)) =
16717 self.buffer.read(cx).text_anchor_for_position(head, cx)
16718 else {
16719 return;
16720 };
16721
16722 let end_position = if head != tail {
16723 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16724 return;
16725 };
16726 Some(pos)
16727 } else {
16728 None
16729 };
16730
16731 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16732 let url = if let Some(end_pos) = end_position {
16733 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16734 } else {
16735 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16736 };
16737
16738 if let Some(url) = url {
16739 cx.update(|window, cx| {
16740 if parse_zed_link(&url, cx).is_some() {
16741 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16742 } else {
16743 cx.open_url(&url);
16744 }
16745 })?;
16746 }
16747
16748 anyhow::Ok(())
16749 });
16750
16751 url_finder.detach();
16752 }
16753
16754 pub fn open_selected_filename(
16755 &mut self,
16756 _: &OpenSelectedFilename,
16757 window: &mut Window,
16758 cx: &mut Context<Self>,
16759 ) {
16760 let Some(workspace) = self.workspace() else {
16761 return;
16762 };
16763
16764 let position = self.selections.newest_anchor().head();
16765
16766 let Some((buffer, buffer_position)) =
16767 self.buffer.read(cx).text_anchor_for_position(position, cx)
16768 else {
16769 return;
16770 };
16771
16772 let project = self.project.clone();
16773
16774 cx.spawn_in(window, async move |_, cx| {
16775 let result = find_file(&buffer, project, buffer_position, cx).await;
16776
16777 if let Some((_, path)) = result {
16778 workspace
16779 .update_in(cx, |workspace, window, cx| {
16780 workspace.open_resolved_path(path, window, cx)
16781 })?
16782 .await?;
16783 }
16784 anyhow::Ok(())
16785 })
16786 .detach();
16787 }
16788
16789 pub(crate) fn navigate_to_hover_links(
16790 &mut self,
16791 kind: Option<GotoDefinitionKind>,
16792 definitions: Vec<HoverLink>,
16793 split: bool,
16794 window: &mut Window,
16795 cx: &mut Context<Editor>,
16796 ) -> Task<Result<Navigated>> {
16797 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16798 let mut first_url_or_file = None;
16799 let definitions: Vec<_> = definitions
16800 .into_iter()
16801 .filter_map(|def| match def {
16802 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16803 HoverLink::InlayHint(lsp_location, server_id) => {
16804 let computation =
16805 self.compute_target_location(lsp_location, server_id, window, cx);
16806 Some(cx.background_spawn(computation))
16807 }
16808 HoverLink::Url(url) => {
16809 first_url_or_file = Some(Either::Left(url));
16810 None
16811 }
16812 HoverLink::File(path) => {
16813 first_url_or_file = Some(Either::Right(path));
16814 None
16815 }
16816 })
16817 .collect();
16818
16819 let workspace = self.workspace();
16820
16821 cx.spawn_in(window, async move |editor, cx| {
16822 let locations: Vec<Location> = future::join_all(definitions)
16823 .await
16824 .into_iter()
16825 .filter_map(|location| location.transpose())
16826 .collect::<Result<_>>()
16827 .context("location tasks")?;
16828 let mut locations = cx.update(|_, cx| {
16829 locations
16830 .into_iter()
16831 .map(|location| {
16832 let buffer = location.buffer.read(cx);
16833 (location.buffer, location.range.to_point(buffer))
16834 })
16835 .into_group_map()
16836 })?;
16837 let mut num_locations = 0;
16838 for ranges in locations.values_mut() {
16839 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16840 ranges.dedup();
16841 num_locations += ranges.len();
16842 }
16843
16844 if num_locations > 1 {
16845 let Some(workspace) = workspace else {
16846 return Ok(Navigated::No);
16847 };
16848
16849 let tab_kind = match kind {
16850 Some(GotoDefinitionKind::Implementation) => "Implementations",
16851 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16852 Some(GotoDefinitionKind::Declaration) => "Declarations",
16853 Some(GotoDefinitionKind::Type) => "Types",
16854 };
16855 let title = editor
16856 .update_in(cx, |_, _, cx| {
16857 let target = locations
16858 .iter()
16859 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16860 .map(|(buffer, location)| {
16861 buffer
16862 .read(cx)
16863 .text_for_range(location.clone())
16864 .collect::<String>()
16865 })
16866 .filter(|text| !text.contains('\n'))
16867 .unique()
16868 .take(3)
16869 .join(", ");
16870 if target.is_empty() {
16871 tab_kind.to_owned()
16872 } else {
16873 format!("{tab_kind} for {target}")
16874 }
16875 })
16876 .context("buffer title")?;
16877
16878 let opened = workspace
16879 .update_in(cx, |workspace, window, cx| {
16880 Self::open_locations_in_multibuffer(
16881 workspace,
16882 locations,
16883 title,
16884 split,
16885 MultibufferSelectionMode::First,
16886 window,
16887 cx,
16888 )
16889 })
16890 .is_ok();
16891
16892 anyhow::Ok(Navigated::from_bool(opened))
16893 } else if num_locations == 0 {
16894 // If there is one url or file, open it directly
16895 match first_url_or_file {
16896 Some(Either::Left(url)) => {
16897 cx.update(|_, cx| cx.open_url(&url))?;
16898 Ok(Navigated::Yes)
16899 }
16900 Some(Either::Right(path)) => {
16901 let Some(workspace) = workspace else {
16902 return Ok(Navigated::No);
16903 };
16904
16905 workspace
16906 .update_in(cx, |workspace, window, cx| {
16907 workspace.open_resolved_path(path, window, cx)
16908 })?
16909 .await?;
16910 Ok(Navigated::Yes)
16911 }
16912 None => Ok(Navigated::No),
16913 }
16914 } else {
16915 let Some(workspace) = workspace else {
16916 return Ok(Navigated::No);
16917 };
16918
16919 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16920 let target_range = target_ranges.first().unwrap().clone();
16921
16922 editor.update_in(cx, |editor, window, cx| {
16923 let range = target_range.to_point(target_buffer.read(cx));
16924 let range = editor.range_for_match(&range, false);
16925 let range = collapse_multiline_range(range);
16926
16927 if !split
16928 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16929 {
16930 editor.go_to_singleton_buffer_range(range, window, cx);
16931 } else {
16932 let pane = workspace.read(cx).active_pane().clone();
16933 window.defer(cx, move |window, cx| {
16934 let target_editor: Entity<Self> =
16935 workspace.update(cx, |workspace, cx| {
16936 let pane = if split {
16937 workspace.adjacent_pane(window, cx)
16938 } else {
16939 workspace.active_pane().clone()
16940 };
16941
16942 workspace.open_project_item(
16943 pane,
16944 target_buffer.clone(),
16945 true,
16946 true,
16947 window,
16948 cx,
16949 )
16950 });
16951 target_editor.update(cx, |target_editor, cx| {
16952 // When selecting a definition in a different buffer, disable the nav history
16953 // to avoid creating a history entry at the previous cursor location.
16954 pane.update(cx, |pane, _| pane.disable_history());
16955 target_editor.go_to_singleton_buffer_range(range, window, cx);
16956 pane.update(cx, |pane, _| pane.enable_history());
16957 });
16958 });
16959 }
16960 Navigated::Yes
16961 })
16962 }
16963 })
16964 }
16965
16966 fn compute_target_location(
16967 &self,
16968 lsp_location: lsp::Location,
16969 server_id: LanguageServerId,
16970 window: &mut Window,
16971 cx: &mut Context<Self>,
16972 ) -> Task<anyhow::Result<Option<Location>>> {
16973 let Some(project) = self.project.clone() else {
16974 return Task::ready(Ok(None));
16975 };
16976
16977 cx.spawn_in(window, async move |editor, cx| {
16978 let location_task = editor.update(cx, |_, cx| {
16979 project.update(cx, |project, cx| {
16980 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16981 })
16982 })?;
16983 let location = Some({
16984 let target_buffer_handle = location_task.await.context("open local buffer")?;
16985 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16986 let target_start = target_buffer
16987 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16988 let target_end = target_buffer
16989 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16990 target_buffer.anchor_after(target_start)
16991 ..target_buffer.anchor_before(target_end)
16992 })?;
16993 Location {
16994 buffer: target_buffer_handle,
16995 range,
16996 }
16997 });
16998 Ok(location)
16999 })
17000 }
17001
17002 fn go_to_next_reference(
17003 &mut self,
17004 _: &GoToNextReference,
17005 window: &mut Window,
17006 cx: &mut Context<Self>,
17007 ) {
17008 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17009 if let Some(task) = task {
17010 task.detach();
17011 };
17012 }
17013
17014 fn go_to_prev_reference(
17015 &mut self,
17016 _: &GoToPreviousReference,
17017 window: &mut Window,
17018 cx: &mut Context<Self>,
17019 ) {
17020 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17021 if let Some(task) = task {
17022 task.detach();
17023 };
17024 }
17025
17026 pub fn go_to_reference_before_or_after_position(
17027 &mut self,
17028 direction: Direction,
17029 count: usize,
17030 window: &mut Window,
17031 cx: &mut Context<Self>,
17032 ) -> Option<Task<Result<()>>> {
17033 let selection = self.selections.newest_anchor();
17034 let head = selection.head();
17035
17036 let multi_buffer = self.buffer.read(cx);
17037
17038 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17039 let workspace = self.workspace()?;
17040 let project = workspace.read(cx).project().clone();
17041 let references =
17042 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17043 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17044 let Some(locations) = references.await? else {
17045 return Ok(());
17046 };
17047
17048 if locations.is_empty() {
17049 // totally normal - the cursor may be on something which is not
17050 // a symbol (e.g. a keyword)
17051 log::info!("no references found under cursor");
17052 return Ok(());
17053 }
17054
17055 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17056
17057 let multi_buffer_snapshot =
17058 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
17059
17060 let (locations, current_location_index) =
17061 multi_buffer.update(cx, |multi_buffer, cx| {
17062 let mut locations = locations
17063 .into_iter()
17064 .filter_map(|loc| {
17065 let start = multi_buffer.buffer_anchor_to_anchor(
17066 &loc.buffer,
17067 loc.range.start,
17068 cx,
17069 )?;
17070 let end = multi_buffer.buffer_anchor_to_anchor(
17071 &loc.buffer,
17072 loc.range.end,
17073 cx,
17074 )?;
17075 Some(start..end)
17076 })
17077 .collect::<Vec<_>>();
17078
17079 // There is an O(n) implementation, but given this list will be
17080 // small (usually <100 items), the extra O(log(n)) factor isn't
17081 // worth the (surprisingly large amount of) extra complexity.
17082 locations
17083 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17084
17085 let head_offset = head.to_offset(&multi_buffer_snapshot);
17086
17087 let current_location_index = locations.iter().position(|loc| {
17088 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17089 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17090 });
17091
17092 (locations, current_location_index)
17093 })?;
17094
17095 let Some(current_location_index) = current_location_index else {
17096 // This indicates something has gone wrong, because we already
17097 // handle the "no references" case above
17098 log::error!(
17099 "failed to find current reference under cursor. Total references: {}",
17100 locations.len()
17101 );
17102 return Ok(());
17103 };
17104
17105 let destination_location_index = match direction {
17106 Direction::Next => (current_location_index + count) % locations.len(),
17107 Direction::Prev => {
17108 (current_location_index + locations.len() - count % locations.len())
17109 % locations.len()
17110 }
17111 };
17112
17113 // TODO(cameron): is this needed?
17114 // the thinking is to avoid "jumping to the current location" (avoid
17115 // polluting "jumplist" in vim terms)
17116 if current_location_index == destination_location_index {
17117 return Ok(());
17118 }
17119
17120 let Range { start, end } = locations[destination_location_index];
17121
17122 editor.update_in(cx, |editor, window, cx| {
17123 let effects = SelectionEffects::default();
17124
17125 editor.unfold_ranges(&[start..end], false, false, cx);
17126 editor.change_selections(effects, window, cx, |s| {
17127 s.select_ranges([start..start]);
17128 });
17129 })?;
17130
17131 Ok(())
17132 }))
17133 }
17134
17135 pub fn find_all_references(
17136 &mut self,
17137 _: &FindAllReferences,
17138 window: &mut Window,
17139 cx: &mut Context<Self>,
17140 ) -> Option<Task<Result<Navigated>>> {
17141 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
17142 let multi_buffer = self.buffer.read(cx);
17143 let head = selection.head();
17144
17145 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17146 let head_anchor = multi_buffer_snapshot.anchor_at(
17147 head,
17148 if head < selection.tail() {
17149 Bias::Right
17150 } else {
17151 Bias::Left
17152 },
17153 );
17154
17155 match self
17156 .find_all_references_task_sources
17157 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17158 {
17159 Ok(_) => {
17160 log::info!(
17161 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17162 );
17163 return None;
17164 }
17165 Err(i) => {
17166 self.find_all_references_task_sources.insert(i, head_anchor);
17167 }
17168 }
17169
17170 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17171 let workspace = self.workspace()?;
17172 let project = workspace.read(cx).project().clone();
17173 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17174 Some(cx.spawn_in(window, async move |editor, cx| {
17175 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17176 if let Ok(i) = editor
17177 .find_all_references_task_sources
17178 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17179 {
17180 editor.find_all_references_task_sources.remove(i);
17181 }
17182 });
17183
17184 let Some(locations) = references.await? else {
17185 return anyhow::Ok(Navigated::No);
17186 };
17187 let mut locations = cx.update(|_, cx| {
17188 locations
17189 .into_iter()
17190 .map(|location| {
17191 let buffer = location.buffer.read(cx);
17192 (location.buffer, location.range.to_point(buffer))
17193 })
17194 .into_group_map()
17195 })?;
17196 if locations.is_empty() {
17197 return anyhow::Ok(Navigated::No);
17198 }
17199 for ranges in locations.values_mut() {
17200 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17201 ranges.dedup();
17202 }
17203
17204 workspace.update_in(cx, |workspace, window, cx| {
17205 let target = locations
17206 .iter()
17207 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17208 .map(|(buffer, location)| {
17209 buffer
17210 .read(cx)
17211 .text_for_range(location.clone())
17212 .collect::<String>()
17213 })
17214 .filter(|text| !text.contains('\n'))
17215 .unique()
17216 .take(3)
17217 .join(", ");
17218 let title = if target.is_empty() {
17219 "References".to_owned()
17220 } else {
17221 format!("References to {target}")
17222 };
17223 Self::open_locations_in_multibuffer(
17224 workspace,
17225 locations,
17226 title,
17227 false,
17228 MultibufferSelectionMode::First,
17229 window,
17230 cx,
17231 );
17232 Navigated::Yes
17233 })
17234 }))
17235 }
17236
17237 /// Opens a multibuffer with the given project locations in it
17238 pub fn open_locations_in_multibuffer(
17239 workspace: &mut Workspace,
17240 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17241 title: String,
17242 split: bool,
17243 multibuffer_selection_mode: MultibufferSelectionMode,
17244 window: &mut Window,
17245 cx: &mut Context<Workspace>,
17246 ) {
17247 if locations.is_empty() {
17248 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17249 return;
17250 }
17251
17252 let capability = workspace.project().read(cx).capability();
17253 let mut ranges = <Vec<Range<Anchor>>>::new();
17254
17255 // a key to find existing multibuffer editors with the same set of locations
17256 // to prevent us from opening more and more multibuffer tabs for searches and the like
17257 let mut key = (title.clone(), vec![]);
17258 let excerpt_buffer = cx.new(|cx| {
17259 let key = &mut key.1;
17260 let mut multibuffer = MultiBuffer::new(capability);
17261 for (buffer, mut ranges_for_buffer) in locations {
17262 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17263 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17264 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17265 PathKey::for_buffer(&buffer, cx),
17266 buffer.clone(),
17267 ranges_for_buffer,
17268 multibuffer_context_lines(cx),
17269 cx,
17270 );
17271 ranges.extend(new_ranges)
17272 }
17273
17274 multibuffer.with_title(title)
17275 });
17276 let existing = workspace.active_pane().update(cx, |pane, cx| {
17277 pane.items()
17278 .filter_map(|item| item.downcast::<Editor>())
17279 .find(|editor| {
17280 editor
17281 .read(cx)
17282 .lookup_key
17283 .as_ref()
17284 .and_then(|it| {
17285 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17286 })
17287 .is_some_and(|it| *it == key)
17288 })
17289 });
17290 let editor = existing.unwrap_or_else(|| {
17291 cx.new(|cx| {
17292 let mut editor = Editor::for_multibuffer(
17293 excerpt_buffer,
17294 Some(workspace.project().clone()),
17295 window,
17296 cx,
17297 );
17298 editor.lookup_key = Some(Box::new(key));
17299 editor
17300 })
17301 });
17302 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17303 MultibufferSelectionMode::First => {
17304 if let Some(first_range) = ranges.first() {
17305 editor.change_selections(
17306 SelectionEffects::no_scroll(),
17307 window,
17308 cx,
17309 |selections| {
17310 selections.clear_disjoint();
17311 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17312 },
17313 );
17314 }
17315 editor.highlight_background::<Self>(
17316 &ranges,
17317 |theme| theme.colors().editor_highlighted_line_background,
17318 cx,
17319 );
17320 }
17321 MultibufferSelectionMode::All => {
17322 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17323 selections.clear_disjoint();
17324 selections.select_anchor_ranges(ranges);
17325 });
17326 }
17327 });
17328
17329 let item = Box::new(editor);
17330 let item_id = item.item_id();
17331
17332 if split {
17333 let pane = workspace.adjacent_pane(window, cx);
17334 workspace.add_item(pane, item, None, true, true, window, cx);
17335 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17336 let (preview_item_id, preview_item_idx) =
17337 workspace.active_pane().read_with(cx, |pane, _| {
17338 (pane.preview_item_id(), pane.preview_item_idx())
17339 });
17340
17341 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17342
17343 if let Some(preview_item_id) = preview_item_id {
17344 workspace.active_pane().update(cx, |pane, cx| {
17345 pane.remove_item(preview_item_id, false, false, window, cx);
17346 });
17347 }
17348 } else {
17349 workspace.add_item_to_active_pane(item, None, true, window, cx);
17350 }
17351 workspace.active_pane().update(cx, |pane, cx| {
17352 pane.set_preview_item_id(Some(item_id), cx);
17353 });
17354 }
17355
17356 pub fn rename(
17357 &mut self,
17358 _: &Rename,
17359 window: &mut Window,
17360 cx: &mut Context<Self>,
17361 ) -> Option<Task<Result<()>>> {
17362 use language::ToOffset as _;
17363
17364 let provider = self.semantics_provider.clone()?;
17365 let selection = self.selections.newest_anchor().clone();
17366 let (cursor_buffer, cursor_buffer_position) = self
17367 .buffer
17368 .read(cx)
17369 .text_anchor_for_position(selection.head(), cx)?;
17370 let (tail_buffer, cursor_buffer_position_end) = self
17371 .buffer
17372 .read(cx)
17373 .text_anchor_for_position(selection.tail(), cx)?;
17374 if tail_buffer != cursor_buffer {
17375 return None;
17376 }
17377
17378 let snapshot = cursor_buffer.read(cx).snapshot();
17379 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17380 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17381 let prepare_rename = provider
17382 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17383 .unwrap_or_else(|| Task::ready(Ok(None)));
17384 drop(snapshot);
17385
17386 Some(cx.spawn_in(window, async move |this, cx| {
17387 let rename_range = if let Some(range) = prepare_rename.await? {
17388 Some(range)
17389 } else {
17390 this.update(cx, |this, cx| {
17391 let buffer = this.buffer.read(cx).snapshot(cx);
17392 let mut buffer_highlights = this
17393 .document_highlights_for_position(selection.head(), &buffer)
17394 .filter(|highlight| {
17395 highlight.start.excerpt_id == selection.head().excerpt_id
17396 && highlight.end.excerpt_id == selection.head().excerpt_id
17397 });
17398 buffer_highlights
17399 .next()
17400 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17401 })?
17402 };
17403 if let Some(rename_range) = rename_range {
17404 this.update_in(cx, |this, window, cx| {
17405 let snapshot = cursor_buffer.read(cx).snapshot();
17406 let rename_buffer_range = rename_range.to_offset(&snapshot);
17407 let cursor_offset_in_rename_range =
17408 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17409 let cursor_offset_in_rename_range_end =
17410 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17411
17412 this.take_rename(false, window, cx);
17413 let buffer = this.buffer.read(cx).read(cx);
17414 let cursor_offset = selection.head().to_offset(&buffer);
17415 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17416 let rename_end = rename_start + rename_buffer_range.len();
17417 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17418 let mut old_highlight_id = None;
17419 let old_name: Arc<str> = buffer
17420 .chunks(rename_start..rename_end, true)
17421 .map(|chunk| {
17422 if old_highlight_id.is_none() {
17423 old_highlight_id = chunk.syntax_highlight_id;
17424 }
17425 chunk.text
17426 })
17427 .collect::<String>()
17428 .into();
17429
17430 drop(buffer);
17431
17432 // Position the selection in the rename editor so that it matches the current selection.
17433 this.show_local_selections = false;
17434 let rename_editor = cx.new(|cx| {
17435 let mut editor = Editor::single_line(window, cx);
17436 editor.buffer.update(cx, |buffer, cx| {
17437 buffer.edit([(0..0, old_name.clone())], None, cx)
17438 });
17439 let rename_selection_range = match cursor_offset_in_rename_range
17440 .cmp(&cursor_offset_in_rename_range_end)
17441 {
17442 Ordering::Equal => {
17443 editor.select_all(&SelectAll, window, cx);
17444 return editor;
17445 }
17446 Ordering::Less => {
17447 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17448 }
17449 Ordering::Greater => {
17450 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17451 }
17452 };
17453 if rename_selection_range.end > old_name.len() {
17454 editor.select_all(&SelectAll, window, cx);
17455 } else {
17456 editor.change_selections(Default::default(), window, cx, |s| {
17457 s.select_ranges([rename_selection_range]);
17458 });
17459 }
17460 editor
17461 });
17462 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17463 if e == &EditorEvent::Focused {
17464 cx.emit(EditorEvent::FocusedIn)
17465 }
17466 })
17467 .detach();
17468
17469 let write_highlights =
17470 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17471 let read_highlights =
17472 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17473 let ranges = write_highlights
17474 .iter()
17475 .flat_map(|(_, ranges)| ranges.iter())
17476 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17477 .cloned()
17478 .collect();
17479
17480 this.highlight_text::<Rename>(
17481 ranges,
17482 HighlightStyle {
17483 fade_out: Some(0.6),
17484 ..Default::default()
17485 },
17486 cx,
17487 );
17488 let rename_focus_handle = rename_editor.focus_handle(cx);
17489 window.focus(&rename_focus_handle);
17490 let block_id = this.insert_blocks(
17491 [BlockProperties {
17492 style: BlockStyle::Flex,
17493 placement: BlockPlacement::Below(range.start),
17494 height: Some(1),
17495 render: Arc::new({
17496 let rename_editor = rename_editor.clone();
17497 move |cx: &mut BlockContext| {
17498 let mut text_style = cx.editor_style.text.clone();
17499 if let Some(highlight_style) = old_highlight_id
17500 .and_then(|h| h.style(&cx.editor_style.syntax))
17501 {
17502 text_style = text_style.highlight(highlight_style);
17503 }
17504 div()
17505 .block_mouse_except_scroll()
17506 .pl(cx.anchor_x)
17507 .child(EditorElement::new(
17508 &rename_editor,
17509 EditorStyle {
17510 background: cx.theme().system().transparent,
17511 local_player: cx.editor_style.local_player,
17512 text: text_style,
17513 scrollbar_width: cx.editor_style.scrollbar_width,
17514 syntax: cx.editor_style.syntax.clone(),
17515 status: cx.editor_style.status.clone(),
17516 inlay_hints_style: HighlightStyle {
17517 font_weight: Some(FontWeight::BOLD),
17518 ..make_inlay_hints_style(cx.app)
17519 },
17520 edit_prediction_styles: make_suggestion_styles(
17521 cx.app,
17522 ),
17523 ..EditorStyle::default()
17524 },
17525 ))
17526 .into_any_element()
17527 }
17528 }),
17529 priority: 0,
17530 }],
17531 Some(Autoscroll::fit()),
17532 cx,
17533 )[0];
17534 this.pending_rename = Some(RenameState {
17535 range,
17536 old_name,
17537 editor: rename_editor,
17538 block_id,
17539 });
17540 })?;
17541 }
17542
17543 Ok(())
17544 }))
17545 }
17546
17547 pub fn confirm_rename(
17548 &mut self,
17549 _: &ConfirmRename,
17550 window: &mut Window,
17551 cx: &mut Context<Self>,
17552 ) -> Option<Task<Result<()>>> {
17553 let rename = self.take_rename(false, window, cx)?;
17554 let workspace = self.workspace()?.downgrade();
17555 let (buffer, start) = self
17556 .buffer
17557 .read(cx)
17558 .text_anchor_for_position(rename.range.start, cx)?;
17559 let (end_buffer, _) = self
17560 .buffer
17561 .read(cx)
17562 .text_anchor_for_position(rename.range.end, cx)?;
17563 if buffer != end_buffer {
17564 return None;
17565 }
17566
17567 let old_name = rename.old_name;
17568 let new_name = rename.editor.read(cx).text(cx);
17569
17570 let rename = self.semantics_provider.as_ref()?.perform_rename(
17571 &buffer,
17572 start,
17573 new_name.clone(),
17574 cx,
17575 )?;
17576
17577 Some(cx.spawn_in(window, async move |editor, cx| {
17578 let project_transaction = rename.await?;
17579 Self::open_project_transaction(
17580 &editor,
17581 workspace,
17582 project_transaction,
17583 format!("Rename: {} → {}", old_name, new_name),
17584 cx,
17585 )
17586 .await?;
17587
17588 editor.update(cx, |editor, cx| {
17589 editor.refresh_document_highlights(cx);
17590 })?;
17591 Ok(())
17592 }))
17593 }
17594
17595 fn take_rename(
17596 &mut self,
17597 moving_cursor: bool,
17598 window: &mut Window,
17599 cx: &mut Context<Self>,
17600 ) -> Option<RenameState> {
17601 let rename = self.pending_rename.take()?;
17602 if rename.editor.focus_handle(cx).is_focused(window) {
17603 window.focus(&self.focus_handle);
17604 }
17605
17606 self.remove_blocks(
17607 [rename.block_id].into_iter().collect(),
17608 Some(Autoscroll::fit()),
17609 cx,
17610 );
17611 self.clear_highlights::<Rename>(cx);
17612 self.show_local_selections = true;
17613
17614 if moving_cursor {
17615 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17616 editor
17617 .selections
17618 .newest::<usize>(&editor.display_snapshot(cx))
17619 .head()
17620 });
17621
17622 // Update the selection to match the position of the selection inside
17623 // the rename editor.
17624 let snapshot = self.buffer.read(cx).read(cx);
17625 let rename_range = rename.range.to_offset(&snapshot);
17626 let cursor_in_editor = snapshot
17627 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17628 .min(rename_range.end);
17629 drop(snapshot);
17630
17631 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17632 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17633 });
17634 } else {
17635 self.refresh_document_highlights(cx);
17636 }
17637
17638 Some(rename)
17639 }
17640
17641 pub fn pending_rename(&self) -> Option<&RenameState> {
17642 self.pending_rename.as_ref()
17643 }
17644
17645 fn format(
17646 &mut self,
17647 _: &Format,
17648 window: &mut Window,
17649 cx: &mut Context<Self>,
17650 ) -> Option<Task<Result<()>>> {
17651 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17652
17653 let project = match &self.project {
17654 Some(project) => project.clone(),
17655 None => return None,
17656 };
17657
17658 Some(self.perform_format(
17659 project,
17660 FormatTrigger::Manual,
17661 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17662 window,
17663 cx,
17664 ))
17665 }
17666
17667 fn format_selections(
17668 &mut self,
17669 _: &FormatSelections,
17670 window: &mut Window,
17671 cx: &mut Context<Self>,
17672 ) -> Option<Task<Result<()>>> {
17673 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17674
17675 let project = match &self.project {
17676 Some(project) => project.clone(),
17677 None => return None,
17678 };
17679
17680 let ranges = self
17681 .selections
17682 .all_adjusted(&self.display_snapshot(cx))
17683 .into_iter()
17684 .map(|selection| selection.range())
17685 .collect_vec();
17686
17687 Some(self.perform_format(
17688 project,
17689 FormatTrigger::Manual,
17690 FormatTarget::Ranges(ranges),
17691 window,
17692 cx,
17693 ))
17694 }
17695
17696 fn perform_format(
17697 &mut self,
17698 project: Entity<Project>,
17699 trigger: FormatTrigger,
17700 target: FormatTarget,
17701 window: &mut Window,
17702 cx: &mut Context<Self>,
17703 ) -> Task<Result<()>> {
17704 let buffer = self.buffer.clone();
17705 let (buffers, target) = match target {
17706 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17707 FormatTarget::Ranges(selection_ranges) => {
17708 let multi_buffer = buffer.read(cx);
17709 let snapshot = multi_buffer.read(cx);
17710 let mut buffers = HashSet::default();
17711 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17712 BTreeMap::new();
17713 for selection_range in selection_ranges {
17714 for (buffer, buffer_range, _) in
17715 snapshot.range_to_buffer_ranges(selection_range)
17716 {
17717 let buffer_id = buffer.remote_id();
17718 let start = buffer.anchor_before(buffer_range.start);
17719 let end = buffer.anchor_after(buffer_range.end);
17720 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17721 buffer_id_to_ranges
17722 .entry(buffer_id)
17723 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17724 .or_insert_with(|| vec![start..end]);
17725 }
17726 }
17727 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17728 }
17729 };
17730
17731 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17732 let selections_prev = transaction_id_prev
17733 .and_then(|transaction_id_prev| {
17734 // default to selections as they were after the last edit, if we have them,
17735 // instead of how they are now.
17736 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17737 // will take you back to where you made the last edit, instead of staying where you scrolled
17738 self.selection_history
17739 .transaction(transaction_id_prev)
17740 .map(|t| t.0.clone())
17741 })
17742 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17743
17744 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17745 let format = project.update(cx, |project, cx| {
17746 project.format(buffers, target, true, trigger, cx)
17747 });
17748
17749 cx.spawn_in(window, async move |editor, cx| {
17750 let transaction = futures::select_biased! {
17751 transaction = format.log_err().fuse() => transaction,
17752 () = timeout => {
17753 log::warn!("timed out waiting for formatting");
17754 None
17755 }
17756 };
17757
17758 buffer
17759 .update(cx, |buffer, cx| {
17760 if let Some(transaction) = transaction
17761 && !buffer.is_singleton()
17762 {
17763 buffer.push_transaction(&transaction.0, cx);
17764 }
17765 cx.notify();
17766 })
17767 .ok();
17768
17769 if let Some(transaction_id_now) =
17770 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17771 {
17772 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17773 if has_new_transaction {
17774 _ = editor.update(cx, |editor, _| {
17775 editor
17776 .selection_history
17777 .insert_transaction(transaction_id_now, selections_prev);
17778 });
17779 }
17780 }
17781
17782 Ok(())
17783 })
17784 }
17785
17786 fn organize_imports(
17787 &mut self,
17788 _: &OrganizeImports,
17789 window: &mut Window,
17790 cx: &mut Context<Self>,
17791 ) -> Option<Task<Result<()>>> {
17792 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17793 let project = match &self.project {
17794 Some(project) => project.clone(),
17795 None => return None,
17796 };
17797 Some(self.perform_code_action_kind(
17798 project,
17799 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17800 window,
17801 cx,
17802 ))
17803 }
17804
17805 fn perform_code_action_kind(
17806 &mut self,
17807 project: Entity<Project>,
17808 kind: CodeActionKind,
17809 window: &mut Window,
17810 cx: &mut Context<Self>,
17811 ) -> Task<Result<()>> {
17812 let buffer = self.buffer.clone();
17813 let buffers = buffer.read(cx).all_buffers();
17814 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17815 let apply_action = project.update(cx, |project, cx| {
17816 project.apply_code_action_kind(buffers, kind, true, cx)
17817 });
17818 cx.spawn_in(window, async move |_, cx| {
17819 let transaction = futures::select_biased! {
17820 () = timeout => {
17821 log::warn!("timed out waiting for executing code action");
17822 None
17823 }
17824 transaction = apply_action.log_err().fuse() => transaction,
17825 };
17826 buffer
17827 .update(cx, |buffer, cx| {
17828 // check if we need this
17829 if let Some(transaction) = transaction
17830 && !buffer.is_singleton()
17831 {
17832 buffer.push_transaction(&transaction.0, cx);
17833 }
17834 cx.notify();
17835 })
17836 .ok();
17837 Ok(())
17838 })
17839 }
17840
17841 pub fn restart_language_server(
17842 &mut self,
17843 _: &RestartLanguageServer,
17844 _: &mut Window,
17845 cx: &mut Context<Self>,
17846 ) {
17847 if let Some(project) = self.project.clone() {
17848 self.buffer.update(cx, |multi_buffer, cx| {
17849 project.update(cx, |project, cx| {
17850 project.restart_language_servers_for_buffers(
17851 multi_buffer.all_buffers().into_iter().collect(),
17852 HashSet::default(),
17853 cx,
17854 );
17855 });
17856 })
17857 }
17858 }
17859
17860 pub fn stop_language_server(
17861 &mut self,
17862 _: &StopLanguageServer,
17863 _: &mut Window,
17864 cx: &mut Context<Self>,
17865 ) {
17866 if let Some(project) = self.project.clone() {
17867 self.buffer.update(cx, |multi_buffer, cx| {
17868 project.update(cx, |project, cx| {
17869 project.stop_language_servers_for_buffers(
17870 multi_buffer.all_buffers().into_iter().collect(),
17871 HashSet::default(),
17872 cx,
17873 );
17874 });
17875 });
17876 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17877 }
17878 }
17879
17880 fn cancel_language_server_work(
17881 workspace: &mut Workspace,
17882 _: &actions::CancelLanguageServerWork,
17883 _: &mut Window,
17884 cx: &mut Context<Workspace>,
17885 ) {
17886 let project = workspace.project();
17887 let buffers = workspace
17888 .active_item(cx)
17889 .and_then(|item| item.act_as::<Editor>(cx))
17890 .map_or(HashSet::default(), |editor| {
17891 editor.read(cx).buffer.read(cx).all_buffers()
17892 });
17893 project.update(cx, |project, cx| {
17894 project.cancel_language_server_work_for_buffers(buffers, cx);
17895 });
17896 }
17897
17898 fn show_character_palette(
17899 &mut self,
17900 _: &ShowCharacterPalette,
17901 window: &mut Window,
17902 _: &mut Context<Self>,
17903 ) {
17904 window.show_character_palette();
17905 }
17906
17907 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17908 if !self.diagnostics_enabled() {
17909 return;
17910 }
17911
17912 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17913 let buffer = self.buffer.read(cx).snapshot(cx);
17914 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17915 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17916 let is_valid = buffer
17917 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17918 .any(|entry| {
17919 entry.diagnostic.is_primary
17920 && !entry.range.is_empty()
17921 && entry.range.start == primary_range_start
17922 && entry.diagnostic.message == active_diagnostics.active_message
17923 });
17924
17925 if !is_valid {
17926 self.dismiss_diagnostics(cx);
17927 }
17928 }
17929 }
17930
17931 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17932 match &self.active_diagnostics {
17933 ActiveDiagnostic::Group(group) => Some(group),
17934 _ => None,
17935 }
17936 }
17937
17938 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17939 if !self.diagnostics_enabled() {
17940 return;
17941 }
17942 self.dismiss_diagnostics(cx);
17943 self.active_diagnostics = ActiveDiagnostic::All;
17944 }
17945
17946 fn activate_diagnostics(
17947 &mut self,
17948 buffer_id: BufferId,
17949 diagnostic: DiagnosticEntryRef<'_, usize>,
17950 window: &mut Window,
17951 cx: &mut Context<Self>,
17952 ) {
17953 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17954 return;
17955 }
17956 self.dismiss_diagnostics(cx);
17957 let snapshot = self.snapshot(window, cx);
17958 let buffer = self.buffer.read(cx).snapshot(cx);
17959 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17960 return;
17961 };
17962
17963 let diagnostic_group = buffer
17964 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17965 .collect::<Vec<_>>();
17966
17967 let language_registry = self
17968 .project()
17969 .map(|project| project.read(cx).languages().clone());
17970
17971 let blocks = renderer.render_group(
17972 diagnostic_group,
17973 buffer_id,
17974 snapshot,
17975 cx.weak_entity(),
17976 language_registry,
17977 cx,
17978 );
17979
17980 let blocks = self.display_map.update(cx, |display_map, cx| {
17981 display_map.insert_blocks(blocks, cx).into_iter().collect()
17982 });
17983 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17984 active_range: buffer.anchor_before(diagnostic.range.start)
17985 ..buffer.anchor_after(diagnostic.range.end),
17986 active_message: diagnostic.diagnostic.message.clone(),
17987 group_id: diagnostic.diagnostic.group_id,
17988 blocks,
17989 });
17990 cx.notify();
17991 }
17992
17993 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17994 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17995 return;
17996 };
17997
17998 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17999 if let ActiveDiagnostic::Group(group) = prev {
18000 self.display_map.update(cx, |display_map, cx| {
18001 display_map.remove_blocks(group.blocks, cx);
18002 });
18003 cx.notify();
18004 }
18005 }
18006
18007 /// Disable inline diagnostics rendering for this editor.
18008 pub fn disable_inline_diagnostics(&mut self) {
18009 self.inline_diagnostics_enabled = false;
18010 self.inline_diagnostics_update = Task::ready(());
18011 self.inline_diagnostics.clear();
18012 }
18013
18014 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18015 self.diagnostics_enabled = false;
18016 self.dismiss_diagnostics(cx);
18017 self.inline_diagnostics_update = Task::ready(());
18018 self.inline_diagnostics.clear();
18019 }
18020
18021 pub fn disable_word_completions(&mut self) {
18022 self.word_completions_enabled = false;
18023 }
18024
18025 pub fn diagnostics_enabled(&self) -> bool {
18026 self.diagnostics_enabled && self.mode.is_full()
18027 }
18028
18029 pub fn inline_diagnostics_enabled(&self) -> bool {
18030 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18031 }
18032
18033 pub fn show_inline_diagnostics(&self) -> bool {
18034 self.show_inline_diagnostics
18035 }
18036
18037 pub fn toggle_inline_diagnostics(
18038 &mut self,
18039 _: &ToggleInlineDiagnostics,
18040 window: &mut Window,
18041 cx: &mut Context<Editor>,
18042 ) {
18043 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18044 self.refresh_inline_diagnostics(false, window, cx);
18045 }
18046
18047 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18048 self.diagnostics_max_severity = severity;
18049 self.display_map.update(cx, |display_map, _| {
18050 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18051 });
18052 }
18053
18054 pub fn toggle_diagnostics(
18055 &mut self,
18056 _: &ToggleDiagnostics,
18057 window: &mut Window,
18058 cx: &mut Context<Editor>,
18059 ) {
18060 if !self.diagnostics_enabled() {
18061 return;
18062 }
18063
18064 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18065 EditorSettings::get_global(cx)
18066 .diagnostics_max_severity
18067 .filter(|severity| severity != &DiagnosticSeverity::Off)
18068 .unwrap_or(DiagnosticSeverity::Hint)
18069 } else {
18070 DiagnosticSeverity::Off
18071 };
18072 self.set_max_diagnostics_severity(new_severity, cx);
18073 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18074 self.active_diagnostics = ActiveDiagnostic::None;
18075 self.inline_diagnostics_update = Task::ready(());
18076 self.inline_diagnostics.clear();
18077 } else {
18078 self.refresh_inline_diagnostics(false, window, cx);
18079 }
18080
18081 cx.notify();
18082 }
18083
18084 pub fn toggle_minimap(
18085 &mut self,
18086 _: &ToggleMinimap,
18087 window: &mut Window,
18088 cx: &mut Context<Editor>,
18089 ) {
18090 if self.supports_minimap(cx) {
18091 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18092 }
18093 }
18094
18095 fn refresh_inline_diagnostics(
18096 &mut self,
18097 debounce: bool,
18098 window: &mut Window,
18099 cx: &mut Context<Self>,
18100 ) {
18101 let max_severity = ProjectSettings::get_global(cx)
18102 .diagnostics
18103 .inline
18104 .max_severity
18105 .unwrap_or(self.diagnostics_max_severity);
18106
18107 if !self.inline_diagnostics_enabled()
18108 || !self.diagnostics_enabled()
18109 || !self.show_inline_diagnostics
18110 || max_severity == DiagnosticSeverity::Off
18111 {
18112 self.inline_diagnostics_update = Task::ready(());
18113 self.inline_diagnostics.clear();
18114 return;
18115 }
18116
18117 let debounce_ms = ProjectSettings::get_global(cx)
18118 .diagnostics
18119 .inline
18120 .update_debounce_ms;
18121 let debounce = if debounce && debounce_ms > 0 {
18122 Some(Duration::from_millis(debounce_ms))
18123 } else {
18124 None
18125 };
18126 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18127 if let Some(debounce) = debounce {
18128 cx.background_executor().timer(debounce).await;
18129 }
18130 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18131 editor
18132 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18133 .ok()
18134 }) else {
18135 return;
18136 };
18137
18138 let new_inline_diagnostics = cx
18139 .background_spawn(async move {
18140 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18141 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
18142 let message = diagnostic_entry
18143 .diagnostic
18144 .message
18145 .split_once('\n')
18146 .map(|(line, _)| line)
18147 .map(SharedString::new)
18148 .unwrap_or_else(|| {
18149 SharedString::new(&*diagnostic_entry.diagnostic.message)
18150 });
18151 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18152 let (Ok(i) | Err(i)) = inline_diagnostics
18153 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18154 inline_diagnostics.insert(
18155 i,
18156 (
18157 start_anchor,
18158 InlineDiagnostic {
18159 message,
18160 group_id: diagnostic_entry.diagnostic.group_id,
18161 start: diagnostic_entry.range.start.to_point(&snapshot),
18162 is_primary: diagnostic_entry.diagnostic.is_primary,
18163 severity: diagnostic_entry.diagnostic.severity,
18164 },
18165 ),
18166 );
18167 }
18168 inline_diagnostics
18169 })
18170 .await;
18171
18172 editor
18173 .update(cx, |editor, cx| {
18174 editor.inline_diagnostics = new_inline_diagnostics;
18175 cx.notify();
18176 })
18177 .ok();
18178 });
18179 }
18180
18181 fn pull_diagnostics(
18182 &mut self,
18183 buffer_id: Option<BufferId>,
18184 window: &Window,
18185 cx: &mut Context<Self>,
18186 ) -> Option<()> {
18187 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18188 return None;
18189 }
18190 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18191 .diagnostics
18192 .lsp_pull_diagnostics;
18193 if !pull_diagnostics_settings.enabled {
18194 return None;
18195 }
18196 let project = self.project()?.downgrade();
18197 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18198 let mut buffers = self.buffer.read(cx).all_buffers();
18199 buffers.retain(|buffer| {
18200 let buffer_id_to_retain = buffer.read(cx).remote_id();
18201 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18202 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18203 });
18204 if buffers.is_empty() {
18205 self.pull_diagnostics_task = Task::ready(());
18206 return None;
18207 }
18208
18209 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18210 cx.background_executor().timer(debounce).await;
18211
18212 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18213 buffers
18214 .into_iter()
18215 .filter_map(|buffer| {
18216 project
18217 .update(cx, |project, cx| {
18218 project.lsp_store().update(cx, |lsp_store, cx| {
18219 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18220 })
18221 })
18222 .ok()
18223 })
18224 .collect::<FuturesUnordered<_>>()
18225 }) else {
18226 return;
18227 };
18228
18229 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18230 match pull_task {
18231 Ok(()) => {
18232 if editor
18233 .update_in(cx, |editor, window, cx| {
18234 editor.update_diagnostics_state(window, cx);
18235 })
18236 .is_err()
18237 {
18238 return;
18239 }
18240 }
18241 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18242 }
18243 }
18244 });
18245
18246 Some(())
18247 }
18248
18249 pub fn set_selections_from_remote(
18250 &mut self,
18251 selections: Vec<Selection<Anchor>>,
18252 pending_selection: Option<Selection<Anchor>>,
18253 window: &mut Window,
18254 cx: &mut Context<Self>,
18255 ) {
18256 let old_cursor_position = self.selections.newest_anchor().head();
18257 self.selections
18258 .change_with(&self.display_snapshot(cx), |s| {
18259 s.select_anchors(selections);
18260 if let Some(pending_selection) = pending_selection {
18261 s.set_pending(pending_selection, SelectMode::Character);
18262 } else {
18263 s.clear_pending();
18264 }
18265 });
18266 self.selections_did_change(
18267 false,
18268 &old_cursor_position,
18269 SelectionEffects::default(),
18270 window,
18271 cx,
18272 );
18273 }
18274
18275 pub fn transact(
18276 &mut self,
18277 window: &mut Window,
18278 cx: &mut Context<Self>,
18279 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18280 ) -> Option<TransactionId> {
18281 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18282 this.start_transaction_at(Instant::now(), window, cx);
18283 update(this, window, cx);
18284 this.end_transaction_at(Instant::now(), cx)
18285 })
18286 }
18287
18288 pub fn start_transaction_at(
18289 &mut self,
18290 now: Instant,
18291 window: &mut Window,
18292 cx: &mut Context<Self>,
18293 ) -> Option<TransactionId> {
18294 self.end_selection(window, cx);
18295 if let Some(tx_id) = self
18296 .buffer
18297 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18298 {
18299 self.selection_history
18300 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18301 cx.emit(EditorEvent::TransactionBegun {
18302 transaction_id: tx_id,
18303 });
18304 Some(tx_id)
18305 } else {
18306 None
18307 }
18308 }
18309
18310 pub fn end_transaction_at(
18311 &mut self,
18312 now: Instant,
18313 cx: &mut Context<Self>,
18314 ) -> Option<TransactionId> {
18315 if let Some(transaction_id) = self
18316 .buffer
18317 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18318 {
18319 if let Some((_, end_selections)) =
18320 self.selection_history.transaction_mut(transaction_id)
18321 {
18322 *end_selections = Some(self.selections.disjoint_anchors_arc());
18323 } else {
18324 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18325 }
18326
18327 cx.emit(EditorEvent::Edited { transaction_id });
18328 Some(transaction_id)
18329 } else {
18330 None
18331 }
18332 }
18333
18334 pub fn modify_transaction_selection_history(
18335 &mut self,
18336 transaction_id: TransactionId,
18337 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18338 ) -> bool {
18339 self.selection_history
18340 .transaction_mut(transaction_id)
18341 .map(modify)
18342 .is_some()
18343 }
18344
18345 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18346 if self.selection_mark_mode {
18347 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18348 s.move_with(|_, sel| {
18349 sel.collapse_to(sel.head(), SelectionGoal::None);
18350 });
18351 })
18352 }
18353 self.selection_mark_mode = true;
18354 cx.notify();
18355 }
18356
18357 pub fn swap_selection_ends(
18358 &mut self,
18359 _: &actions::SwapSelectionEnds,
18360 window: &mut Window,
18361 cx: &mut Context<Self>,
18362 ) {
18363 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18364 s.move_with(|_, sel| {
18365 if sel.start != sel.end {
18366 sel.reversed = !sel.reversed
18367 }
18368 });
18369 });
18370 self.request_autoscroll(Autoscroll::newest(), cx);
18371 cx.notify();
18372 }
18373
18374 pub fn toggle_focus(
18375 workspace: &mut Workspace,
18376 _: &actions::ToggleFocus,
18377 window: &mut Window,
18378 cx: &mut Context<Workspace>,
18379 ) {
18380 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18381 return;
18382 };
18383 workspace.activate_item(&item, true, true, window, cx);
18384 }
18385
18386 pub fn toggle_fold(
18387 &mut self,
18388 _: &actions::ToggleFold,
18389 window: &mut Window,
18390 cx: &mut Context<Self>,
18391 ) {
18392 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18393 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18394 let selection = self.selections.newest::<Point>(&display_map);
18395
18396 let range = if selection.is_empty() {
18397 let point = selection.head().to_display_point(&display_map);
18398 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18399 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18400 .to_point(&display_map);
18401 start..end
18402 } else {
18403 selection.range()
18404 };
18405 if display_map.folds_in_range(range).next().is_some() {
18406 self.unfold_lines(&Default::default(), window, cx)
18407 } else {
18408 self.fold(&Default::default(), window, cx)
18409 }
18410 } else {
18411 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18412 let buffer_ids: HashSet<_> = self
18413 .selections
18414 .disjoint_anchor_ranges()
18415 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18416 .collect();
18417
18418 let should_unfold = buffer_ids
18419 .iter()
18420 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18421
18422 for buffer_id in buffer_ids {
18423 if should_unfold {
18424 self.unfold_buffer(buffer_id, cx);
18425 } else {
18426 self.fold_buffer(buffer_id, cx);
18427 }
18428 }
18429 }
18430 }
18431
18432 pub fn toggle_fold_recursive(
18433 &mut self,
18434 _: &actions::ToggleFoldRecursive,
18435 window: &mut Window,
18436 cx: &mut Context<Self>,
18437 ) {
18438 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18439
18440 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18441 let range = if selection.is_empty() {
18442 let point = selection.head().to_display_point(&display_map);
18443 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18444 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18445 .to_point(&display_map);
18446 start..end
18447 } else {
18448 selection.range()
18449 };
18450 if display_map.folds_in_range(range).next().is_some() {
18451 self.unfold_recursive(&Default::default(), window, cx)
18452 } else {
18453 self.fold_recursive(&Default::default(), window, cx)
18454 }
18455 }
18456
18457 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18458 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18459 let mut to_fold = Vec::new();
18460 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18461 let selections = self.selections.all_adjusted(&display_map);
18462
18463 for selection in selections {
18464 let range = selection.range().sorted();
18465 let buffer_start_row = range.start.row;
18466
18467 if range.start.row != range.end.row {
18468 let mut found = false;
18469 let mut row = range.start.row;
18470 while row <= range.end.row {
18471 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18472 {
18473 found = true;
18474 row = crease.range().end.row + 1;
18475 to_fold.push(crease);
18476 } else {
18477 row += 1
18478 }
18479 }
18480 if found {
18481 continue;
18482 }
18483 }
18484
18485 for row in (0..=range.start.row).rev() {
18486 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18487 && crease.range().end.row >= buffer_start_row
18488 {
18489 to_fold.push(crease);
18490 if row <= range.start.row {
18491 break;
18492 }
18493 }
18494 }
18495 }
18496
18497 self.fold_creases(to_fold, true, window, cx);
18498 } else {
18499 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18500 let buffer_ids = self
18501 .selections
18502 .disjoint_anchor_ranges()
18503 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18504 .collect::<HashSet<_>>();
18505 for buffer_id in buffer_ids {
18506 self.fold_buffer(buffer_id, cx);
18507 }
18508 }
18509 }
18510
18511 pub fn toggle_fold_all(
18512 &mut self,
18513 _: &actions::ToggleFoldAll,
18514 window: &mut Window,
18515 cx: &mut Context<Self>,
18516 ) {
18517 if self.buffer.read(cx).is_singleton() {
18518 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18519 let has_folds = display_map
18520 .folds_in_range(0..display_map.buffer_snapshot().len())
18521 .next()
18522 .is_some();
18523
18524 if has_folds {
18525 self.unfold_all(&actions::UnfoldAll, window, cx);
18526 } else {
18527 self.fold_all(&actions::FoldAll, window, cx);
18528 }
18529 } else {
18530 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18531 let should_unfold = buffer_ids
18532 .iter()
18533 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18534
18535 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18536 editor
18537 .update_in(cx, |editor, _, cx| {
18538 for buffer_id in buffer_ids {
18539 if should_unfold {
18540 editor.unfold_buffer(buffer_id, cx);
18541 } else {
18542 editor.fold_buffer(buffer_id, cx);
18543 }
18544 }
18545 })
18546 .ok();
18547 });
18548 }
18549 }
18550
18551 fn fold_at_level(
18552 &mut self,
18553 fold_at: &FoldAtLevel,
18554 window: &mut Window,
18555 cx: &mut Context<Self>,
18556 ) {
18557 if !self.buffer.read(cx).is_singleton() {
18558 return;
18559 }
18560
18561 let fold_at_level = fold_at.0;
18562 let snapshot = self.buffer.read(cx).snapshot(cx);
18563 let mut to_fold = Vec::new();
18564 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18565
18566 let row_ranges_to_keep: Vec<Range<u32>> = self
18567 .selections
18568 .all::<Point>(&self.display_snapshot(cx))
18569 .into_iter()
18570 .map(|sel| sel.start.row..sel.end.row)
18571 .collect();
18572
18573 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18574 while start_row < end_row {
18575 match self
18576 .snapshot(window, cx)
18577 .crease_for_buffer_row(MultiBufferRow(start_row))
18578 {
18579 Some(crease) => {
18580 let nested_start_row = crease.range().start.row + 1;
18581 let nested_end_row = crease.range().end.row;
18582
18583 if current_level < fold_at_level {
18584 stack.push((nested_start_row, nested_end_row, current_level + 1));
18585 } else if current_level == fold_at_level {
18586 // Fold iff there is no selection completely contained within the fold region
18587 if !row_ranges_to_keep.iter().any(|selection| {
18588 selection.end >= nested_start_row
18589 && selection.start <= nested_end_row
18590 }) {
18591 to_fold.push(crease);
18592 }
18593 }
18594
18595 start_row = nested_end_row + 1;
18596 }
18597 None => start_row += 1,
18598 }
18599 }
18600 }
18601
18602 self.fold_creases(to_fold, true, window, cx);
18603 }
18604
18605 pub fn fold_at_level_1(
18606 &mut self,
18607 _: &actions::FoldAtLevel1,
18608 window: &mut Window,
18609 cx: &mut Context<Self>,
18610 ) {
18611 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18612 }
18613
18614 pub fn fold_at_level_2(
18615 &mut self,
18616 _: &actions::FoldAtLevel2,
18617 window: &mut Window,
18618 cx: &mut Context<Self>,
18619 ) {
18620 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18621 }
18622
18623 pub fn fold_at_level_3(
18624 &mut self,
18625 _: &actions::FoldAtLevel3,
18626 window: &mut Window,
18627 cx: &mut Context<Self>,
18628 ) {
18629 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18630 }
18631
18632 pub fn fold_at_level_4(
18633 &mut self,
18634 _: &actions::FoldAtLevel4,
18635 window: &mut Window,
18636 cx: &mut Context<Self>,
18637 ) {
18638 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18639 }
18640
18641 pub fn fold_at_level_5(
18642 &mut self,
18643 _: &actions::FoldAtLevel5,
18644 window: &mut Window,
18645 cx: &mut Context<Self>,
18646 ) {
18647 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18648 }
18649
18650 pub fn fold_at_level_6(
18651 &mut self,
18652 _: &actions::FoldAtLevel6,
18653 window: &mut Window,
18654 cx: &mut Context<Self>,
18655 ) {
18656 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18657 }
18658
18659 pub fn fold_at_level_7(
18660 &mut self,
18661 _: &actions::FoldAtLevel7,
18662 window: &mut Window,
18663 cx: &mut Context<Self>,
18664 ) {
18665 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18666 }
18667
18668 pub fn fold_at_level_8(
18669 &mut self,
18670 _: &actions::FoldAtLevel8,
18671 window: &mut Window,
18672 cx: &mut Context<Self>,
18673 ) {
18674 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18675 }
18676
18677 pub fn fold_at_level_9(
18678 &mut self,
18679 _: &actions::FoldAtLevel9,
18680 window: &mut Window,
18681 cx: &mut Context<Self>,
18682 ) {
18683 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18684 }
18685
18686 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18687 if self.buffer.read(cx).is_singleton() {
18688 let mut fold_ranges = Vec::new();
18689 let snapshot = self.buffer.read(cx).snapshot(cx);
18690
18691 for row in 0..snapshot.max_row().0 {
18692 if let Some(foldable_range) = self
18693 .snapshot(window, cx)
18694 .crease_for_buffer_row(MultiBufferRow(row))
18695 {
18696 fold_ranges.push(foldable_range);
18697 }
18698 }
18699
18700 self.fold_creases(fold_ranges, true, window, cx);
18701 } else {
18702 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18703 editor
18704 .update_in(cx, |editor, _, cx| {
18705 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18706 editor.fold_buffer(buffer_id, cx);
18707 }
18708 })
18709 .ok();
18710 });
18711 }
18712 }
18713
18714 pub fn fold_function_bodies(
18715 &mut self,
18716 _: &actions::FoldFunctionBodies,
18717 window: &mut Window,
18718 cx: &mut Context<Self>,
18719 ) {
18720 let snapshot = self.buffer.read(cx).snapshot(cx);
18721
18722 let ranges = snapshot
18723 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18724 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18725 .collect::<Vec<_>>();
18726
18727 let creases = ranges
18728 .into_iter()
18729 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18730 .collect();
18731
18732 self.fold_creases(creases, true, window, cx);
18733 }
18734
18735 pub fn fold_recursive(
18736 &mut self,
18737 _: &actions::FoldRecursive,
18738 window: &mut Window,
18739 cx: &mut Context<Self>,
18740 ) {
18741 let mut to_fold = Vec::new();
18742 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18743 let selections = self.selections.all_adjusted(&display_map);
18744
18745 for selection in selections {
18746 let range = selection.range().sorted();
18747 let buffer_start_row = range.start.row;
18748
18749 if range.start.row != range.end.row {
18750 let mut found = false;
18751 for row in range.start.row..=range.end.row {
18752 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18753 found = true;
18754 to_fold.push(crease);
18755 }
18756 }
18757 if found {
18758 continue;
18759 }
18760 }
18761
18762 for row in (0..=range.start.row).rev() {
18763 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18764 if crease.range().end.row >= buffer_start_row {
18765 to_fold.push(crease);
18766 } else {
18767 break;
18768 }
18769 }
18770 }
18771 }
18772
18773 self.fold_creases(to_fold, true, window, cx);
18774 }
18775
18776 pub fn fold_at(
18777 &mut self,
18778 buffer_row: MultiBufferRow,
18779 window: &mut Window,
18780 cx: &mut Context<Self>,
18781 ) {
18782 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18783
18784 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18785 let autoscroll = self
18786 .selections
18787 .all::<Point>(&display_map)
18788 .iter()
18789 .any(|selection| crease.range().overlaps(&selection.range()));
18790
18791 self.fold_creases(vec![crease], autoscroll, window, cx);
18792 }
18793 }
18794
18795 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18796 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18797 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18798 let buffer = display_map.buffer_snapshot();
18799 let selections = self.selections.all::<Point>(&display_map);
18800 let ranges = selections
18801 .iter()
18802 .map(|s| {
18803 let range = s.display_range(&display_map).sorted();
18804 let mut start = range.start.to_point(&display_map);
18805 let mut end = range.end.to_point(&display_map);
18806 start.column = 0;
18807 end.column = buffer.line_len(MultiBufferRow(end.row));
18808 start..end
18809 })
18810 .collect::<Vec<_>>();
18811
18812 self.unfold_ranges(&ranges, true, true, cx);
18813 } else {
18814 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18815 let buffer_ids = self
18816 .selections
18817 .disjoint_anchor_ranges()
18818 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18819 .collect::<HashSet<_>>();
18820 for buffer_id in buffer_ids {
18821 self.unfold_buffer(buffer_id, cx);
18822 }
18823 }
18824 }
18825
18826 pub fn unfold_recursive(
18827 &mut self,
18828 _: &UnfoldRecursive,
18829 _window: &mut Window,
18830 cx: &mut Context<Self>,
18831 ) {
18832 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18833 let selections = self.selections.all::<Point>(&display_map);
18834 let ranges = selections
18835 .iter()
18836 .map(|s| {
18837 let mut range = s.display_range(&display_map).sorted();
18838 *range.start.column_mut() = 0;
18839 *range.end.column_mut() = display_map.line_len(range.end.row());
18840 let start = range.start.to_point(&display_map);
18841 let end = range.end.to_point(&display_map);
18842 start..end
18843 })
18844 .collect::<Vec<_>>();
18845
18846 self.unfold_ranges(&ranges, true, true, cx);
18847 }
18848
18849 pub fn unfold_at(
18850 &mut self,
18851 buffer_row: MultiBufferRow,
18852 _window: &mut Window,
18853 cx: &mut Context<Self>,
18854 ) {
18855 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18856
18857 let intersection_range = Point::new(buffer_row.0, 0)
18858 ..Point::new(
18859 buffer_row.0,
18860 display_map.buffer_snapshot().line_len(buffer_row),
18861 );
18862
18863 let autoscroll = self
18864 .selections
18865 .all::<Point>(&display_map)
18866 .iter()
18867 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18868
18869 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18870 }
18871
18872 pub fn unfold_all(
18873 &mut self,
18874 _: &actions::UnfoldAll,
18875 _window: &mut Window,
18876 cx: &mut Context<Self>,
18877 ) {
18878 if self.buffer.read(cx).is_singleton() {
18879 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18880 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18881 } else {
18882 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18883 editor
18884 .update(cx, |editor, cx| {
18885 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18886 editor.unfold_buffer(buffer_id, cx);
18887 }
18888 })
18889 .ok();
18890 });
18891 }
18892 }
18893
18894 pub fn fold_selected_ranges(
18895 &mut self,
18896 _: &FoldSelectedRanges,
18897 window: &mut Window,
18898 cx: &mut Context<Self>,
18899 ) {
18900 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18901 let selections = self.selections.all_adjusted(&display_map);
18902 let ranges = selections
18903 .into_iter()
18904 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18905 .collect::<Vec<_>>();
18906 self.fold_creases(ranges, true, window, cx);
18907 }
18908
18909 pub fn fold_ranges<T: ToOffset + Clone>(
18910 &mut self,
18911 ranges: Vec<Range<T>>,
18912 auto_scroll: bool,
18913 window: &mut Window,
18914 cx: &mut Context<Self>,
18915 ) {
18916 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18917 let ranges = ranges
18918 .into_iter()
18919 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18920 .collect::<Vec<_>>();
18921 self.fold_creases(ranges, auto_scroll, window, cx);
18922 }
18923
18924 pub fn fold_creases<T: ToOffset + Clone>(
18925 &mut self,
18926 creases: Vec<Crease<T>>,
18927 auto_scroll: bool,
18928 _window: &mut Window,
18929 cx: &mut Context<Self>,
18930 ) {
18931 if creases.is_empty() {
18932 return;
18933 }
18934
18935 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18936
18937 if auto_scroll {
18938 self.request_autoscroll(Autoscroll::fit(), cx);
18939 }
18940
18941 cx.notify();
18942
18943 self.scrollbar_marker_state.dirty = true;
18944 self.folds_did_change(cx);
18945 }
18946
18947 /// Removes any folds whose ranges intersect any of the given ranges.
18948 pub fn unfold_ranges<T: ToOffset + Clone>(
18949 &mut self,
18950 ranges: &[Range<T>],
18951 inclusive: bool,
18952 auto_scroll: bool,
18953 cx: &mut Context<Self>,
18954 ) {
18955 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18956 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18957 });
18958 self.folds_did_change(cx);
18959 }
18960
18961 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18962 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18963 return;
18964 }
18965
18966 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18967 self.display_map.update(cx, |display_map, cx| {
18968 display_map.fold_buffers([buffer_id], cx)
18969 });
18970
18971 let snapshot = self.display_snapshot(cx);
18972 self.selections.change_with(&snapshot, |selections| {
18973 selections.remove_selections_from_buffer(buffer_id);
18974 });
18975
18976 cx.emit(EditorEvent::BufferFoldToggled {
18977 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18978 folded: true,
18979 });
18980 cx.notify();
18981 }
18982
18983 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18984 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18985 return;
18986 }
18987 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18988 self.display_map.update(cx, |display_map, cx| {
18989 display_map.unfold_buffers([buffer_id], cx);
18990 });
18991 cx.emit(EditorEvent::BufferFoldToggled {
18992 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18993 folded: false,
18994 });
18995 cx.notify();
18996 }
18997
18998 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18999 self.display_map.read(cx).is_buffer_folded(buffer)
19000 }
19001
19002 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19003 self.display_map.read(cx).folded_buffers()
19004 }
19005
19006 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19007 self.display_map.update(cx, |display_map, cx| {
19008 display_map.disable_header_for_buffer(buffer_id, cx);
19009 });
19010 cx.notify();
19011 }
19012
19013 /// Removes any folds with the given ranges.
19014 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19015 &mut self,
19016 ranges: &[Range<T>],
19017 type_id: TypeId,
19018 auto_scroll: bool,
19019 cx: &mut Context<Self>,
19020 ) {
19021 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19022 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19023 });
19024 self.folds_did_change(cx);
19025 }
19026
19027 fn remove_folds_with<T: ToOffset + Clone>(
19028 &mut self,
19029 ranges: &[Range<T>],
19030 auto_scroll: bool,
19031 cx: &mut Context<Self>,
19032 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19033 ) {
19034 if ranges.is_empty() {
19035 return;
19036 }
19037
19038 let mut buffers_affected = HashSet::default();
19039 let multi_buffer = self.buffer().read(cx);
19040 for range in ranges {
19041 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19042 buffers_affected.insert(buffer.read(cx).remote_id());
19043 };
19044 }
19045
19046 self.display_map.update(cx, update);
19047
19048 if auto_scroll {
19049 self.request_autoscroll(Autoscroll::fit(), cx);
19050 }
19051
19052 cx.notify();
19053 self.scrollbar_marker_state.dirty = true;
19054 self.active_indent_guides_state.dirty = true;
19055 }
19056
19057 pub fn update_renderer_widths(
19058 &mut self,
19059 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19060 cx: &mut Context<Self>,
19061 ) -> bool {
19062 self.display_map
19063 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19064 }
19065
19066 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19067 self.display_map.read(cx).fold_placeholder.clone()
19068 }
19069
19070 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19071 self.buffer.update(cx, |buffer, cx| {
19072 buffer.set_all_diff_hunks_expanded(cx);
19073 });
19074 }
19075
19076 pub fn expand_all_diff_hunks(
19077 &mut self,
19078 _: &ExpandAllDiffHunks,
19079 _window: &mut Window,
19080 cx: &mut Context<Self>,
19081 ) {
19082 self.buffer.update(cx, |buffer, cx| {
19083 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19084 });
19085 }
19086
19087 pub fn collapse_all_diff_hunks(
19088 &mut self,
19089 _: &CollapseAllDiffHunks,
19090 _window: &mut Window,
19091 cx: &mut Context<Self>,
19092 ) {
19093 self.buffer.update(cx, |buffer, cx| {
19094 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19095 });
19096 }
19097
19098 pub fn toggle_selected_diff_hunks(
19099 &mut self,
19100 _: &ToggleSelectedDiffHunks,
19101 _window: &mut Window,
19102 cx: &mut Context<Self>,
19103 ) {
19104 let ranges: Vec<_> = self
19105 .selections
19106 .disjoint_anchors()
19107 .iter()
19108 .map(|s| s.range())
19109 .collect();
19110 self.toggle_diff_hunks_in_ranges(ranges, cx);
19111 }
19112
19113 pub fn diff_hunks_in_ranges<'a>(
19114 &'a self,
19115 ranges: &'a [Range<Anchor>],
19116 buffer: &'a MultiBufferSnapshot,
19117 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19118 ranges.iter().flat_map(move |range| {
19119 let end_excerpt_id = range.end.excerpt_id;
19120 let range = range.to_point(buffer);
19121 let mut peek_end = range.end;
19122 if range.end.row < buffer.max_row().0 {
19123 peek_end = Point::new(range.end.row + 1, 0);
19124 }
19125 buffer
19126 .diff_hunks_in_range(range.start..peek_end)
19127 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19128 })
19129 }
19130
19131 pub fn has_stageable_diff_hunks_in_ranges(
19132 &self,
19133 ranges: &[Range<Anchor>],
19134 snapshot: &MultiBufferSnapshot,
19135 ) -> bool {
19136 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19137 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19138 }
19139
19140 pub fn toggle_staged_selected_diff_hunks(
19141 &mut self,
19142 _: &::git::ToggleStaged,
19143 _: &mut Window,
19144 cx: &mut Context<Self>,
19145 ) {
19146 let snapshot = self.buffer.read(cx).snapshot(cx);
19147 let ranges: Vec<_> = self
19148 .selections
19149 .disjoint_anchors()
19150 .iter()
19151 .map(|s| s.range())
19152 .collect();
19153 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19154 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19155 }
19156
19157 pub fn set_render_diff_hunk_controls(
19158 &mut self,
19159 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19160 cx: &mut Context<Self>,
19161 ) {
19162 self.render_diff_hunk_controls = render_diff_hunk_controls;
19163 cx.notify();
19164 }
19165
19166 pub fn stage_and_next(
19167 &mut self,
19168 _: &::git::StageAndNext,
19169 window: &mut Window,
19170 cx: &mut Context<Self>,
19171 ) {
19172 self.do_stage_or_unstage_and_next(true, window, cx);
19173 }
19174
19175 pub fn unstage_and_next(
19176 &mut self,
19177 _: &::git::UnstageAndNext,
19178 window: &mut Window,
19179 cx: &mut Context<Self>,
19180 ) {
19181 self.do_stage_or_unstage_and_next(false, window, cx);
19182 }
19183
19184 pub fn stage_or_unstage_diff_hunks(
19185 &mut self,
19186 stage: bool,
19187 ranges: Vec<Range<Anchor>>,
19188 cx: &mut Context<Self>,
19189 ) {
19190 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19191 cx.spawn(async move |this, cx| {
19192 task.await?;
19193 this.update(cx, |this, cx| {
19194 let snapshot = this.buffer.read(cx).snapshot(cx);
19195 let chunk_by = this
19196 .diff_hunks_in_ranges(&ranges, &snapshot)
19197 .chunk_by(|hunk| hunk.buffer_id);
19198 for (buffer_id, hunks) in &chunk_by {
19199 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19200 }
19201 })
19202 })
19203 .detach_and_log_err(cx);
19204 }
19205
19206 fn save_buffers_for_ranges_if_needed(
19207 &mut self,
19208 ranges: &[Range<Anchor>],
19209 cx: &mut Context<Editor>,
19210 ) -> Task<Result<()>> {
19211 let multibuffer = self.buffer.read(cx);
19212 let snapshot = multibuffer.read(cx);
19213 let buffer_ids: HashSet<_> = ranges
19214 .iter()
19215 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19216 .collect();
19217 drop(snapshot);
19218
19219 let mut buffers = HashSet::default();
19220 for buffer_id in buffer_ids {
19221 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19222 let buffer = buffer_entity.read(cx);
19223 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19224 {
19225 buffers.insert(buffer_entity);
19226 }
19227 }
19228 }
19229
19230 if let Some(project) = &self.project {
19231 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19232 } else {
19233 Task::ready(Ok(()))
19234 }
19235 }
19236
19237 fn do_stage_or_unstage_and_next(
19238 &mut self,
19239 stage: bool,
19240 window: &mut Window,
19241 cx: &mut Context<Self>,
19242 ) {
19243 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19244
19245 if ranges.iter().any(|range| range.start != range.end) {
19246 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19247 return;
19248 }
19249
19250 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19251 let snapshot = self.snapshot(window, cx);
19252 let position = self
19253 .selections
19254 .newest::<Point>(&snapshot.display_snapshot)
19255 .head();
19256 let mut row = snapshot
19257 .buffer_snapshot()
19258 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19259 .find(|hunk| hunk.row_range.start.0 > position.row)
19260 .map(|hunk| hunk.row_range.start);
19261
19262 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19263 // Outside of the project diff editor, wrap around to the beginning.
19264 if !all_diff_hunks_expanded {
19265 row = row.or_else(|| {
19266 snapshot
19267 .buffer_snapshot()
19268 .diff_hunks_in_range(Point::zero()..position)
19269 .find(|hunk| hunk.row_range.end.0 < position.row)
19270 .map(|hunk| hunk.row_range.start)
19271 });
19272 }
19273
19274 if let Some(row) = row {
19275 let destination = Point::new(row.0, 0);
19276 let autoscroll = Autoscroll::center();
19277
19278 self.unfold_ranges(&[destination..destination], false, false, cx);
19279 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19280 s.select_ranges([destination..destination]);
19281 });
19282 }
19283 }
19284
19285 fn do_stage_or_unstage(
19286 &self,
19287 stage: bool,
19288 buffer_id: BufferId,
19289 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19290 cx: &mut App,
19291 ) -> Option<()> {
19292 let project = self.project()?;
19293 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19294 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19295 let buffer_snapshot = buffer.read(cx).snapshot();
19296 let file_exists = buffer_snapshot
19297 .file()
19298 .is_some_and(|file| file.disk_state().exists());
19299 diff.update(cx, |diff, cx| {
19300 diff.stage_or_unstage_hunks(
19301 stage,
19302 &hunks
19303 .map(|hunk| buffer_diff::DiffHunk {
19304 buffer_range: hunk.buffer_range,
19305 diff_base_byte_range: hunk.diff_base_byte_range,
19306 secondary_status: hunk.secondary_status,
19307 range: Point::zero()..Point::zero(), // unused
19308 })
19309 .collect::<Vec<_>>(),
19310 &buffer_snapshot,
19311 file_exists,
19312 cx,
19313 )
19314 });
19315 None
19316 }
19317
19318 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19319 let ranges: Vec<_> = self
19320 .selections
19321 .disjoint_anchors()
19322 .iter()
19323 .map(|s| s.range())
19324 .collect();
19325 self.buffer
19326 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19327 }
19328
19329 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19330 self.buffer.update(cx, |buffer, cx| {
19331 let ranges = vec![Anchor::min()..Anchor::max()];
19332 if !buffer.all_diff_hunks_expanded()
19333 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19334 {
19335 buffer.collapse_diff_hunks(ranges, cx);
19336 true
19337 } else {
19338 false
19339 }
19340 })
19341 }
19342
19343 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19344 if self.buffer.read(cx).all_diff_hunks_expanded() {
19345 return true;
19346 }
19347 let ranges = vec![Anchor::min()..Anchor::max()];
19348 self.buffer
19349 .read(cx)
19350 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19351 }
19352
19353 fn toggle_diff_hunks_in_ranges(
19354 &mut self,
19355 ranges: Vec<Range<Anchor>>,
19356 cx: &mut Context<Editor>,
19357 ) {
19358 self.buffer.update(cx, |buffer, cx| {
19359 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19360 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19361 })
19362 }
19363
19364 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19365 self.buffer.update(cx, |buffer, cx| {
19366 let snapshot = buffer.snapshot(cx);
19367 let excerpt_id = range.end.excerpt_id;
19368 let point_range = range.to_point(&snapshot);
19369 let expand = !buffer.single_hunk_is_expanded(range, cx);
19370 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19371 })
19372 }
19373
19374 pub(crate) fn apply_all_diff_hunks(
19375 &mut self,
19376 _: &ApplyAllDiffHunks,
19377 window: &mut Window,
19378 cx: &mut Context<Self>,
19379 ) {
19380 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19381
19382 let buffers = self.buffer.read(cx).all_buffers();
19383 for branch_buffer in buffers {
19384 branch_buffer.update(cx, |branch_buffer, cx| {
19385 branch_buffer.merge_into_base(Vec::new(), cx);
19386 });
19387 }
19388
19389 if let Some(project) = self.project.clone() {
19390 self.save(
19391 SaveOptions {
19392 format: true,
19393 autosave: false,
19394 },
19395 project,
19396 window,
19397 cx,
19398 )
19399 .detach_and_log_err(cx);
19400 }
19401 }
19402
19403 pub(crate) fn apply_selected_diff_hunks(
19404 &mut self,
19405 _: &ApplyDiffHunk,
19406 window: &mut Window,
19407 cx: &mut Context<Self>,
19408 ) {
19409 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19410 let snapshot = self.snapshot(window, cx);
19411 let hunks = snapshot.hunks_for_ranges(
19412 self.selections
19413 .all(&snapshot.display_snapshot)
19414 .into_iter()
19415 .map(|selection| selection.range()),
19416 );
19417 let mut ranges_by_buffer = HashMap::default();
19418 self.transact(window, cx, |editor, _window, cx| {
19419 for hunk in hunks {
19420 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19421 ranges_by_buffer
19422 .entry(buffer.clone())
19423 .or_insert_with(Vec::new)
19424 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19425 }
19426 }
19427
19428 for (buffer, ranges) in ranges_by_buffer {
19429 buffer.update(cx, |buffer, cx| {
19430 buffer.merge_into_base(ranges, cx);
19431 });
19432 }
19433 });
19434
19435 if let Some(project) = self.project.clone() {
19436 self.save(
19437 SaveOptions {
19438 format: true,
19439 autosave: false,
19440 },
19441 project,
19442 window,
19443 cx,
19444 )
19445 .detach_and_log_err(cx);
19446 }
19447 }
19448
19449 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19450 if hovered != self.gutter_hovered {
19451 self.gutter_hovered = hovered;
19452 cx.notify();
19453 }
19454 }
19455
19456 pub fn insert_blocks(
19457 &mut self,
19458 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19459 autoscroll: Option<Autoscroll>,
19460 cx: &mut Context<Self>,
19461 ) -> Vec<CustomBlockId> {
19462 let blocks = self
19463 .display_map
19464 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19465 if let Some(autoscroll) = autoscroll {
19466 self.request_autoscroll(autoscroll, cx);
19467 }
19468 cx.notify();
19469 blocks
19470 }
19471
19472 pub fn resize_blocks(
19473 &mut self,
19474 heights: HashMap<CustomBlockId, u32>,
19475 autoscroll: Option<Autoscroll>,
19476 cx: &mut Context<Self>,
19477 ) {
19478 self.display_map
19479 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19480 if let Some(autoscroll) = autoscroll {
19481 self.request_autoscroll(autoscroll, cx);
19482 }
19483 cx.notify();
19484 }
19485
19486 pub fn replace_blocks(
19487 &mut self,
19488 renderers: HashMap<CustomBlockId, RenderBlock>,
19489 autoscroll: Option<Autoscroll>,
19490 cx: &mut Context<Self>,
19491 ) {
19492 self.display_map
19493 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19494 if let Some(autoscroll) = autoscroll {
19495 self.request_autoscroll(autoscroll, cx);
19496 }
19497 cx.notify();
19498 }
19499
19500 pub fn remove_blocks(
19501 &mut self,
19502 block_ids: HashSet<CustomBlockId>,
19503 autoscroll: Option<Autoscroll>,
19504 cx: &mut Context<Self>,
19505 ) {
19506 self.display_map.update(cx, |display_map, cx| {
19507 display_map.remove_blocks(block_ids, cx)
19508 });
19509 if let Some(autoscroll) = autoscroll {
19510 self.request_autoscroll(autoscroll, cx);
19511 }
19512 cx.notify();
19513 }
19514
19515 pub fn row_for_block(
19516 &self,
19517 block_id: CustomBlockId,
19518 cx: &mut Context<Self>,
19519 ) -> Option<DisplayRow> {
19520 self.display_map
19521 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19522 }
19523
19524 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19525 self.focused_block = Some(focused_block);
19526 }
19527
19528 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19529 self.focused_block.take()
19530 }
19531
19532 pub fn insert_creases(
19533 &mut self,
19534 creases: impl IntoIterator<Item = Crease<Anchor>>,
19535 cx: &mut Context<Self>,
19536 ) -> Vec<CreaseId> {
19537 self.display_map
19538 .update(cx, |map, cx| map.insert_creases(creases, cx))
19539 }
19540
19541 pub fn remove_creases(
19542 &mut self,
19543 ids: impl IntoIterator<Item = CreaseId>,
19544 cx: &mut Context<Self>,
19545 ) -> Vec<(CreaseId, Range<Anchor>)> {
19546 self.display_map
19547 .update(cx, |map, cx| map.remove_creases(ids, cx))
19548 }
19549
19550 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19551 self.display_map
19552 .update(cx, |map, cx| map.snapshot(cx))
19553 .longest_row()
19554 }
19555
19556 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19557 self.display_map
19558 .update(cx, |map, cx| map.snapshot(cx))
19559 .max_point()
19560 }
19561
19562 pub fn text(&self, cx: &App) -> String {
19563 self.buffer.read(cx).read(cx).text()
19564 }
19565
19566 pub fn is_empty(&self, cx: &App) -> bool {
19567 self.buffer.read(cx).read(cx).is_empty()
19568 }
19569
19570 pub fn text_option(&self, cx: &App) -> Option<String> {
19571 let text = self.text(cx);
19572 let text = text.trim();
19573
19574 if text.is_empty() {
19575 return None;
19576 }
19577
19578 Some(text.to_string())
19579 }
19580
19581 pub fn set_text(
19582 &mut self,
19583 text: impl Into<Arc<str>>,
19584 window: &mut Window,
19585 cx: &mut Context<Self>,
19586 ) {
19587 self.transact(window, cx, |this, _, cx| {
19588 this.buffer
19589 .read(cx)
19590 .as_singleton()
19591 .expect("you can only call set_text on editors for singleton buffers")
19592 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19593 });
19594 }
19595
19596 pub fn display_text(&self, cx: &mut App) -> String {
19597 self.display_map
19598 .update(cx, |map, cx| map.snapshot(cx))
19599 .text()
19600 }
19601
19602 fn create_minimap(
19603 &self,
19604 minimap_settings: MinimapSettings,
19605 window: &mut Window,
19606 cx: &mut Context<Self>,
19607 ) -> Option<Entity<Self>> {
19608 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19609 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19610 }
19611
19612 fn initialize_new_minimap(
19613 &self,
19614 minimap_settings: MinimapSettings,
19615 window: &mut Window,
19616 cx: &mut Context<Self>,
19617 ) -> Entity<Self> {
19618 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19619
19620 let mut minimap = Editor::new_internal(
19621 EditorMode::Minimap {
19622 parent: cx.weak_entity(),
19623 },
19624 self.buffer.clone(),
19625 None,
19626 Some(self.display_map.clone()),
19627 window,
19628 cx,
19629 );
19630 minimap.scroll_manager.clone_state(&self.scroll_manager);
19631 minimap.set_text_style_refinement(TextStyleRefinement {
19632 font_size: Some(MINIMAP_FONT_SIZE),
19633 font_weight: Some(MINIMAP_FONT_WEIGHT),
19634 ..Default::default()
19635 });
19636 minimap.update_minimap_configuration(minimap_settings, cx);
19637 cx.new(|_| minimap)
19638 }
19639
19640 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19641 let current_line_highlight = minimap_settings
19642 .current_line_highlight
19643 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19644 self.set_current_line_highlight(Some(current_line_highlight));
19645 }
19646
19647 pub fn minimap(&self) -> Option<&Entity<Self>> {
19648 self.minimap
19649 .as_ref()
19650 .filter(|_| self.minimap_visibility.visible())
19651 }
19652
19653 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19654 let mut wrap_guides = smallvec![];
19655
19656 if self.show_wrap_guides == Some(false) {
19657 return wrap_guides;
19658 }
19659
19660 let settings = self.buffer.read(cx).language_settings(cx);
19661 if settings.show_wrap_guides {
19662 match self.soft_wrap_mode(cx) {
19663 SoftWrap::Column(soft_wrap) => {
19664 wrap_guides.push((soft_wrap as usize, true));
19665 }
19666 SoftWrap::Bounded(soft_wrap) => {
19667 wrap_guides.push((soft_wrap as usize, true));
19668 }
19669 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19670 }
19671 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19672 }
19673
19674 wrap_guides
19675 }
19676
19677 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19678 let settings = self.buffer.read(cx).language_settings(cx);
19679 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19680 match mode {
19681 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19682 SoftWrap::None
19683 }
19684 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19685 language_settings::SoftWrap::PreferredLineLength => {
19686 SoftWrap::Column(settings.preferred_line_length)
19687 }
19688 language_settings::SoftWrap::Bounded => {
19689 SoftWrap::Bounded(settings.preferred_line_length)
19690 }
19691 }
19692 }
19693
19694 pub fn set_soft_wrap_mode(
19695 &mut self,
19696 mode: language_settings::SoftWrap,
19697
19698 cx: &mut Context<Self>,
19699 ) {
19700 self.soft_wrap_mode_override = Some(mode);
19701 cx.notify();
19702 }
19703
19704 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19705 self.hard_wrap = hard_wrap;
19706 cx.notify();
19707 }
19708
19709 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19710 self.text_style_refinement = Some(style);
19711 }
19712
19713 /// called by the Element so we know what style we were most recently rendered with.
19714 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19715 // We intentionally do not inform the display map about the minimap style
19716 // so that wrapping is not recalculated and stays consistent for the editor
19717 // and its linked minimap.
19718 if !self.mode.is_minimap() {
19719 let font = style.text.font();
19720 let font_size = style.text.font_size.to_pixels(window.rem_size());
19721 let display_map = self
19722 .placeholder_display_map
19723 .as_ref()
19724 .filter(|_| self.is_empty(cx))
19725 .unwrap_or(&self.display_map);
19726
19727 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19728 }
19729 self.style = Some(style);
19730 }
19731
19732 pub fn style(&self) -> Option<&EditorStyle> {
19733 self.style.as_ref()
19734 }
19735
19736 // Called by the element. This method is not designed to be called outside of the editor
19737 // element's layout code because it does not notify when rewrapping is computed synchronously.
19738 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19739 if self.is_empty(cx) {
19740 self.placeholder_display_map
19741 .as_ref()
19742 .map_or(false, |display_map| {
19743 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19744 })
19745 } else {
19746 self.display_map
19747 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19748 }
19749 }
19750
19751 pub fn set_soft_wrap(&mut self) {
19752 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19753 }
19754
19755 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19756 if self.soft_wrap_mode_override.is_some() {
19757 self.soft_wrap_mode_override.take();
19758 } else {
19759 let soft_wrap = match self.soft_wrap_mode(cx) {
19760 SoftWrap::GitDiff => return,
19761 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19762 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19763 language_settings::SoftWrap::None
19764 }
19765 };
19766 self.soft_wrap_mode_override = Some(soft_wrap);
19767 }
19768 cx.notify();
19769 }
19770
19771 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19772 let Some(workspace) = self.workspace() else {
19773 return;
19774 };
19775 let fs = workspace.read(cx).app_state().fs.clone();
19776 let current_show = TabBarSettings::get_global(cx).show;
19777 update_settings_file(fs, cx, move |setting, _| {
19778 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19779 });
19780 }
19781
19782 pub fn toggle_indent_guides(
19783 &mut self,
19784 _: &ToggleIndentGuides,
19785 _: &mut Window,
19786 cx: &mut Context<Self>,
19787 ) {
19788 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19789 self.buffer
19790 .read(cx)
19791 .language_settings(cx)
19792 .indent_guides
19793 .enabled
19794 });
19795 self.show_indent_guides = Some(!currently_enabled);
19796 cx.notify();
19797 }
19798
19799 fn should_show_indent_guides(&self) -> Option<bool> {
19800 self.show_indent_guides
19801 }
19802
19803 pub fn toggle_line_numbers(
19804 &mut self,
19805 _: &ToggleLineNumbers,
19806 _: &mut Window,
19807 cx: &mut Context<Self>,
19808 ) {
19809 let mut editor_settings = EditorSettings::get_global(cx).clone();
19810 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19811 EditorSettings::override_global(editor_settings, cx);
19812 }
19813
19814 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19815 if let Some(show_line_numbers) = self.show_line_numbers {
19816 return show_line_numbers;
19817 }
19818 EditorSettings::get_global(cx).gutter.line_numbers
19819 }
19820
19821 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19822 match (
19823 self.use_relative_line_numbers,
19824 EditorSettings::get_global(cx).relative_line_numbers,
19825 ) {
19826 (None, setting) => setting,
19827 (Some(false), _) => RelativeLineNumbers::Disabled,
19828 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19829 (Some(true), _) => RelativeLineNumbers::Enabled,
19830 }
19831 }
19832
19833 pub fn toggle_relative_line_numbers(
19834 &mut self,
19835 _: &ToggleRelativeLineNumbers,
19836 _: &mut Window,
19837 cx: &mut Context<Self>,
19838 ) {
19839 let is_relative = self.relative_line_numbers(cx);
19840 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19841 }
19842
19843 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19844 self.use_relative_line_numbers = is_relative;
19845 cx.notify();
19846 }
19847
19848 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19849 self.show_gutter = show_gutter;
19850 cx.notify();
19851 }
19852
19853 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19854 self.show_scrollbars = ScrollbarAxes {
19855 horizontal: show,
19856 vertical: show,
19857 };
19858 cx.notify();
19859 }
19860
19861 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19862 self.show_scrollbars.vertical = show;
19863 cx.notify();
19864 }
19865
19866 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19867 self.show_scrollbars.horizontal = show;
19868 cx.notify();
19869 }
19870
19871 pub fn set_minimap_visibility(
19872 &mut self,
19873 minimap_visibility: MinimapVisibility,
19874 window: &mut Window,
19875 cx: &mut Context<Self>,
19876 ) {
19877 if self.minimap_visibility != minimap_visibility {
19878 if minimap_visibility.visible() && self.minimap.is_none() {
19879 let minimap_settings = EditorSettings::get_global(cx).minimap;
19880 self.minimap =
19881 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19882 }
19883 self.minimap_visibility = minimap_visibility;
19884 cx.notify();
19885 }
19886 }
19887
19888 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19889 self.set_show_scrollbars(false, cx);
19890 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19891 }
19892
19893 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19894 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19895 }
19896
19897 /// Normally the text in full mode and auto height editors is padded on the
19898 /// left side by roughly half a character width for improved hit testing.
19899 ///
19900 /// Use this method to disable this for cases where this is not wanted (e.g.
19901 /// if you want to align the editor text with some other text above or below)
19902 /// or if you want to add this padding to single-line editors.
19903 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19904 self.offset_content = offset_content;
19905 cx.notify();
19906 }
19907
19908 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19909 self.show_line_numbers = Some(show_line_numbers);
19910 cx.notify();
19911 }
19912
19913 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19914 self.disable_expand_excerpt_buttons = true;
19915 cx.notify();
19916 }
19917
19918 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19919 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19920 cx.notify();
19921 }
19922
19923 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19924 self.show_code_actions = Some(show_code_actions);
19925 cx.notify();
19926 }
19927
19928 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19929 self.show_runnables = Some(show_runnables);
19930 cx.notify();
19931 }
19932
19933 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19934 self.show_breakpoints = Some(show_breakpoints);
19935 cx.notify();
19936 }
19937
19938 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19939 if self.display_map.read(cx).masked != masked {
19940 self.display_map.update(cx, |map, _| map.masked = masked);
19941 }
19942 cx.notify()
19943 }
19944
19945 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19946 self.show_wrap_guides = Some(show_wrap_guides);
19947 cx.notify();
19948 }
19949
19950 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19951 self.show_indent_guides = Some(show_indent_guides);
19952 cx.notify();
19953 }
19954
19955 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19956 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19957 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19958 && let Some(dir) = file.abs_path(cx).parent()
19959 {
19960 return Some(dir.to_owned());
19961 }
19962 }
19963
19964 None
19965 }
19966
19967 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19968 self.active_excerpt(cx)?
19969 .1
19970 .read(cx)
19971 .file()
19972 .and_then(|f| f.as_local())
19973 }
19974
19975 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19976 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19977 let buffer = buffer.read(cx);
19978 if let Some(project_path) = buffer.project_path(cx) {
19979 let project = self.project()?.read(cx);
19980 project.absolute_path(&project_path, cx)
19981 } else {
19982 buffer
19983 .file()
19984 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19985 }
19986 })
19987 }
19988
19989 pub fn reveal_in_finder(
19990 &mut self,
19991 _: &RevealInFileManager,
19992 _window: &mut Window,
19993 cx: &mut Context<Self>,
19994 ) {
19995 if let Some(target) = self.target_file(cx) {
19996 cx.reveal_path(&target.abs_path(cx));
19997 }
19998 }
19999
20000 pub fn copy_path(
20001 &mut self,
20002 _: &zed_actions::workspace::CopyPath,
20003 _window: &mut Window,
20004 cx: &mut Context<Self>,
20005 ) {
20006 if let Some(path) = self.target_file_abs_path(cx)
20007 && let Some(path) = path.to_str()
20008 {
20009 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20010 } else {
20011 cx.propagate();
20012 }
20013 }
20014
20015 pub fn copy_relative_path(
20016 &mut self,
20017 _: &zed_actions::workspace::CopyRelativePath,
20018 _window: &mut Window,
20019 cx: &mut Context<Self>,
20020 ) {
20021 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20022 let project = self.project()?.read(cx);
20023 let path = buffer.read(cx).file()?.path();
20024 let path = path.display(project.path_style(cx));
20025 Some(path)
20026 }) {
20027 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20028 } else {
20029 cx.propagate();
20030 }
20031 }
20032
20033 /// Returns the project path for the editor's buffer, if any buffer is
20034 /// opened in the editor.
20035 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20036 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20037 buffer.read(cx).project_path(cx)
20038 } else {
20039 None
20040 }
20041 }
20042
20043 // Returns true if the editor handled a go-to-line request
20044 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20045 maybe!({
20046 let breakpoint_store = self.breakpoint_store.as_ref()?;
20047
20048 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20049 else {
20050 self.clear_row_highlights::<ActiveDebugLine>();
20051 return None;
20052 };
20053
20054 let position = active_stack_frame.position;
20055 let buffer_id = position.buffer_id?;
20056 let snapshot = self
20057 .project
20058 .as_ref()?
20059 .read(cx)
20060 .buffer_for_id(buffer_id, cx)?
20061 .read(cx)
20062 .snapshot();
20063
20064 let mut handled = false;
20065 for (id, ExcerptRange { context, .. }) in
20066 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20067 {
20068 if context.start.cmp(&position, &snapshot).is_ge()
20069 || context.end.cmp(&position, &snapshot).is_lt()
20070 {
20071 continue;
20072 }
20073 let snapshot = self.buffer.read(cx).snapshot(cx);
20074 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20075
20076 handled = true;
20077 self.clear_row_highlights::<ActiveDebugLine>();
20078
20079 self.go_to_line::<ActiveDebugLine>(
20080 multibuffer_anchor,
20081 Some(cx.theme().colors().editor_debugger_active_line_background),
20082 window,
20083 cx,
20084 );
20085
20086 cx.notify();
20087 }
20088
20089 handled.then_some(())
20090 })
20091 .is_some()
20092 }
20093
20094 pub fn copy_file_name_without_extension(
20095 &mut self,
20096 _: &CopyFileNameWithoutExtension,
20097 _: &mut Window,
20098 cx: &mut Context<Self>,
20099 ) {
20100 if let Some(file) = self.target_file(cx)
20101 && let Some(file_stem) = file.path().file_stem()
20102 {
20103 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20104 }
20105 }
20106
20107 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20108 if let Some(file) = self.target_file(cx)
20109 && let Some(name) = file.path().file_name()
20110 {
20111 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
20112 }
20113 }
20114
20115 pub fn toggle_git_blame(
20116 &mut self,
20117 _: &::git::Blame,
20118 window: &mut Window,
20119 cx: &mut Context<Self>,
20120 ) {
20121 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20122
20123 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20124 self.start_git_blame(true, window, cx);
20125 }
20126
20127 cx.notify();
20128 }
20129
20130 pub fn toggle_git_blame_inline(
20131 &mut self,
20132 _: &ToggleGitBlameInline,
20133 window: &mut Window,
20134 cx: &mut Context<Self>,
20135 ) {
20136 self.toggle_git_blame_inline_internal(true, window, cx);
20137 cx.notify();
20138 }
20139
20140 pub fn open_git_blame_commit(
20141 &mut self,
20142 _: &OpenGitBlameCommit,
20143 window: &mut Window,
20144 cx: &mut Context<Self>,
20145 ) {
20146 self.open_git_blame_commit_internal(window, cx);
20147 }
20148
20149 fn open_git_blame_commit_internal(
20150 &mut self,
20151 window: &mut Window,
20152 cx: &mut Context<Self>,
20153 ) -> Option<()> {
20154 let blame = self.blame.as_ref()?;
20155 let snapshot = self.snapshot(window, cx);
20156 let cursor = self
20157 .selections
20158 .newest::<Point>(&snapshot.display_snapshot)
20159 .head();
20160 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20161 let (_, blame_entry) = blame
20162 .update(cx, |blame, cx| {
20163 blame
20164 .blame_for_rows(
20165 &[RowInfo {
20166 buffer_id: Some(buffer.remote_id()),
20167 buffer_row: Some(point.row),
20168 ..Default::default()
20169 }],
20170 cx,
20171 )
20172 .next()
20173 })
20174 .flatten()?;
20175 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20176 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20177 let workspace = self.workspace()?.downgrade();
20178 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20179 None
20180 }
20181
20182 pub fn git_blame_inline_enabled(&self) -> bool {
20183 self.git_blame_inline_enabled
20184 }
20185
20186 pub fn toggle_selection_menu(
20187 &mut self,
20188 _: &ToggleSelectionMenu,
20189 _: &mut Window,
20190 cx: &mut Context<Self>,
20191 ) {
20192 self.show_selection_menu = self
20193 .show_selection_menu
20194 .map(|show_selections_menu| !show_selections_menu)
20195 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20196
20197 cx.notify();
20198 }
20199
20200 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20201 self.show_selection_menu
20202 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20203 }
20204
20205 fn start_git_blame(
20206 &mut self,
20207 user_triggered: bool,
20208 window: &mut Window,
20209 cx: &mut Context<Self>,
20210 ) {
20211 if let Some(project) = self.project() {
20212 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20213 && buffer.read(cx).file().is_none()
20214 {
20215 return;
20216 }
20217
20218 let focused = self.focus_handle(cx).contains_focused(window, cx);
20219
20220 let project = project.clone();
20221 let blame = cx
20222 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20223 self.blame_subscription =
20224 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20225 self.blame = Some(blame);
20226 }
20227 }
20228
20229 fn toggle_git_blame_inline_internal(
20230 &mut self,
20231 user_triggered: bool,
20232 window: &mut Window,
20233 cx: &mut Context<Self>,
20234 ) {
20235 if self.git_blame_inline_enabled {
20236 self.git_blame_inline_enabled = false;
20237 self.show_git_blame_inline = false;
20238 self.show_git_blame_inline_delay_task.take();
20239 } else {
20240 self.git_blame_inline_enabled = true;
20241 self.start_git_blame_inline(user_triggered, window, cx);
20242 }
20243
20244 cx.notify();
20245 }
20246
20247 fn start_git_blame_inline(
20248 &mut self,
20249 user_triggered: bool,
20250 window: &mut Window,
20251 cx: &mut Context<Self>,
20252 ) {
20253 self.start_git_blame(user_triggered, window, cx);
20254
20255 if ProjectSettings::get_global(cx)
20256 .git
20257 .inline_blame_delay()
20258 .is_some()
20259 {
20260 self.start_inline_blame_timer(window, cx);
20261 } else {
20262 self.show_git_blame_inline = true
20263 }
20264 }
20265
20266 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20267 self.blame.as_ref()
20268 }
20269
20270 pub fn show_git_blame_gutter(&self) -> bool {
20271 self.show_git_blame_gutter
20272 }
20273
20274 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20275 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20276 }
20277
20278 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20279 self.show_git_blame_inline
20280 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20281 && !self.newest_selection_head_on_empty_line(cx)
20282 && self.has_blame_entries(cx)
20283 }
20284
20285 fn has_blame_entries(&self, cx: &App) -> bool {
20286 self.blame()
20287 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20288 }
20289
20290 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20291 let cursor_anchor = self.selections.newest_anchor().head();
20292
20293 let snapshot = self.buffer.read(cx).snapshot(cx);
20294 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20295
20296 snapshot.line_len(buffer_row) == 0
20297 }
20298
20299 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20300 let buffer_and_selection = maybe!({
20301 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20302 let selection_range = selection.range();
20303
20304 let multi_buffer = self.buffer().read(cx);
20305 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20306 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20307
20308 let (buffer, range, _) = if selection.reversed {
20309 buffer_ranges.first()
20310 } else {
20311 buffer_ranges.last()
20312 }?;
20313
20314 let selection = text::ToPoint::to_point(&range.start, buffer).row
20315 ..text::ToPoint::to_point(&range.end, buffer).row;
20316 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20317 });
20318
20319 let Some((buffer, selection)) = buffer_and_selection else {
20320 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20321 };
20322
20323 let Some(project) = self.project() else {
20324 return Task::ready(Err(anyhow!("editor does not have project")));
20325 };
20326
20327 project.update(cx, |project, cx| {
20328 project.get_permalink_to_line(&buffer, selection, cx)
20329 })
20330 }
20331
20332 pub fn copy_permalink_to_line(
20333 &mut self,
20334 _: &CopyPermalinkToLine,
20335 window: &mut Window,
20336 cx: &mut Context<Self>,
20337 ) {
20338 let permalink_task = self.get_permalink_to_line(cx);
20339 let workspace = self.workspace();
20340
20341 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20342 Ok(permalink) => {
20343 cx.update(|_, cx| {
20344 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20345 })
20346 .ok();
20347 }
20348 Err(err) => {
20349 let message = format!("Failed to copy permalink: {err}");
20350
20351 anyhow::Result::<()>::Err(err).log_err();
20352
20353 if let Some(workspace) = workspace {
20354 workspace
20355 .update_in(cx, |workspace, _, cx| {
20356 struct CopyPermalinkToLine;
20357
20358 workspace.show_toast(
20359 Toast::new(
20360 NotificationId::unique::<CopyPermalinkToLine>(),
20361 message,
20362 ),
20363 cx,
20364 )
20365 })
20366 .ok();
20367 }
20368 }
20369 })
20370 .detach();
20371 }
20372
20373 pub fn copy_file_location(
20374 &mut self,
20375 _: &CopyFileLocation,
20376 _: &mut Window,
20377 cx: &mut Context<Self>,
20378 ) {
20379 let selection = self
20380 .selections
20381 .newest::<Point>(&self.display_snapshot(cx))
20382 .start
20383 .row
20384 + 1;
20385 if let Some(file) = self.target_file(cx) {
20386 let path = file.path().display(file.path_style(cx));
20387 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20388 }
20389 }
20390
20391 pub fn open_permalink_to_line(
20392 &mut self,
20393 _: &OpenPermalinkToLine,
20394 window: &mut Window,
20395 cx: &mut Context<Self>,
20396 ) {
20397 let permalink_task = self.get_permalink_to_line(cx);
20398 let workspace = self.workspace();
20399
20400 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20401 Ok(permalink) => {
20402 cx.update(|_, cx| {
20403 cx.open_url(permalink.as_ref());
20404 })
20405 .ok();
20406 }
20407 Err(err) => {
20408 let message = format!("Failed to open permalink: {err}");
20409
20410 anyhow::Result::<()>::Err(err).log_err();
20411
20412 if let Some(workspace) = workspace {
20413 workspace
20414 .update(cx, |workspace, cx| {
20415 struct OpenPermalinkToLine;
20416
20417 workspace.show_toast(
20418 Toast::new(
20419 NotificationId::unique::<OpenPermalinkToLine>(),
20420 message,
20421 ),
20422 cx,
20423 )
20424 })
20425 .ok();
20426 }
20427 }
20428 })
20429 .detach();
20430 }
20431
20432 pub fn insert_uuid_v4(
20433 &mut self,
20434 _: &InsertUuidV4,
20435 window: &mut Window,
20436 cx: &mut Context<Self>,
20437 ) {
20438 self.insert_uuid(UuidVersion::V4, window, cx);
20439 }
20440
20441 pub fn insert_uuid_v7(
20442 &mut self,
20443 _: &InsertUuidV7,
20444 window: &mut Window,
20445 cx: &mut Context<Self>,
20446 ) {
20447 self.insert_uuid(UuidVersion::V7, window, cx);
20448 }
20449
20450 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20451 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20452 self.transact(window, cx, |this, window, cx| {
20453 let edits = this
20454 .selections
20455 .all::<Point>(&this.display_snapshot(cx))
20456 .into_iter()
20457 .map(|selection| {
20458 let uuid = match version {
20459 UuidVersion::V4 => uuid::Uuid::new_v4(),
20460 UuidVersion::V7 => uuid::Uuid::now_v7(),
20461 };
20462
20463 (selection.range(), uuid.to_string())
20464 });
20465 this.edit(edits, cx);
20466 this.refresh_edit_prediction(true, false, window, cx);
20467 });
20468 }
20469
20470 pub fn open_selections_in_multibuffer(
20471 &mut self,
20472 _: &OpenSelectionsInMultibuffer,
20473 window: &mut Window,
20474 cx: &mut Context<Self>,
20475 ) {
20476 let multibuffer = self.buffer.read(cx);
20477
20478 let Some(buffer) = multibuffer.as_singleton() else {
20479 return;
20480 };
20481
20482 let Some(workspace) = self.workspace() else {
20483 return;
20484 };
20485
20486 let title = multibuffer.title(cx).to_string();
20487
20488 let locations = self
20489 .selections
20490 .all_anchors(&self.display_snapshot(cx))
20491 .iter()
20492 .map(|selection| {
20493 (
20494 buffer.clone(),
20495 (selection.start.text_anchor..selection.end.text_anchor)
20496 .to_point(buffer.read(cx)),
20497 )
20498 })
20499 .into_group_map();
20500
20501 cx.spawn_in(window, async move |_, cx| {
20502 workspace.update_in(cx, |workspace, window, cx| {
20503 Self::open_locations_in_multibuffer(
20504 workspace,
20505 locations,
20506 format!("Selections for '{title}'"),
20507 false,
20508 MultibufferSelectionMode::All,
20509 window,
20510 cx,
20511 );
20512 })
20513 })
20514 .detach();
20515 }
20516
20517 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20518 /// last highlight added will be used.
20519 ///
20520 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20521 pub fn highlight_rows<T: 'static>(
20522 &mut self,
20523 range: Range<Anchor>,
20524 color: Hsla,
20525 options: RowHighlightOptions,
20526 cx: &mut Context<Self>,
20527 ) {
20528 let snapshot = self.buffer().read(cx).snapshot(cx);
20529 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20530 let ix = row_highlights.binary_search_by(|highlight| {
20531 Ordering::Equal
20532 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20533 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20534 });
20535
20536 if let Err(mut ix) = ix {
20537 let index = post_inc(&mut self.highlight_order);
20538
20539 // If this range intersects with the preceding highlight, then merge it with
20540 // the preceding highlight. Otherwise insert a new highlight.
20541 let mut merged = false;
20542 if ix > 0 {
20543 let prev_highlight = &mut row_highlights[ix - 1];
20544 if prev_highlight
20545 .range
20546 .end
20547 .cmp(&range.start, &snapshot)
20548 .is_ge()
20549 {
20550 ix -= 1;
20551 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20552 prev_highlight.range.end = range.end;
20553 }
20554 merged = true;
20555 prev_highlight.index = index;
20556 prev_highlight.color = color;
20557 prev_highlight.options = options;
20558 }
20559 }
20560
20561 if !merged {
20562 row_highlights.insert(
20563 ix,
20564 RowHighlight {
20565 range,
20566 index,
20567 color,
20568 options,
20569 type_id: TypeId::of::<T>(),
20570 },
20571 );
20572 }
20573
20574 // If any of the following highlights intersect with this one, merge them.
20575 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20576 let highlight = &row_highlights[ix];
20577 if next_highlight
20578 .range
20579 .start
20580 .cmp(&highlight.range.end, &snapshot)
20581 .is_le()
20582 {
20583 if next_highlight
20584 .range
20585 .end
20586 .cmp(&highlight.range.end, &snapshot)
20587 .is_gt()
20588 {
20589 row_highlights[ix].range.end = next_highlight.range.end;
20590 }
20591 row_highlights.remove(ix + 1);
20592 } else {
20593 break;
20594 }
20595 }
20596 }
20597 }
20598
20599 /// Remove any highlighted row ranges of the given type that intersect the
20600 /// given ranges.
20601 pub fn remove_highlighted_rows<T: 'static>(
20602 &mut self,
20603 ranges_to_remove: Vec<Range<Anchor>>,
20604 cx: &mut Context<Self>,
20605 ) {
20606 let snapshot = self.buffer().read(cx).snapshot(cx);
20607 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20608 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20609 row_highlights.retain(|highlight| {
20610 while let Some(range_to_remove) = ranges_to_remove.peek() {
20611 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20612 Ordering::Less | Ordering::Equal => {
20613 ranges_to_remove.next();
20614 }
20615 Ordering::Greater => {
20616 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20617 Ordering::Less | Ordering::Equal => {
20618 return false;
20619 }
20620 Ordering::Greater => break,
20621 }
20622 }
20623 }
20624 }
20625
20626 true
20627 })
20628 }
20629
20630 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20631 pub fn clear_row_highlights<T: 'static>(&mut self) {
20632 self.highlighted_rows.remove(&TypeId::of::<T>());
20633 }
20634
20635 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20636 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20637 self.highlighted_rows
20638 .get(&TypeId::of::<T>())
20639 .map_or(&[] as &[_], |vec| vec.as_slice())
20640 .iter()
20641 .map(|highlight| (highlight.range.clone(), highlight.color))
20642 }
20643
20644 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20645 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20646 /// Allows to ignore certain kinds of highlights.
20647 pub fn highlighted_display_rows(
20648 &self,
20649 window: &mut Window,
20650 cx: &mut App,
20651 ) -> BTreeMap<DisplayRow, LineHighlight> {
20652 let snapshot = self.snapshot(window, cx);
20653 let mut used_highlight_orders = HashMap::default();
20654 self.highlighted_rows
20655 .iter()
20656 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20657 .fold(
20658 BTreeMap::<DisplayRow, LineHighlight>::new(),
20659 |mut unique_rows, highlight| {
20660 let start = highlight.range.start.to_display_point(&snapshot);
20661 let end = highlight.range.end.to_display_point(&snapshot);
20662 let start_row = start.row().0;
20663 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20664 && end.column() == 0
20665 {
20666 end.row().0.saturating_sub(1)
20667 } else {
20668 end.row().0
20669 };
20670 for row in start_row..=end_row {
20671 let used_index =
20672 used_highlight_orders.entry(row).or_insert(highlight.index);
20673 if highlight.index >= *used_index {
20674 *used_index = highlight.index;
20675 unique_rows.insert(
20676 DisplayRow(row),
20677 LineHighlight {
20678 include_gutter: highlight.options.include_gutter,
20679 border: None,
20680 background: highlight.color.into(),
20681 type_id: Some(highlight.type_id),
20682 },
20683 );
20684 }
20685 }
20686 unique_rows
20687 },
20688 )
20689 }
20690
20691 pub fn highlighted_display_row_for_autoscroll(
20692 &self,
20693 snapshot: &DisplaySnapshot,
20694 ) -> Option<DisplayRow> {
20695 self.highlighted_rows
20696 .values()
20697 .flat_map(|highlighted_rows| highlighted_rows.iter())
20698 .filter_map(|highlight| {
20699 if highlight.options.autoscroll {
20700 Some(highlight.range.start.to_display_point(snapshot).row())
20701 } else {
20702 None
20703 }
20704 })
20705 .min()
20706 }
20707
20708 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20709 self.highlight_background::<SearchWithinRange>(
20710 ranges,
20711 |colors| colors.colors().editor_document_highlight_read_background,
20712 cx,
20713 )
20714 }
20715
20716 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20717 self.breadcrumb_header = Some(new_header);
20718 }
20719
20720 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20721 self.clear_background_highlights::<SearchWithinRange>(cx);
20722 }
20723
20724 pub fn highlight_background<T: 'static>(
20725 &mut self,
20726 ranges: &[Range<Anchor>],
20727 color_fetcher: fn(&Theme) -> Hsla,
20728 cx: &mut Context<Self>,
20729 ) {
20730 self.background_highlights.insert(
20731 HighlightKey::Type(TypeId::of::<T>()),
20732 (color_fetcher, Arc::from(ranges)),
20733 );
20734 self.scrollbar_marker_state.dirty = true;
20735 cx.notify();
20736 }
20737
20738 pub fn highlight_background_key<T: 'static>(
20739 &mut self,
20740 key: usize,
20741 ranges: &[Range<Anchor>],
20742 color_fetcher: fn(&Theme) -> Hsla,
20743 cx: &mut Context<Self>,
20744 ) {
20745 self.background_highlights.insert(
20746 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20747 (color_fetcher, Arc::from(ranges)),
20748 );
20749 self.scrollbar_marker_state.dirty = true;
20750 cx.notify();
20751 }
20752
20753 pub fn clear_background_highlights<T: 'static>(
20754 &mut self,
20755 cx: &mut Context<Self>,
20756 ) -> Option<BackgroundHighlight> {
20757 let text_highlights = self
20758 .background_highlights
20759 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20760 if !text_highlights.1.is_empty() {
20761 self.scrollbar_marker_state.dirty = true;
20762 cx.notify();
20763 }
20764 Some(text_highlights)
20765 }
20766
20767 pub fn highlight_gutter<T: 'static>(
20768 &mut self,
20769 ranges: impl Into<Vec<Range<Anchor>>>,
20770 color_fetcher: fn(&App) -> Hsla,
20771 cx: &mut Context<Self>,
20772 ) {
20773 self.gutter_highlights
20774 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20775 cx.notify();
20776 }
20777
20778 pub fn clear_gutter_highlights<T: 'static>(
20779 &mut self,
20780 cx: &mut Context<Self>,
20781 ) -> Option<GutterHighlight> {
20782 cx.notify();
20783 self.gutter_highlights.remove(&TypeId::of::<T>())
20784 }
20785
20786 pub fn insert_gutter_highlight<T: 'static>(
20787 &mut self,
20788 range: Range<Anchor>,
20789 color_fetcher: fn(&App) -> Hsla,
20790 cx: &mut Context<Self>,
20791 ) {
20792 let snapshot = self.buffer().read(cx).snapshot(cx);
20793 let mut highlights = self
20794 .gutter_highlights
20795 .remove(&TypeId::of::<T>())
20796 .map(|(_, highlights)| highlights)
20797 .unwrap_or_default();
20798 let ix = highlights.binary_search_by(|highlight| {
20799 Ordering::Equal
20800 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20801 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20802 });
20803 if let Err(ix) = ix {
20804 highlights.insert(ix, range);
20805 }
20806 self.gutter_highlights
20807 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20808 }
20809
20810 pub fn remove_gutter_highlights<T: 'static>(
20811 &mut self,
20812 ranges_to_remove: Vec<Range<Anchor>>,
20813 cx: &mut Context<Self>,
20814 ) {
20815 let snapshot = self.buffer().read(cx).snapshot(cx);
20816 let Some((color_fetcher, mut gutter_highlights)) =
20817 self.gutter_highlights.remove(&TypeId::of::<T>())
20818 else {
20819 return;
20820 };
20821 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20822 gutter_highlights.retain(|highlight| {
20823 while let Some(range_to_remove) = ranges_to_remove.peek() {
20824 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20825 Ordering::Less | Ordering::Equal => {
20826 ranges_to_remove.next();
20827 }
20828 Ordering::Greater => {
20829 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20830 Ordering::Less | Ordering::Equal => {
20831 return false;
20832 }
20833 Ordering::Greater => break,
20834 }
20835 }
20836 }
20837 }
20838
20839 true
20840 });
20841 self.gutter_highlights
20842 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20843 }
20844
20845 #[cfg(feature = "test-support")]
20846 pub fn all_text_highlights(
20847 &self,
20848 window: &mut Window,
20849 cx: &mut Context<Self>,
20850 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20851 let snapshot = self.snapshot(window, cx);
20852 self.display_map.update(cx, |display_map, _| {
20853 display_map
20854 .all_text_highlights()
20855 .map(|highlight| {
20856 let (style, ranges) = highlight.as_ref();
20857 (
20858 *style,
20859 ranges
20860 .iter()
20861 .map(|range| range.clone().to_display_points(&snapshot))
20862 .collect(),
20863 )
20864 })
20865 .collect()
20866 })
20867 }
20868
20869 #[cfg(feature = "test-support")]
20870 pub fn all_text_background_highlights(
20871 &self,
20872 window: &mut Window,
20873 cx: &mut Context<Self>,
20874 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20875 let snapshot = self.snapshot(window, cx);
20876 let buffer = &snapshot.buffer_snapshot();
20877 let start = buffer.anchor_before(0);
20878 let end = buffer.anchor_after(buffer.len());
20879 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20880 }
20881
20882 #[cfg(any(test, feature = "test-support"))]
20883 pub fn sorted_background_highlights_in_range(
20884 &self,
20885 search_range: Range<Anchor>,
20886 display_snapshot: &DisplaySnapshot,
20887 theme: &Theme,
20888 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20889 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20890 res.sort_by(|a, b| {
20891 a.0.start
20892 .cmp(&b.0.start)
20893 .then_with(|| a.0.end.cmp(&b.0.end))
20894 .then_with(|| a.1.cmp(&b.1))
20895 });
20896 res
20897 }
20898
20899 #[cfg(feature = "test-support")]
20900 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20901 let snapshot = self.buffer().read(cx).snapshot(cx);
20902
20903 let highlights = self
20904 .background_highlights
20905 .get(&HighlightKey::Type(TypeId::of::<
20906 items::BufferSearchHighlights,
20907 >()));
20908
20909 if let Some((_color, ranges)) = highlights {
20910 ranges
20911 .iter()
20912 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20913 .collect_vec()
20914 } else {
20915 vec![]
20916 }
20917 }
20918
20919 fn document_highlights_for_position<'a>(
20920 &'a self,
20921 position: Anchor,
20922 buffer: &'a MultiBufferSnapshot,
20923 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20924 let read_highlights = self
20925 .background_highlights
20926 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20927 .map(|h| &h.1);
20928 let write_highlights = self
20929 .background_highlights
20930 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20931 .map(|h| &h.1);
20932 let left_position = position.bias_left(buffer);
20933 let right_position = position.bias_right(buffer);
20934 read_highlights
20935 .into_iter()
20936 .chain(write_highlights)
20937 .flat_map(move |ranges| {
20938 let start_ix = match ranges.binary_search_by(|probe| {
20939 let cmp = probe.end.cmp(&left_position, buffer);
20940 if cmp.is_ge() {
20941 Ordering::Greater
20942 } else {
20943 Ordering::Less
20944 }
20945 }) {
20946 Ok(i) | Err(i) => i,
20947 };
20948
20949 ranges[start_ix..]
20950 .iter()
20951 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20952 })
20953 }
20954
20955 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20956 self.background_highlights
20957 .get(&HighlightKey::Type(TypeId::of::<T>()))
20958 .is_some_and(|(_, highlights)| !highlights.is_empty())
20959 }
20960
20961 /// Returns all background highlights for a given range.
20962 ///
20963 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20964 pub fn background_highlights_in_range(
20965 &self,
20966 search_range: Range<Anchor>,
20967 display_snapshot: &DisplaySnapshot,
20968 theme: &Theme,
20969 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20970 let mut results = Vec::new();
20971 for (color_fetcher, ranges) in self.background_highlights.values() {
20972 let color = color_fetcher(theme);
20973 let start_ix = match ranges.binary_search_by(|probe| {
20974 let cmp = probe
20975 .end
20976 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20977 if cmp.is_gt() {
20978 Ordering::Greater
20979 } else {
20980 Ordering::Less
20981 }
20982 }) {
20983 Ok(i) | Err(i) => i,
20984 };
20985 for range in &ranges[start_ix..] {
20986 if range
20987 .start
20988 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20989 .is_ge()
20990 {
20991 break;
20992 }
20993
20994 let start = range.start.to_display_point(display_snapshot);
20995 let end = range.end.to_display_point(display_snapshot);
20996 results.push((start..end, color))
20997 }
20998 }
20999 results
21000 }
21001
21002 pub fn gutter_highlights_in_range(
21003 &self,
21004 search_range: Range<Anchor>,
21005 display_snapshot: &DisplaySnapshot,
21006 cx: &App,
21007 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21008 let mut results = Vec::new();
21009 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21010 let color = color_fetcher(cx);
21011 let start_ix = match ranges.binary_search_by(|probe| {
21012 let cmp = probe
21013 .end
21014 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21015 if cmp.is_gt() {
21016 Ordering::Greater
21017 } else {
21018 Ordering::Less
21019 }
21020 }) {
21021 Ok(i) | Err(i) => i,
21022 };
21023 for range in &ranges[start_ix..] {
21024 if range
21025 .start
21026 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21027 .is_ge()
21028 {
21029 break;
21030 }
21031
21032 let start = range.start.to_display_point(display_snapshot);
21033 let end = range.end.to_display_point(display_snapshot);
21034 results.push((start..end, color))
21035 }
21036 }
21037 results
21038 }
21039
21040 /// Get the text ranges corresponding to the redaction query
21041 pub fn redacted_ranges(
21042 &self,
21043 search_range: Range<Anchor>,
21044 display_snapshot: &DisplaySnapshot,
21045 cx: &App,
21046 ) -> Vec<Range<DisplayPoint>> {
21047 display_snapshot
21048 .buffer_snapshot()
21049 .redacted_ranges(search_range, |file| {
21050 if let Some(file) = file {
21051 file.is_private()
21052 && EditorSettings::get(
21053 Some(SettingsLocation {
21054 worktree_id: file.worktree_id(cx),
21055 path: file.path().as_ref(),
21056 }),
21057 cx,
21058 )
21059 .redact_private_values
21060 } else {
21061 false
21062 }
21063 })
21064 .map(|range| {
21065 range.start.to_display_point(display_snapshot)
21066 ..range.end.to_display_point(display_snapshot)
21067 })
21068 .collect()
21069 }
21070
21071 pub fn highlight_text_key<T: 'static>(
21072 &mut self,
21073 key: usize,
21074 ranges: Vec<Range<Anchor>>,
21075 style: HighlightStyle,
21076 cx: &mut Context<Self>,
21077 ) {
21078 self.display_map.update(cx, |map, _| {
21079 map.highlight_text(
21080 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21081 ranges,
21082 style,
21083 );
21084 });
21085 cx.notify();
21086 }
21087
21088 pub fn highlight_text<T: 'static>(
21089 &mut self,
21090 ranges: Vec<Range<Anchor>>,
21091 style: HighlightStyle,
21092 cx: &mut Context<Self>,
21093 ) {
21094 self.display_map.update(cx, |map, _| {
21095 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
21096 });
21097 cx.notify();
21098 }
21099
21100 pub fn text_highlights<'a, T: 'static>(
21101 &'a self,
21102 cx: &'a App,
21103 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21104 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21105 }
21106
21107 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21108 let cleared = self
21109 .display_map
21110 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21111 if cleared {
21112 cx.notify();
21113 }
21114 }
21115
21116 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21117 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21118 && self.focus_handle.is_focused(window)
21119 }
21120
21121 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21122 self.show_cursor_when_unfocused = is_enabled;
21123 cx.notify();
21124 }
21125
21126 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21127 cx.notify();
21128 }
21129
21130 fn on_debug_session_event(
21131 &mut self,
21132 _session: Entity<Session>,
21133 event: &SessionEvent,
21134 cx: &mut Context<Self>,
21135 ) {
21136 if let SessionEvent::InvalidateInlineValue = event {
21137 self.refresh_inline_values(cx);
21138 }
21139 }
21140
21141 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21142 let Some(project) = self.project.clone() else {
21143 return;
21144 };
21145
21146 if !self.inline_value_cache.enabled {
21147 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21148 self.splice_inlays(&inlays, Vec::new(), cx);
21149 return;
21150 }
21151
21152 let current_execution_position = self
21153 .highlighted_rows
21154 .get(&TypeId::of::<ActiveDebugLine>())
21155 .and_then(|lines| lines.last().map(|line| line.range.end));
21156
21157 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21158 let inline_values = editor
21159 .update(cx, |editor, cx| {
21160 let Some(current_execution_position) = current_execution_position else {
21161 return Some(Task::ready(Ok(Vec::new())));
21162 };
21163
21164 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21165 let snapshot = buffer.snapshot(cx);
21166
21167 let excerpt = snapshot.excerpt_containing(
21168 current_execution_position..current_execution_position,
21169 )?;
21170
21171 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21172 })?;
21173
21174 let range =
21175 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21176
21177 project.inline_values(buffer, range, cx)
21178 })
21179 .ok()
21180 .flatten()?
21181 .await
21182 .context("refreshing debugger inlays")
21183 .log_err()?;
21184
21185 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21186
21187 for (buffer_id, inline_value) in inline_values
21188 .into_iter()
21189 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21190 {
21191 buffer_inline_values
21192 .entry(buffer_id)
21193 .or_default()
21194 .push(inline_value);
21195 }
21196
21197 editor
21198 .update(cx, |editor, cx| {
21199 let snapshot = editor.buffer.read(cx).snapshot(cx);
21200 let mut new_inlays = Vec::default();
21201
21202 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21203 let buffer_id = buffer_snapshot.remote_id();
21204 buffer_inline_values
21205 .get(&buffer_id)
21206 .into_iter()
21207 .flatten()
21208 .for_each(|hint| {
21209 let inlay = Inlay::debugger(
21210 post_inc(&mut editor.next_inlay_id),
21211 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
21212 hint.text(),
21213 );
21214 if !inlay.text().chars().contains(&'\n') {
21215 new_inlays.push(inlay);
21216 }
21217 });
21218 }
21219
21220 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21221 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21222
21223 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21224 })
21225 .ok()?;
21226 Some(())
21227 });
21228 }
21229
21230 fn on_buffer_event(
21231 &mut self,
21232 multibuffer: &Entity<MultiBuffer>,
21233 event: &multi_buffer::Event,
21234 window: &mut Window,
21235 cx: &mut Context<Self>,
21236 ) {
21237 match event {
21238 multi_buffer::Event::Edited { edited_buffer } => {
21239 self.scrollbar_marker_state.dirty = true;
21240 self.active_indent_guides_state.dirty = true;
21241 self.refresh_active_diagnostics(cx);
21242 self.refresh_code_actions(window, cx);
21243 self.refresh_selected_text_highlights(true, window, cx);
21244 self.refresh_single_line_folds(window, cx);
21245 self.refresh_matching_bracket_highlights(window, cx);
21246 if self.has_active_edit_prediction() {
21247 self.update_visible_edit_prediction(window, cx);
21248 }
21249
21250 if let Some(buffer) = edited_buffer {
21251 if buffer.read(cx).file().is_none() {
21252 cx.emit(EditorEvent::TitleChanged);
21253 }
21254
21255 if self.project.is_some() {
21256 let buffer_id = buffer.read(cx).remote_id();
21257 self.register_buffer(buffer_id, cx);
21258 self.update_lsp_data(Some(buffer_id), window, cx);
21259 self.refresh_inlay_hints(
21260 InlayHintRefreshReason::BufferEdited(buffer_id),
21261 cx,
21262 );
21263 }
21264 }
21265
21266 cx.emit(EditorEvent::BufferEdited);
21267 cx.emit(SearchEvent::MatchesInvalidated);
21268
21269 let Some(project) = &self.project else { return };
21270 let (telemetry, is_via_ssh) = {
21271 let project = project.read(cx);
21272 let telemetry = project.client().telemetry().clone();
21273 let is_via_ssh = project.is_via_remote_server();
21274 (telemetry, is_via_ssh)
21275 };
21276 telemetry.log_edit_event("editor", is_via_ssh);
21277 }
21278 multi_buffer::Event::ExcerptsAdded {
21279 buffer,
21280 predecessor,
21281 excerpts,
21282 } => {
21283 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21284 let buffer_id = buffer.read(cx).remote_id();
21285 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21286 && let Some(project) = &self.project
21287 {
21288 update_uncommitted_diff_for_buffer(
21289 cx.entity(),
21290 project,
21291 [buffer.clone()],
21292 self.buffer.clone(),
21293 cx,
21294 )
21295 .detach();
21296 }
21297 self.update_lsp_data(Some(buffer_id), window, cx);
21298 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21299 cx.emit(EditorEvent::ExcerptsAdded {
21300 buffer: buffer.clone(),
21301 predecessor: *predecessor,
21302 excerpts: excerpts.clone(),
21303 });
21304 }
21305 multi_buffer::Event::ExcerptsRemoved {
21306 ids,
21307 removed_buffer_ids,
21308 } => {
21309 if let Some(inlay_hints) = &mut self.inlay_hints {
21310 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21311 }
21312 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21313 for buffer_id in removed_buffer_ids {
21314 self.registered_buffers.remove(buffer_id);
21315 }
21316 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21317 cx.emit(EditorEvent::ExcerptsRemoved {
21318 ids: ids.clone(),
21319 removed_buffer_ids: removed_buffer_ids.clone(),
21320 });
21321 }
21322 multi_buffer::Event::ExcerptsEdited {
21323 excerpt_ids,
21324 buffer_ids,
21325 } => {
21326 self.display_map.update(cx, |map, cx| {
21327 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21328 });
21329 cx.emit(EditorEvent::ExcerptsEdited {
21330 ids: excerpt_ids.clone(),
21331 });
21332 }
21333 multi_buffer::Event::ExcerptsExpanded { ids } => {
21334 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21335 self.refresh_document_highlights(cx);
21336 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21337 }
21338 multi_buffer::Event::Reparsed(buffer_id) => {
21339 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21340 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21341
21342 cx.emit(EditorEvent::Reparsed(*buffer_id));
21343 }
21344 multi_buffer::Event::DiffHunksToggled => {
21345 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21346 }
21347 multi_buffer::Event::LanguageChanged(buffer_id) => {
21348 self.registered_buffers.remove(&buffer_id);
21349 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21350 cx.emit(EditorEvent::Reparsed(*buffer_id));
21351 cx.notify();
21352 }
21353 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21354 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21355 multi_buffer::Event::FileHandleChanged
21356 | multi_buffer::Event::Reloaded
21357 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21358 multi_buffer::Event::DiagnosticsUpdated => {
21359 self.update_diagnostics_state(window, cx);
21360 }
21361 _ => {}
21362 };
21363 }
21364
21365 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21366 if !self.diagnostics_enabled() {
21367 return;
21368 }
21369 self.refresh_active_diagnostics(cx);
21370 self.refresh_inline_diagnostics(true, window, cx);
21371 self.scrollbar_marker_state.dirty = true;
21372 cx.notify();
21373 }
21374
21375 pub fn start_temporary_diff_override(&mut self) {
21376 self.load_diff_task.take();
21377 self.temporary_diff_override = true;
21378 }
21379
21380 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21381 self.temporary_diff_override = false;
21382 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21383 self.buffer.update(cx, |buffer, cx| {
21384 buffer.set_all_diff_hunks_collapsed(cx);
21385 });
21386
21387 if let Some(project) = self.project.clone() {
21388 self.load_diff_task = Some(
21389 update_uncommitted_diff_for_buffer(
21390 cx.entity(),
21391 &project,
21392 self.buffer.read(cx).all_buffers(),
21393 self.buffer.clone(),
21394 cx,
21395 )
21396 .shared(),
21397 );
21398 }
21399 }
21400
21401 fn on_display_map_changed(
21402 &mut self,
21403 _: Entity<DisplayMap>,
21404 _: &mut Window,
21405 cx: &mut Context<Self>,
21406 ) {
21407 cx.notify();
21408 }
21409
21410 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21411 if self.diagnostics_enabled() {
21412 let new_severity = EditorSettings::get_global(cx)
21413 .diagnostics_max_severity
21414 .unwrap_or(DiagnosticSeverity::Hint);
21415 self.set_max_diagnostics_severity(new_severity, cx);
21416 }
21417 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21418 self.update_edit_prediction_settings(cx);
21419 self.refresh_edit_prediction(true, false, window, cx);
21420 self.refresh_inline_values(cx);
21421 self.refresh_inlay_hints(
21422 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21423 self.selections.newest_anchor().head(),
21424 &self.buffer.read(cx).snapshot(cx),
21425 cx,
21426 )),
21427 cx,
21428 );
21429
21430 let old_cursor_shape = self.cursor_shape;
21431 let old_show_breadcrumbs = self.show_breadcrumbs;
21432
21433 {
21434 let editor_settings = EditorSettings::get_global(cx);
21435 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21436 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21437 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21438 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21439 }
21440
21441 if old_cursor_shape != self.cursor_shape {
21442 cx.emit(EditorEvent::CursorShapeChanged);
21443 }
21444
21445 if old_show_breadcrumbs != self.show_breadcrumbs {
21446 cx.emit(EditorEvent::BreadcrumbsChanged);
21447 }
21448
21449 let project_settings = ProjectSettings::get_global(cx);
21450 self.buffer_serialization = self
21451 .should_serialize_buffer()
21452 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21453
21454 if self.mode.is_full() {
21455 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21456 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21457 if self.show_inline_diagnostics != show_inline_diagnostics {
21458 self.show_inline_diagnostics = show_inline_diagnostics;
21459 self.refresh_inline_diagnostics(false, window, cx);
21460 }
21461
21462 if self.git_blame_inline_enabled != inline_blame_enabled {
21463 self.toggle_git_blame_inline_internal(false, window, cx);
21464 }
21465
21466 let minimap_settings = EditorSettings::get_global(cx).minimap;
21467 if self.minimap_visibility != MinimapVisibility::Disabled {
21468 if self.minimap_visibility.settings_visibility()
21469 != minimap_settings.minimap_enabled()
21470 {
21471 self.set_minimap_visibility(
21472 MinimapVisibility::for_mode(self.mode(), cx),
21473 window,
21474 cx,
21475 );
21476 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21477 minimap_entity.update(cx, |minimap_editor, cx| {
21478 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21479 })
21480 }
21481 }
21482 }
21483
21484 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21485 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21486 }) {
21487 if !inlay_splice.is_empty() {
21488 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21489 }
21490 self.refresh_colors_for_visible_range(None, window, cx);
21491 }
21492
21493 cx.notify();
21494 }
21495
21496 pub fn set_searchable(&mut self, searchable: bool) {
21497 self.searchable = searchable;
21498 }
21499
21500 pub fn searchable(&self) -> bool {
21501 self.searchable
21502 }
21503
21504 pub fn open_excerpts_in_split(
21505 &mut self,
21506 _: &OpenExcerptsSplit,
21507 window: &mut Window,
21508 cx: &mut Context<Self>,
21509 ) {
21510 self.open_excerpts_common(None, true, window, cx)
21511 }
21512
21513 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21514 self.open_excerpts_common(None, false, window, cx)
21515 }
21516
21517 fn open_excerpts_common(
21518 &mut self,
21519 jump_data: Option<JumpData>,
21520 split: bool,
21521 window: &mut Window,
21522 cx: &mut Context<Self>,
21523 ) {
21524 let Some(workspace) = self.workspace() else {
21525 cx.propagate();
21526 return;
21527 };
21528
21529 if self.buffer.read(cx).is_singleton() {
21530 cx.propagate();
21531 return;
21532 }
21533
21534 let mut new_selections_by_buffer = HashMap::default();
21535 match &jump_data {
21536 Some(JumpData::MultiBufferPoint {
21537 excerpt_id,
21538 position,
21539 anchor,
21540 line_offset_from_top,
21541 }) => {
21542 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21543 if let Some(buffer) = multi_buffer_snapshot
21544 .buffer_id_for_excerpt(*excerpt_id)
21545 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21546 {
21547 let buffer_snapshot = buffer.read(cx).snapshot();
21548 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21549 language::ToPoint::to_point(anchor, &buffer_snapshot)
21550 } else {
21551 buffer_snapshot.clip_point(*position, Bias::Left)
21552 };
21553 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21554 new_selections_by_buffer.insert(
21555 buffer,
21556 (
21557 vec![jump_to_offset..jump_to_offset],
21558 Some(*line_offset_from_top),
21559 ),
21560 );
21561 }
21562 }
21563 Some(JumpData::MultiBufferRow {
21564 row,
21565 line_offset_from_top,
21566 }) => {
21567 let point = MultiBufferPoint::new(row.0, 0);
21568 if let Some((buffer, buffer_point, _)) =
21569 self.buffer.read(cx).point_to_buffer_point(point, cx)
21570 {
21571 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21572 new_selections_by_buffer
21573 .entry(buffer)
21574 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21575 .0
21576 .push(buffer_offset..buffer_offset)
21577 }
21578 }
21579 None => {
21580 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21581 let multi_buffer = self.buffer.read(cx);
21582 for selection in selections {
21583 for (snapshot, range, _, anchor) in multi_buffer
21584 .snapshot(cx)
21585 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21586 {
21587 if let Some(anchor) = anchor {
21588 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21589 else {
21590 continue;
21591 };
21592 let offset = text::ToOffset::to_offset(
21593 &anchor.text_anchor,
21594 &buffer_handle.read(cx).snapshot(),
21595 );
21596 let range = offset..offset;
21597 new_selections_by_buffer
21598 .entry(buffer_handle)
21599 .or_insert((Vec::new(), None))
21600 .0
21601 .push(range)
21602 } else {
21603 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21604 else {
21605 continue;
21606 };
21607 new_selections_by_buffer
21608 .entry(buffer_handle)
21609 .or_insert((Vec::new(), None))
21610 .0
21611 .push(range)
21612 }
21613 }
21614 }
21615 }
21616 }
21617
21618 new_selections_by_buffer
21619 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21620
21621 if new_selections_by_buffer.is_empty() {
21622 return;
21623 }
21624
21625 // We defer the pane interaction because we ourselves are a workspace item
21626 // and activating a new item causes the pane to call a method on us reentrantly,
21627 // which panics if we're on the stack.
21628 window.defer(cx, move |window, cx| {
21629 workspace.update(cx, |workspace, cx| {
21630 let pane = if split {
21631 workspace.adjacent_pane(window, cx)
21632 } else {
21633 workspace.active_pane().clone()
21634 };
21635
21636 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21637 let editor = buffer
21638 .read(cx)
21639 .file()
21640 .is_none()
21641 .then(|| {
21642 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21643 // so `workspace.open_project_item` will never find them, always opening a new editor.
21644 // Instead, we try to activate the existing editor in the pane first.
21645 let (editor, pane_item_index) =
21646 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21647 let editor = item.downcast::<Editor>()?;
21648 let singleton_buffer =
21649 editor.read(cx).buffer().read(cx).as_singleton()?;
21650 if singleton_buffer == buffer {
21651 Some((editor, i))
21652 } else {
21653 None
21654 }
21655 })?;
21656 pane.update(cx, |pane, cx| {
21657 pane.activate_item(pane_item_index, true, true, window, cx)
21658 });
21659 Some(editor)
21660 })
21661 .flatten()
21662 .unwrap_or_else(|| {
21663 workspace.open_project_item::<Self>(
21664 pane.clone(),
21665 buffer,
21666 true,
21667 true,
21668 window,
21669 cx,
21670 )
21671 });
21672
21673 editor.update(cx, |editor, cx| {
21674 let autoscroll = match scroll_offset {
21675 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21676 None => Autoscroll::newest(),
21677 };
21678 let nav_history = editor.nav_history.take();
21679 editor.change_selections(
21680 SelectionEffects::scroll(autoscroll),
21681 window,
21682 cx,
21683 |s| {
21684 s.select_ranges(ranges);
21685 },
21686 );
21687 editor.nav_history = nav_history;
21688 });
21689 }
21690 })
21691 });
21692 }
21693
21694 // For now, don't allow opening excerpts in buffers that aren't backed by
21695 // regular project files.
21696 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21697 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21698 }
21699
21700 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21701 let snapshot = self.buffer.read(cx).read(cx);
21702 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21703 Some(
21704 ranges
21705 .iter()
21706 .map(move |range| {
21707 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21708 })
21709 .collect(),
21710 )
21711 }
21712
21713 fn selection_replacement_ranges(
21714 &self,
21715 range: Range<OffsetUtf16>,
21716 cx: &mut App,
21717 ) -> Vec<Range<OffsetUtf16>> {
21718 let selections = self
21719 .selections
21720 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21721 let newest_selection = selections
21722 .iter()
21723 .max_by_key(|selection| selection.id)
21724 .unwrap();
21725 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21726 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21727 let snapshot = self.buffer.read(cx).read(cx);
21728 selections
21729 .into_iter()
21730 .map(|mut selection| {
21731 selection.start.0 =
21732 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21733 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21734 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21735 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21736 })
21737 .collect()
21738 }
21739
21740 fn report_editor_event(
21741 &self,
21742 reported_event: ReportEditorEvent,
21743 file_extension: Option<String>,
21744 cx: &App,
21745 ) {
21746 if cfg!(any(test, feature = "test-support")) {
21747 return;
21748 }
21749
21750 let Some(project) = &self.project else { return };
21751
21752 // If None, we are in a file without an extension
21753 let file = self
21754 .buffer
21755 .read(cx)
21756 .as_singleton()
21757 .and_then(|b| b.read(cx).file());
21758 let file_extension = file_extension.or(file
21759 .as_ref()
21760 .and_then(|file| Path::new(file.file_name(cx)).extension())
21761 .and_then(|e| e.to_str())
21762 .map(|a| a.to_string()));
21763
21764 let vim_mode = vim_flavor(cx).is_some();
21765
21766 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21767 let copilot_enabled = edit_predictions_provider
21768 == language::language_settings::EditPredictionProvider::Copilot;
21769 let copilot_enabled_for_language = self
21770 .buffer
21771 .read(cx)
21772 .language_settings(cx)
21773 .show_edit_predictions;
21774
21775 let project = project.read(cx);
21776 let event_type = reported_event.event_type();
21777
21778 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21779 telemetry::event!(
21780 event_type,
21781 type = if auto_saved {"autosave"} else {"manual"},
21782 file_extension,
21783 vim_mode,
21784 copilot_enabled,
21785 copilot_enabled_for_language,
21786 edit_predictions_provider,
21787 is_via_ssh = project.is_via_remote_server(),
21788 );
21789 } else {
21790 telemetry::event!(
21791 event_type,
21792 file_extension,
21793 vim_mode,
21794 copilot_enabled,
21795 copilot_enabled_for_language,
21796 edit_predictions_provider,
21797 is_via_ssh = project.is_via_remote_server(),
21798 );
21799 };
21800 }
21801
21802 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21803 /// with each line being an array of {text, highlight} objects.
21804 fn copy_highlight_json(
21805 &mut self,
21806 _: &CopyHighlightJson,
21807 window: &mut Window,
21808 cx: &mut Context<Self>,
21809 ) {
21810 #[derive(Serialize)]
21811 struct Chunk<'a> {
21812 text: String,
21813 highlight: Option<&'a str>,
21814 }
21815
21816 let snapshot = self.buffer.read(cx).snapshot(cx);
21817 let range = self
21818 .selected_text_range(false, window, cx)
21819 .and_then(|selection| {
21820 if selection.range.is_empty() {
21821 None
21822 } else {
21823 Some(
21824 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21825 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21826 )
21827 }
21828 })
21829 .unwrap_or_else(|| 0..snapshot.len());
21830
21831 let chunks = snapshot.chunks(range, true);
21832 let mut lines = Vec::new();
21833 let mut line: VecDeque<Chunk> = VecDeque::new();
21834
21835 let Some(style) = self.style.as_ref() else {
21836 return;
21837 };
21838
21839 for chunk in chunks {
21840 let highlight = chunk
21841 .syntax_highlight_id
21842 .and_then(|id| id.name(&style.syntax));
21843 let mut chunk_lines = chunk.text.split('\n').peekable();
21844 while let Some(text) = chunk_lines.next() {
21845 let mut merged_with_last_token = false;
21846 if let Some(last_token) = line.back_mut()
21847 && last_token.highlight == highlight
21848 {
21849 last_token.text.push_str(text);
21850 merged_with_last_token = true;
21851 }
21852
21853 if !merged_with_last_token {
21854 line.push_back(Chunk {
21855 text: text.into(),
21856 highlight,
21857 });
21858 }
21859
21860 if chunk_lines.peek().is_some() {
21861 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21862 line.pop_front();
21863 }
21864 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21865 line.pop_back();
21866 }
21867
21868 lines.push(mem::take(&mut line));
21869 }
21870 }
21871 }
21872
21873 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21874 return;
21875 };
21876 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21877 }
21878
21879 pub fn open_context_menu(
21880 &mut self,
21881 _: &OpenContextMenu,
21882 window: &mut Window,
21883 cx: &mut Context<Self>,
21884 ) {
21885 self.request_autoscroll(Autoscroll::newest(), cx);
21886 let position = self
21887 .selections
21888 .newest_display(&self.display_snapshot(cx))
21889 .start;
21890 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21891 }
21892
21893 pub fn replay_insert_event(
21894 &mut self,
21895 text: &str,
21896 relative_utf16_range: Option<Range<isize>>,
21897 window: &mut Window,
21898 cx: &mut Context<Self>,
21899 ) {
21900 if !self.input_enabled {
21901 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21902 return;
21903 }
21904 if let Some(relative_utf16_range) = relative_utf16_range {
21905 let selections = self
21906 .selections
21907 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21908 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21909 let new_ranges = selections.into_iter().map(|range| {
21910 let start = OffsetUtf16(
21911 range
21912 .head()
21913 .0
21914 .saturating_add_signed(relative_utf16_range.start),
21915 );
21916 let end = OffsetUtf16(
21917 range
21918 .head()
21919 .0
21920 .saturating_add_signed(relative_utf16_range.end),
21921 );
21922 start..end
21923 });
21924 s.select_ranges(new_ranges);
21925 });
21926 }
21927
21928 self.handle_input(text, window, cx);
21929 }
21930
21931 pub fn is_focused(&self, window: &Window) -> bool {
21932 self.focus_handle.is_focused(window)
21933 }
21934
21935 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21936 cx.emit(EditorEvent::Focused);
21937
21938 if let Some(descendant) = self
21939 .last_focused_descendant
21940 .take()
21941 .and_then(|descendant| descendant.upgrade())
21942 {
21943 window.focus(&descendant);
21944 } else {
21945 if let Some(blame) = self.blame.as_ref() {
21946 blame.update(cx, GitBlame::focus)
21947 }
21948
21949 self.blink_manager.update(cx, BlinkManager::enable);
21950 self.show_cursor_names(window, cx);
21951 self.buffer.update(cx, |buffer, cx| {
21952 buffer.finalize_last_transaction(cx);
21953 if self.leader_id.is_none() {
21954 buffer.set_active_selections(
21955 &self.selections.disjoint_anchors_arc(),
21956 self.selections.line_mode(),
21957 self.cursor_shape,
21958 cx,
21959 );
21960 }
21961 });
21962 }
21963 }
21964
21965 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21966 cx.emit(EditorEvent::FocusedIn)
21967 }
21968
21969 fn handle_focus_out(
21970 &mut self,
21971 event: FocusOutEvent,
21972 _window: &mut Window,
21973 cx: &mut Context<Self>,
21974 ) {
21975 if event.blurred != self.focus_handle {
21976 self.last_focused_descendant = Some(event.blurred);
21977 }
21978 self.selection_drag_state = SelectionDragState::None;
21979 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21980 }
21981
21982 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21983 self.blink_manager.update(cx, BlinkManager::disable);
21984 self.buffer
21985 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21986
21987 if let Some(blame) = self.blame.as_ref() {
21988 blame.update(cx, GitBlame::blur)
21989 }
21990 if !self.hover_state.focused(window, cx) {
21991 hide_hover(self, cx);
21992 }
21993 if !self
21994 .context_menu
21995 .borrow()
21996 .as_ref()
21997 .is_some_and(|context_menu| context_menu.focused(window, cx))
21998 {
21999 self.hide_context_menu(window, cx);
22000 }
22001 self.take_active_edit_prediction(cx);
22002 cx.emit(EditorEvent::Blurred);
22003 cx.notify();
22004 }
22005
22006 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22007 let mut pending: String = window
22008 .pending_input_keystrokes()
22009 .into_iter()
22010 .flatten()
22011 .filter_map(|keystroke| {
22012 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
22013 keystroke.key_char.clone()
22014 } else {
22015 None
22016 }
22017 })
22018 .collect();
22019
22020 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22021 pending = "".to_string();
22022 }
22023
22024 let existing_pending = self
22025 .text_highlights::<PendingInput>(cx)
22026 .map(|(_, ranges)| ranges.to_vec());
22027 if existing_pending.is_none() && pending.is_empty() {
22028 return;
22029 }
22030 let transaction =
22031 self.transact(window, cx, |this, window, cx| {
22032 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
22033 let edits = selections
22034 .iter()
22035 .map(|selection| (selection.end..selection.end, pending.clone()));
22036 this.edit(edits, cx);
22037 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22038 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22039 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22040 }));
22041 });
22042 if let Some(existing_ranges) = existing_pending {
22043 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22044 this.edit(edits, cx);
22045 }
22046 });
22047
22048 let snapshot = self.snapshot(window, cx);
22049 let ranges = self
22050 .selections
22051 .all::<usize>(&snapshot.display_snapshot)
22052 .into_iter()
22053 .map(|selection| {
22054 snapshot.buffer_snapshot().anchor_after(selection.end)
22055 ..snapshot
22056 .buffer_snapshot()
22057 .anchor_before(selection.end + pending.len())
22058 })
22059 .collect();
22060
22061 if pending.is_empty() {
22062 self.clear_highlights::<PendingInput>(cx);
22063 } else {
22064 self.highlight_text::<PendingInput>(
22065 ranges,
22066 HighlightStyle {
22067 underline: Some(UnderlineStyle {
22068 thickness: px(1.),
22069 color: None,
22070 wavy: false,
22071 }),
22072 ..Default::default()
22073 },
22074 cx,
22075 );
22076 }
22077
22078 self.ime_transaction = self.ime_transaction.or(transaction);
22079 if let Some(transaction) = self.ime_transaction {
22080 self.buffer.update(cx, |buffer, cx| {
22081 buffer.group_until_transaction(transaction, cx);
22082 });
22083 }
22084
22085 if self.text_highlights::<PendingInput>(cx).is_none() {
22086 self.ime_transaction.take();
22087 }
22088 }
22089
22090 pub fn register_action_renderer(
22091 &mut self,
22092 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22093 ) -> Subscription {
22094 let id = self.next_editor_action_id.post_inc();
22095 self.editor_actions
22096 .borrow_mut()
22097 .insert(id, Box::new(listener));
22098
22099 let editor_actions = self.editor_actions.clone();
22100 Subscription::new(move || {
22101 editor_actions.borrow_mut().remove(&id);
22102 })
22103 }
22104
22105 pub fn register_action<A: Action>(
22106 &mut self,
22107 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22108 ) -> Subscription {
22109 let id = self.next_editor_action_id.post_inc();
22110 let listener = Arc::new(listener);
22111 self.editor_actions.borrow_mut().insert(
22112 id,
22113 Box::new(move |_, window, _| {
22114 let listener = listener.clone();
22115 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22116 let action = action.downcast_ref().unwrap();
22117 if phase == DispatchPhase::Bubble {
22118 listener(action, window, cx)
22119 }
22120 })
22121 }),
22122 );
22123
22124 let editor_actions = self.editor_actions.clone();
22125 Subscription::new(move || {
22126 editor_actions.borrow_mut().remove(&id);
22127 })
22128 }
22129
22130 pub fn file_header_size(&self) -> u32 {
22131 FILE_HEADER_HEIGHT
22132 }
22133
22134 pub fn restore(
22135 &mut self,
22136 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22137 window: &mut Window,
22138 cx: &mut Context<Self>,
22139 ) {
22140 let workspace = self.workspace();
22141 let project = self.project();
22142 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22143 let mut tasks = Vec::new();
22144 for (buffer_id, changes) in revert_changes {
22145 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22146 buffer.update(cx, |buffer, cx| {
22147 buffer.edit(
22148 changes
22149 .into_iter()
22150 .map(|(range, text)| (range, text.to_string())),
22151 None,
22152 cx,
22153 );
22154 });
22155
22156 if let Some(project) =
22157 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22158 {
22159 project.update(cx, |project, cx| {
22160 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22161 })
22162 }
22163 }
22164 }
22165 tasks
22166 });
22167 cx.spawn_in(window, async move |_, cx| {
22168 for (buffer, task) in save_tasks {
22169 let result = task.await;
22170 if result.is_err() {
22171 let Some(path) = buffer
22172 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22173 .ok()
22174 else {
22175 continue;
22176 };
22177 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22178 let Some(task) = cx
22179 .update_window_entity(workspace, |workspace, window, cx| {
22180 workspace
22181 .open_path_preview(path, None, false, false, false, window, cx)
22182 })
22183 .ok()
22184 else {
22185 continue;
22186 };
22187 task.await.log_err();
22188 }
22189 }
22190 }
22191 })
22192 .detach();
22193 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22194 selections.refresh()
22195 });
22196 }
22197
22198 pub fn to_pixel_point(
22199 &self,
22200 source: multi_buffer::Anchor,
22201 editor_snapshot: &EditorSnapshot,
22202 window: &mut Window,
22203 ) -> Option<gpui::Point<Pixels>> {
22204 let source_point = source.to_display_point(editor_snapshot);
22205 self.display_to_pixel_point(source_point, editor_snapshot, window)
22206 }
22207
22208 pub fn display_to_pixel_point(
22209 &self,
22210 source: DisplayPoint,
22211 editor_snapshot: &EditorSnapshot,
22212 window: &mut Window,
22213 ) -> Option<gpui::Point<Pixels>> {
22214 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22215 let text_layout_details = self.text_layout_details(window);
22216 let scroll_top = text_layout_details
22217 .scroll_anchor
22218 .scroll_position(editor_snapshot)
22219 .y;
22220
22221 if source.row().as_f64() < scroll_top.floor() {
22222 return None;
22223 }
22224 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22225 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22226 Some(gpui::Point::new(source_x, source_y))
22227 }
22228
22229 pub fn has_visible_completions_menu(&self) -> bool {
22230 !self.edit_prediction_preview_is_active()
22231 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22232 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22233 })
22234 }
22235
22236 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22237 if self.mode.is_minimap() {
22238 return;
22239 }
22240 self.addons
22241 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22242 }
22243
22244 pub fn unregister_addon<T: Addon>(&mut self) {
22245 self.addons.remove(&std::any::TypeId::of::<T>());
22246 }
22247
22248 pub fn addon<T: Addon>(&self) -> Option<&T> {
22249 let type_id = std::any::TypeId::of::<T>();
22250 self.addons
22251 .get(&type_id)
22252 .and_then(|item| item.to_any().downcast_ref::<T>())
22253 }
22254
22255 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22256 let type_id = std::any::TypeId::of::<T>();
22257 self.addons
22258 .get_mut(&type_id)
22259 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22260 }
22261
22262 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22263 let text_layout_details = self.text_layout_details(window);
22264 let style = &text_layout_details.editor_style;
22265 let font_id = window.text_system().resolve_font(&style.text.font());
22266 let font_size = style.text.font_size.to_pixels(window.rem_size());
22267 let line_height = style.text.line_height_in_pixels(window.rem_size());
22268 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22269 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22270
22271 CharacterDimensions {
22272 em_width,
22273 em_advance,
22274 line_height,
22275 }
22276 }
22277
22278 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22279 self.load_diff_task.clone()
22280 }
22281
22282 fn read_metadata_from_db(
22283 &mut self,
22284 item_id: u64,
22285 workspace_id: WorkspaceId,
22286 window: &mut Window,
22287 cx: &mut Context<Editor>,
22288 ) {
22289 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22290 && !self.mode.is_minimap()
22291 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22292 {
22293 let buffer_snapshot = OnceCell::new();
22294
22295 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22296 && !folds.is_empty()
22297 {
22298 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22299 self.fold_ranges(
22300 folds
22301 .into_iter()
22302 .map(|(start, end)| {
22303 snapshot.clip_offset(start, Bias::Left)
22304 ..snapshot.clip_offset(end, Bias::Right)
22305 })
22306 .collect(),
22307 false,
22308 window,
22309 cx,
22310 );
22311 }
22312
22313 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22314 && !selections.is_empty()
22315 {
22316 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22317 // skip adding the initial selection to selection history
22318 self.selection_history.mode = SelectionHistoryMode::Skipping;
22319 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22320 s.select_ranges(selections.into_iter().map(|(start, end)| {
22321 snapshot.clip_offset(start, Bias::Left)
22322 ..snapshot.clip_offset(end, Bias::Right)
22323 }));
22324 });
22325 self.selection_history.mode = SelectionHistoryMode::Normal;
22326 };
22327 }
22328
22329 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22330 }
22331
22332 fn update_lsp_data(
22333 &mut self,
22334 for_buffer: Option<BufferId>,
22335 window: &mut Window,
22336 cx: &mut Context<'_, Self>,
22337 ) {
22338 self.pull_diagnostics(for_buffer, window, cx);
22339 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22340 }
22341
22342 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22343 if self.ignore_lsp_data() {
22344 return;
22345 }
22346 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22347 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22348 }
22349 }
22350
22351 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22352 if self.ignore_lsp_data() {
22353 return;
22354 }
22355
22356 if !self.registered_buffers.contains_key(&buffer_id)
22357 && let Some(project) = self.project.as_ref()
22358 {
22359 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22360 project.update(cx, |project, cx| {
22361 self.registered_buffers.insert(
22362 buffer_id,
22363 project.register_buffer_with_language_servers(&buffer, cx),
22364 );
22365 });
22366 } else {
22367 self.registered_buffers.remove(&buffer_id);
22368 }
22369 }
22370 }
22371
22372 fn ignore_lsp_data(&self) -> bool {
22373 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22374 // skip any LSP updates for it.
22375 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22376 }
22377}
22378
22379fn edit_for_markdown_paste<'a>(
22380 buffer: &MultiBufferSnapshot,
22381 range: Range<usize>,
22382 to_insert: &'a str,
22383 url: Option<url::Url>,
22384) -> (Range<usize>, Cow<'a, str>) {
22385 if url.is_none() {
22386 return (range, Cow::Borrowed(to_insert));
22387 };
22388
22389 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22390
22391 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22392 Cow::Borrowed(to_insert)
22393 } else {
22394 Cow::Owned(format!("[{old_text}]({to_insert})"))
22395 };
22396 (range, new_text)
22397}
22398
22399#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
22400pub enum VimFlavor {
22401 Vim,
22402 Helix,
22403}
22404
22405pub fn vim_flavor(cx: &App) -> Option<VimFlavor> {
22406 if vim_mode_setting::HelixModeSetting::try_get(cx)
22407 .map(|helix_mode| helix_mode.0)
22408 .unwrap_or(false)
22409 {
22410 Some(VimFlavor::Helix)
22411 } else if vim_mode_setting::VimModeSetting::try_get(cx)
22412 .map(|vim_mode| vim_mode.0)
22413 .unwrap_or(false)
22414 {
22415 Some(VimFlavor::Vim)
22416 } else {
22417 None // neither vim nor helix mode
22418 }
22419}
22420
22421fn process_completion_for_edit(
22422 completion: &Completion,
22423 intent: CompletionIntent,
22424 buffer: &Entity<Buffer>,
22425 cursor_position: &text::Anchor,
22426 cx: &mut Context<Editor>,
22427) -> CompletionEdit {
22428 let buffer = buffer.read(cx);
22429 let buffer_snapshot = buffer.snapshot();
22430 let (snippet, new_text) = if completion.is_snippet() {
22431 let mut snippet_source = completion.new_text.clone();
22432 // Workaround for typescript language server issues so that methods don't expand within
22433 // strings and functions with type expressions. The previous point is used because the query
22434 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22435 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22436 let previous_point = if previous_point.column > 0 {
22437 cursor_position.to_previous_offset(&buffer_snapshot)
22438 } else {
22439 cursor_position.to_offset(&buffer_snapshot)
22440 };
22441 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22442 && scope.prefers_label_for_snippet_in_completion()
22443 && let Some(label) = completion.label()
22444 && matches!(
22445 completion.kind(),
22446 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22447 )
22448 {
22449 snippet_source = label;
22450 }
22451 match Snippet::parse(&snippet_source).log_err() {
22452 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22453 None => (None, completion.new_text.clone()),
22454 }
22455 } else {
22456 (None, completion.new_text.clone())
22457 };
22458
22459 let mut range_to_replace = {
22460 let replace_range = &completion.replace_range;
22461 if let CompletionSource::Lsp {
22462 insert_range: Some(insert_range),
22463 ..
22464 } = &completion.source
22465 {
22466 debug_assert_eq!(
22467 insert_range.start, replace_range.start,
22468 "insert_range and replace_range should start at the same position"
22469 );
22470 debug_assert!(
22471 insert_range
22472 .start
22473 .cmp(cursor_position, &buffer_snapshot)
22474 .is_le(),
22475 "insert_range should start before or at cursor position"
22476 );
22477 debug_assert!(
22478 replace_range
22479 .start
22480 .cmp(cursor_position, &buffer_snapshot)
22481 .is_le(),
22482 "replace_range should start before or at cursor position"
22483 );
22484
22485 let should_replace = match intent {
22486 CompletionIntent::CompleteWithInsert => false,
22487 CompletionIntent::CompleteWithReplace => true,
22488 CompletionIntent::Complete | CompletionIntent::Compose => {
22489 let insert_mode =
22490 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22491 .completions
22492 .lsp_insert_mode;
22493 match insert_mode {
22494 LspInsertMode::Insert => false,
22495 LspInsertMode::Replace => true,
22496 LspInsertMode::ReplaceSubsequence => {
22497 let mut text_to_replace = buffer.chars_for_range(
22498 buffer.anchor_before(replace_range.start)
22499 ..buffer.anchor_after(replace_range.end),
22500 );
22501 let mut current_needle = text_to_replace.next();
22502 for haystack_ch in completion.label.text.chars() {
22503 if let Some(needle_ch) = current_needle
22504 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22505 {
22506 current_needle = text_to_replace.next();
22507 }
22508 }
22509 current_needle.is_none()
22510 }
22511 LspInsertMode::ReplaceSuffix => {
22512 if replace_range
22513 .end
22514 .cmp(cursor_position, &buffer_snapshot)
22515 .is_gt()
22516 {
22517 let range_after_cursor = *cursor_position..replace_range.end;
22518 let text_after_cursor = buffer
22519 .text_for_range(
22520 buffer.anchor_before(range_after_cursor.start)
22521 ..buffer.anchor_after(range_after_cursor.end),
22522 )
22523 .collect::<String>()
22524 .to_ascii_lowercase();
22525 completion
22526 .label
22527 .text
22528 .to_ascii_lowercase()
22529 .ends_with(&text_after_cursor)
22530 } else {
22531 true
22532 }
22533 }
22534 }
22535 }
22536 };
22537
22538 if should_replace {
22539 replace_range.clone()
22540 } else {
22541 insert_range.clone()
22542 }
22543 } else {
22544 replace_range.clone()
22545 }
22546 };
22547
22548 if range_to_replace
22549 .end
22550 .cmp(cursor_position, &buffer_snapshot)
22551 .is_lt()
22552 {
22553 range_to_replace.end = *cursor_position;
22554 }
22555
22556 CompletionEdit {
22557 new_text,
22558 replace_range: range_to_replace.to_offset(buffer),
22559 snippet,
22560 }
22561}
22562
22563struct CompletionEdit {
22564 new_text: String,
22565 replace_range: Range<usize>,
22566 snippet: Option<Snippet>,
22567}
22568
22569fn insert_extra_newline_brackets(
22570 buffer: &MultiBufferSnapshot,
22571 range: Range<usize>,
22572 language: &language::LanguageScope,
22573) -> bool {
22574 let leading_whitespace_len = buffer
22575 .reversed_chars_at(range.start)
22576 .take_while(|c| c.is_whitespace() && *c != '\n')
22577 .map(|c| c.len_utf8())
22578 .sum::<usize>();
22579 let trailing_whitespace_len = buffer
22580 .chars_at(range.end)
22581 .take_while(|c| c.is_whitespace() && *c != '\n')
22582 .map(|c| c.len_utf8())
22583 .sum::<usize>();
22584 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22585
22586 language.brackets().any(|(pair, enabled)| {
22587 let pair_start = pair.start.trim_end();
22588 let pair_end = pair.end.trim_start();
22589
22590 enabled
22591 && pair.newline
22592 && buffer.contains_str_at(range.end, pair_end)
22593 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22594 })
22595}
22596
22597fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22598 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22599 [(buffer, range, _)] => (*buffer, range.clone()),
22600 _ => return false,
22601 };
22602 let pair = {
22603 let mut result: Option<BracketMatch> = None;
22604
22605 for pair in buffer
22606 .all_bracket_ranges(range.clone())
22607 .filter(move |pair| {
22608 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22609 })
22610 {
22611 let len = pair.close_range.end - pair.open_range.start;
22612
22613 if let Some(existing) = &result {
22614 let existing_len = existing.close_range.end - existing.open_range.start;
22615 if len > existing_len {
22616 continue;
22617 }
22618 }
22619
22620 result = Some(pair);
22621 }
22622
22623 result
22624 };
22625 let Some(pair) = pair else {
22626 return false;
22627 };
22628 pair.newline_only
22629 && buffer
22630 .chars_for_range(pair.open_range.end..range.start)
22631 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22632 .all(|c| c.is_whitespace() && c != '\n')
22633}
22634
22635fn update_uncommitted_diff_for_buffer(
22636 editor: Entity<Editor>,
22637 project: &Entity<Project>,
22638 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22639 buffer: Entity<MultiBuffer>,
22640 cx: &mut App,
22641) -> Task<()> {
22642 let mut tasks = Vec::new();
22643 project.update(cx, |project, cx| {
22644 for buffer in buffers {
22645 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22646 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22647 }
22648 }
22649 });
22650 cx.spawn(async move |cx| {
22651 let diffs = future::join_all(tasks).await;
22652 if editor
22653 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22654 .unwrap_or(false)
22655 {
22656 return;
22657 }
22658
22659 buffer
22660 .update(cx, |buffer, cx| {
22661 for diff in diffs.into_iter().flatten() {
22662 buffer.add_diff(diff, cx);
22663 }
22664 })
22665 .ok();
22666 })
22667}
22668
22669fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22670 let tab_size = tab_size.get() as usize;
22671 let mut width = offset;
22672
22673 for ch in text.chars() {
22674 width += if ch == '\t' {
22675 tab_size - (width % tab_size)
22676 } else {
22677 1
22678 };
22679 }
22680
22681 width - offset
22682}
22683
22684#[cfg(test)]
22685mod tests {
22686 use super::*;
22687
22688 #[test]
22689 fn test_string_size_with_expanded_tabs() {
22690 let nz = |val| NonZeroU32::new(val).unwrap();
22691 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22692 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22693 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22694 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22695 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22696 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22697 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22698 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22699 }
22700}
22701
22702/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22703struct WordBreakingTokenizer<'a> {
22704 input: &'a str,
22705}
22706
22707impl<'a> WordBreakingTokenizer<'a> {
22708 fn new(input: &'a str) -> Self {
22709 Self { input }
22710 }
22711}
22712
22713fn is_char_ideographic(ch: char) -> bool {
22714 use unicode_script::Script::*;
22715 use unicode_script::UnicodeScript;
22716 matches!(ch.script(), Han | Tangut | Yi)
22717}
22718
22719fn is_grapheme_ideographic(text: &str) -> bool {
22720 text.chars().any(is_char_ideographic)
22721}
22722
22723fn is_grapheme_whitespace(text: &str) -> bool {
22724 text.chars().any(|x| x.is_whitespace())
22725}
22726
22727fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22728 text.chars()
22729 .next()
22730 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22731}
22732
22733#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22734enum WordBreakToken<'a> {
22735 Word { token: &'a str, grapheme_len: usize },
22736 InlineWhitespace { token: &'a str, grapheme_len: usize },
22737 Newline,
22738}
22739
22740impl<'a> Iterator for WordBreakingTokenizer<'a> {
22741 /// Yields a span, the count of graphemes in the token, and whether it was
22742 /// whitespace. Note that it also breaks at word boundaries.
22743 type Item = WordBreakToken<'a>;
22744
22745 fn next(&mut self) -> Option<Self::Item> {
22746 use unicode_segmentation::UnicodeSegmentation;
22747 if self.input.is_empty() {
22748 return None;
22749 }
22750
22751 let mut iter = self.input.graphemes(true).peekable();
22752 let mut offset = 0;
22753 let mut grapheme_len = 0;
22754 if let Some(first_grapheme) = iter.next() {
22755 let is_newline = first_grapheme == "\n";
22756 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22757 offset += first_grapheme.len();
22758 grapheme_len += 1;
22759 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22760 if let Some(grapheme) = iter.peek().copied()
22761 && should_stay_with_preceding_ideograph(grapheme)
22762 {
22763 offset += grapheme.len();
22764 grapheme_len += 1;
22765 }
22766 } else {
22767 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22768 let mut next_word_bound = words.peek().copied();
22769 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22770 next_word_bound = words.next();
22771 }
22772 while let Some(grapheme) = iter.peek().copied() {
22773 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22774 break;
22775 };
22776 if is_grapheme_whitespace(grapheme) != is_whitespace
22777 || (grapheme == "\n") != is_newline
22778 {
22779 break;
22780 };
22781 offset += grapheme.len();
22782 grapheme_len += 1;
22783 iter.next();
22784 }
22785 }
22786 let token = &self.input[..offset];
22787 self.input = &self.input[offset..];
22788 if token == "\n" {
22789 Some(WordBreakToken::Newline)
22790 } else if is_whitespace {
22791 Some(WordBreakToken::InlineWhitespace {
22792 token,
22793 grapheme_len,
22794 })
22795 } else {
22796 Some(WordBreakToken::Word {
22797 token,
22798 grapheme_len,
22799 })
22800 }
22801 } else {
22802 None
22803 }
22804 }
22805}
22806
22807#[test]
22808fn test_word_breaking_tokenizer() {
22809 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22810 ("", &[]),
22811 (" ", &[whitespace(" ", 2)]),
22812 ("Ʒ", &[word("Ʒ", 1)]),
22813 ("Ǽ", &[word("Ǽ", 1)]),
22814 ("⋑", &[word("⋑", 1)]),
22815 ("⋑⋑", &[word("⋑⋑", 2)]),
22816 (
22817 "原理,进而",
22818 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22819 ),
22820 (
22821 "hello world",
22822 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22823 ),
22824 (
22825 "hello, world",
22826 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22827 ),
22828 (
22829 " hello world",
22830 &[
22831 whitespace(" ", 2),
22832 word("hello", 5),
22833 whitespace(" ", 1),
22834 word("world", 5),
22835 ],
22836 ),
22837 (
22838 "这是什么 \n 钢笔",
22839 &[
22840 word("这", 1),
22841 word("是", 1),
22842 word("什", 1),
22843 word("么", 1),
22844 whitespace(" ", 1),
22845 newline(),
22846 whitespace(" ", 1),
22847 word("钢", 1),
22848 word("笔", 1),
22849 ],
22850 ),
22851 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22852 ];
22853
22854 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22855 WordBreakToken::Word {
22856 token,
22857 grapheme_len,
22858 }
22859 }
22860
22861 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22862 WordBreakToken::InlineWhitespace {
22863 token,
22864 grapheme_len,
22865 }
22866 }
22867
22868 fn newline() -> WordBreakToken<'static> {
22869 WordBreakToken::Newline
22870 }
22871
22872 for (input, result) in tests {
22873 assert_eq!(
22874 WordBreakingTokenizer::new(input)
22875 .collect::<Vec<_>>()
22876 .as_slice(),
22877 *result,
22878 );
22879 }
22880}
22881
22882fn wrap_with_prefix(
22883 first_line_prefix: String,
22884 subsequent_lines_prefix: String,
22885 unwrapped_text: String,
22886 wrap_column: usize,
22887 tab_size: NonZeroU32,
22888 preserve_existing_whitespace: bool,
22889) -> String {
22890 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22891 let subsequent_lines_prefix_len =
22892 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22893 let mut wrapped_text = String::new();
22894 let mut current_line = first_line_prefix;
22895 let mut is_first_line = true;
22896
22897 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22898 let mut current_line_len = first_line_prefix_len;
22899 let mut in_whitespace = false;
22900 for token in tokenizer {
22901 let have_preceding_whitespace = in_whitespace;
22902 match token {
22903 WordBreakToken::Word {
22904 token,
22905 grapheme_len,
22906 } => {
22907 in_whitespace = false;
22908 let current_prefix_len = if is_first_line {
22909 first_line_prefix_len
22910 } else {
22911 subsequent_lines_prefix_len
22912 };
22913 if current_line_len + grapheme_len > wrap_column
22914 && current_line_len != current_prefix_len
22915 {
22916 wrapped_text.push_str(current_line.trim_end());
22917 wrapped_text.push('\n');
22918 is_first_line = false;
22919 current_line = subsequent_lines_prefix.clone();
22920 current_line_len = subsequent_lines_prefix_len;
22921 }
22922 current_line.push_str(token);
22923 current_line_len += grapheme_len;
22924 }
22925 WordBreakToken::InlineWhitespace {
22926 mut token,
22927 mut grapheme_len,
22928 } => {
22929 in_whitespace = true;
22930 if have_preceding_whitespace && !preserve_existing_whitespace {
22931 continue;
22932 }
22933 if !preserve_existing_whitespace {
22934 // Keep a single whitespace grapheme as-is
22935 if let Some(first) =
22936 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22937 {
22938 token = first;
22939 } else {
22940 token = " ";
22941 }
22942 grapheme_len = 1;
22943 }
22944 let current_prefix_len = if is_first_line {
22945 first_line_prefix_len
22946 } else {
22947 subsequent_lines_prefix_len
22948 };
22949 if current_line_len + grapheme_len > wrap_column {
22950 wrapped_text.push_str(current_line.trim_end());
22951 wrapped_text.push('\n');
22952 is_first_line = false;
22953 current_line = subsequent_lines_prefix.clone();
22954 current_line_len = subsequent_lines_prefix_len;
22955 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22956 current_line.push_str(token);
22957 current_line_len += grapheme_len;
22958 }
22959 }
22960 WordBreakToken::Newline => {
22961 in_whitespace = true;
22962 let current_prefix_len = if is_first_line {
22963 first_line_prefix_len
22964 } else {
22965 subsequent_lines_prefix_len
22966 };
22967 if preserve_existing_whitespace {
22968 wrapped_text.push_str(current_line.trim_end());
22969 wrapped_text.push('\n');
22970 is_first_line = false;
22971 current_line = subsequent_lines_prefix.clone();
22972 current_line_len = subsequent_lines_prefix_len;
22973 } else if have_preceding_whitespace {
22974 continue;
22975 } else if current_line_len + 1 > wrap_column
22976 && current_line_len != current_prefix_len
22977 {
22978 wrapped_text.push_str(current_line.trim_end());
22979 wrapped_text.push('\n');
22980 is_first_line = false;
22981 current_line = subsequent_lines_prefix.clone();
22982 current_line_len = subsequent_lines_prefix_len;
22983 } else if current_line_len != current_prefix_len {
22984 current_line.push(' ');
22985 current_line_len += 1;
22986 }
22987 }
22988 }
22989 }
22990
22991 if !current_line.is_empty() {
22992 wrapped_text.push_str(¤t_line);
22993 }
22994 wrapped_text
22995}
22996
22997#[test]
22998fn test_wrap_with_prefix() {
22999 assert_eq!(
23000 wrap_with_prefix(
23001 "# ".to_string(),
23002 "# ".to_string(),
23003 "abcdefg".to_string(),
23004 4,
23005 NonZeroU32::new(4).unwrap(),
23006 false,
23007 ),
23008 "# abcdefg"
23009 );
23010 assert_eq!(
23011 wrap_with_prefix(
23012 "".to_string(),
23013 "".to_string(),
23014 "\thello world".to_string(),
23015 8,
23016 NonZeroU32::new(4).unwrap(),
23017 false,
23018 ),
23019 "hello\nworld"
23020 );
23021 assert_eq!(
23022 wrap_with_prefix(
23023 "// ".to_string(),
23024 "// ".to_string(),
23025 "xx \nyy zz aa bb cc".to_string(),
23026 12,
23027 NonZeroU32::new(4).unwrap(),
23028 false,
23029 ),
23030 "// xx yy zz\n// aa bb cc"
23031 );
23032 assert_eq!(
23033 wrap_with_prefix(
23034 String::new(),
23035 String::new(),
23036 "这是什么 \n 钢笔".to_string(),
23037 3,
23038 NonZeroU32::new(4).unwrap(),
23039 false,
23040 ),
23041 "这是什\n么 钢\n笔"
23042 );
23043 assert_eq!(
23044 wrap_with_prefix(
23045 String::new(),
23046 String::new(),
23047 format!("foo{}bar", '\u{2009}'), // thin space
23048 80,
23049 NonZeroU32::new(4).unwrap(),
23050 false,
23051 ),
23052 format!("foo{}bar", '\u{2009}')
23053 );
23054}
23055
23056pub trait CollaborationHub {
23057 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23058 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23059 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23060}
23061
23062impl CollaborationHub for Entity<Project> {
23063 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23064 self.read(cx).collaborators()
23065 }
23066
23067 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23068 self.read(cx).user_store().read(cx).participant_indices()
23069 }
23070
23071 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23072 let this = self.read(cx);
23073 let user_ids = this.collaborators().values().map(|c| c.user_id);
23074 this.user_store().read(cx).participant_names(user_ids, cx)
23075 }
23076}
23077
23078pub trait SemanticsProvider {
23079 fn hover(
23080 &self,
23081 buffer: &Entity<Buffer>,
23082 position: text::Anchor,
23083 cx: &mut App,
23084 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23085
23086 fn inline_values(
23087 &self,
23088 buffer_handle: Entity<Buffer>,
23089 range: Range<text::Anchor>,
23090 cx: &mut App,
23091 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23092
23093 fn applicable_inlay_chunks(
23094 &self,
23095 buffer: &Entity<Buffer>,
23096 ranges: &[Range<text::Anchor>],
23097 cx: &mut App,
23098 ) -> Vec<Range<BufferRow>>;
23099
23100 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23101
23102 fn inlay_hints(
23103 &self,
23104 invalidate: InvalidationStrategy,
23105 buffer: Entity<Buffer>,
23106 ranges: Vec<Range<text::Anchor>>,
23107 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23108 cx: &mut App,
23109 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23110
23111 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23112
23113 fn document_highlights(
23114 &self,
23115 buffer: &Entity<Buffer>,
23116 position: text::Anchor,
23117 cx: &mut App,
23118 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23119
23120 fn definitions(
23121 &self,
23122 buffer: &Entity<Buffer>,
23123 position: text::Anchor,
23124 kind: GotoDefinitionKind,
23125 cx: &mut App,
23126 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23127
23128 fn range_for_rename(
23129 &self,
23130 buffer: &Entity<Buffer>,
23131 position: text::Anchor,
23132 cx: &mut App,
23133 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23134
23135 fn perform_rename(
23136 &self,
23137 buffer: &Entity<Buffer>,
23138 position: text::Anchor,
23139 new_name: String,
23140 cx: &mut App,
23141 ) -> Option<Task<Result<ProjectTransaction>>>;
23142}
23143
23144pub trait CompletionProvider {
23145 fn completions(
23146 &self,
23147 excerpt_id: ExcerptId,
23148 buffer: &Entity<Buffer>,
23149 buffer_position: text::Anchor,
23150 trigger: CompletionContext,
23151 window: &mut Window,
23152 cx: &mut Context<Editor>,
23153 ) -> Task<Result<Vec<CompletionResponse>>>;
23154
23155 fn resolve_completions(
23156 &self,
23157 _buffer: Entity<Buffer>,
23158 _completion_indices: Vec<usize>,
23159 _completions: Rc<RefCell<Box<[Completion]>>>,
23160 _cx: &mut Context<Editor>,
23161 ) -> Task<Result<bool>> {
23162 Task::ready(Ok(false))
23163 }
23164
23165 fn apply_additional_edits_for_completion(
23166 &self,
23167 _buffer: Entity<Buffer>,
23168 _completions: Rc<RefCell<Box<[Completion]>>>,
23169 _completion_index: usize,
23170 _push_to_history: bool,
23171 _cx: &mut Context<Editor>,
23172 ) -> Task<Result<Option<language::Transaction>>> {
23173 Task::ready(Ok(None))
23174 }
23175
23176 fn is_completion_trigger(
23177 &self,
23178 buffer: &Entity<Buffer>,
23179 position: language::Anchor,
23180 text: &str,
23181 trigger_in_words: bool,
23182 menu_is_open: bool,
23183 cx: &mut Context<Editor>,
23184 ) -> bool;
23185
23186 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23187
23188 fn sort_completions(&self) -> bool {
23189 true
23190 }
23191
23192 fn filter_completions(&self) -> bool {
23193 true
23194 }
23195
23196 fn show_snippets(&self) -> bool {
23197 false
23198 }
23199}
23200
23201pub trait CodeActionProvider {
23202 fn id(&self) -> Arc<str>;
23203
23204 fn code_actions(
23205 &self,
23206 buffer: &Entity<Buffer>,
23207 range: Range<text::Anchor>,
23208 window: &mut Window,
23209 cx: &mut App,
23210 ) -> Task<Result<Vec<CodeAction>>>;
23211
23212 fn apply_code_action(
23213 &self,
23214 buffer_handle: Entity<Buffer>,
23215 action: CodeAction,
23216 excerpt_id: ExcerptId,
23217 push_to_history: bool,
23218 window: &mut Window,
23219 cx: &mut App,
23220 ) -> Task<Result<ProjectTransaction>>;
23221}
23222
23223impl CodeActionProvider for Entity<Project> {
23224 fn id(&self) -> Arc<str> {
23225 "project".into()
23226 }
23227
23228 fn code_actions(
23229 &self,
23230 buffer: &Entity<Buffer>,
23231 range: Range<text::Anchor>,
23232 _window: &mut Window,
23233 cx: &mut App,
23234 ) -> Task<Result<Vec<CodeAction>>> {
23235 self.update(cx, |project, cx| {
23236 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23237 let code_actions = project.code_actions(buffer, range, None, cx);
23238 cx.background_spawn(async move {
23239 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23240 Ok(code_lens_actions
23241 .context("code lens fetch")?
23242 .into_iter()
23243 .flatten()
23244 .chain(
23245 code_actions
23246 .context("code action fetch")?
23247 .into_iter()
23248 .flatten(),
23249 )
23250 .collect())
23251 })
23252 })
23253 }
23254
23255 fn apply_code_action(
23256 &self,
23257 buffer_handle: Entity<Buffer>,
23258 action: CodeAction,
23259 _excerpt_id: ExcerptId,
23260 push_to_history: bool,
23261 _window: &mut Window,
23262 cx: &mut App,
23263 ) -> Task<Result<ProjectTransaction>> {
23264 self.update(cx, |project, cx| {
23265 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23266 })
23267 }
23268}
23269
23270fn snippet_completions(
23271 project: &Project,
23272 buffer: &Entity<Buffer>,
23273 buffer_anchor: text::Anchor,
23274 classifier: CharClassifier,
23275 cx: &mut App,
23276) -> Task<Result<CompletionResponse>> {
23277 let languages = buffer.read(cx).languages_at(buffer_anchor);
23278 let snippet_store = project.snippets().read(cx);
23279
23280 let scopes: Vec<_> = languages
23281 .iter()
23282 .filter_map(|language| {
23283 let language_name = language.lsp_id();
23284 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23285
23286 if snippets.is_empty() {
23287 None
23288 } else {
23289 Some((language.default_scope(), snippets))
23290 }
23291 })
23292 .collect();
23293
23294 if scopes.is_empty() {
23295 return Task::ready(Ok(CompletionResponse {
23296 completions: vec![],
23297 display_options: CompletionDisplayOptions::default(),
23298 is_incomplete: false,
23299 }));
23300 }
23301
23302 let snapshot = buffer.read(cx).text_snapshot();
23303 let executor = cx.background_executor().clone();
23304
23305 cx.background_spawn(async move {
23306 let is_word_char = |c| classifier.is_word(c);
23307
23308 let mut is_incomplete = false;
23309 let mut completions: Vec<Completion> = Vec::new();
23310
23311 const MAX_PREFIX_LEN: usize = 128;
23312 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
23313 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
23314 let window_start = snapshot.clip_offset(window_start, Bias::Left);
23315
23316 let max_buffer_window: String = snapshot
23317 .text_for_range(window_start..buffer_offset)
23318 .collect();
23319
23320 if max_buffer_window.is_empty() {
23321 return Ok(CompletionResponse {
23322 completions: vec![],
23323 display_options: CompletionDisplayOptions::default(),
23324 is_incomplete: true,
23325 });
23326 }
23327
23328 for (_scope, snippets) in scopes.into_iter() {
23329 // Sort snippets by word count to match longer snippet prefixes first.
23330 let mut sorted_snippet_candidates = snippets
23331 .iter()
23332 .enumerate()
23333 .flat_map(|(snippet_ix, snippet)| {
23334 snippet
23335 .prefix
23336 .iter()
23337 .enumerate()
23338 .map(move |(prefix_ix, prefix)| {
23339 let word_count =
23340 snippet_candidate_suffixes(prefix, is_word_char).count();
23341 ((snippet_ix, prefix_ix), prefix, word_count)
23342 })
23343 })
23344 .collect_vec();
23345 sorted_snippet_candidates
23346 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
23347
23348 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
23349
23350 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
23351 .take(
23352 sorted_snippet_candidates
23353 .first()
23354 .map(|(_, _, word_count)| *word_count)
23355 .unwrap_or_default(),
23356 )
23357 .collect_vec();
23358
23359 const MAX_RESULTS: usize = 100;
23360 // Each match also remembers how many characters from the buffer it consumed
23361 let mut matches: Vec<(StringMatch, usize)> = vec![];
23362
23363 let mut snippet_list_cutoff_index = 0;
23364 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
23365 let word_count = buffer_index + 1;
23366 // Increase `snippet_list_cutoff_index` until we have all of the
23367 // snippets with sufficiently many words.
23368 while sorted_snippet_candidates
23369 .get(snippet_list_cutoff_index)
23370 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
23371 *snippet_word_count >= word_count
23372 })
23373 {
23374 snippet_list_cutoff_index += 1;
23375 }
23376
23377 // Take only the candidates with at least `word_count` many words
23378 let snippet_candidates_at_word_len =
23379 &sorted_snippet_candidates[..snippet_list_cutoff_index];
23380
23381 let candidates = snippet_candidates_at_word_len
23382 .iter()
23383 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
23384 .enumerate() // index in `sorted_snippet_candidates`
23385 // First char must match
23386 .filter(|(_ix, prefix)| {
23387 itertools::equal(
23388 prefix
23389 .chars()
23390 .next()
23391 .into_iter()
23392 .flat_map(|c| c.to_lowercase()),
23393 buffer_window
23394 .chars()
23395 .next()
23396 .into_iter()
23397 .flat_map(|c| c.to_lowercase()),
23398 )
23399 })
23400 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
23401 .collect::<Vec<StringMatchCandidate>>();
23402
23403 matches.extend(
23404 fuzzy::match_strings(
23405 &candidates,
23406 &buffer_window,
23407 buffer_window.chars().any(|c| c.is_uppercase()),
23408 true,
23409 MAX_RESULTS - matches.len(), // always prioritize longer snippets
23410 &Default::default(),
23411 executor.clone(),
23412 )
23413 .await
23414 .into_iter()
23415 .map(|string_match| (string_match, buffer_window.len())),
23416 );
23417
23418 if matches.len() >= MAX_RESULTS {
23419 break;
23420 }
23421 }
23422
23423 let to_lsp = |point: &text::Anchor| {
23424 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23425 point_to_lsp(end)
23426 };
23427 let lsp_end = to_lsp(&buffer_anchor);
23428
23429 if matches.len() >= MAX_RESULTS {
23430 is_incomplete = true;
23431 }
23432
23433 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
23434 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
23435 sorted_snippet_candidates[string_match.candidate_id];
23436 let snippet = &snippets[snippet_index];
23437 let start = buffer_offset - buffer_window_len;
23438 let start = snapshot.anchor_before(start);
23439 let range = start..buffer_anchor;
23440 let lsp_start = to_lsp(&start);
23441 let lsp_range = lsp::Range {
23442 start: lsp_start,
23443 end: lsp_end,
23444 };
23445 Completion {
23446 replace_range: range,
23447 new_text: snippet.body.clone(),
23448 source: CompletionSource::Lsp {
23449 insert_range: None,
23450 server_id: LanguageServerId(usize::MAX),
23451 resolved: true,
23452 lsp_completion: Box::new(lsp::CompletionItem {
23453 label: snippet.prefix.first().unwrap().clone(),
23454 kind: Some(CompletionItemKind::SNIPPET),
23455 label_details: snippet.description.as_ref().map(|description| {
23456 lsp::CompletionItemLabelDetails {
23457 detail: Some(description.clone()),
23458 description: None,
23459 }
23460 }),
23461 insert_text_format: Some(InsertTextFormat::SNIPPET),
23462 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23463 lsp::InsertReplaceEdit {
23464 new_text: snippet.body.clone(),
23465 insert: lsp_range,
23466 replace: lsp_range,
23467 },
23468 )),
23469 filter_text: Some(snippet.body.clone()),
23470 sort_text: Some(char::MAX.to_string()),
23471 ..lsp::CompletionItem::default()
23472 }),
23473 lsp_defaults: None,
23474 },
23475 label: CodeLabel {
23476 text: matching_prefix.clone(),
23477 runs: Vec::new(),
23478 filter_range: 0..matching_prefix.len(),
23479 },
23480 icon_path: None,
23481 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23482 single_line: snippet.name.clone().into(),
23483 plain_text: snippet
23484 .description
23485 .clone()
23486 .map(|description| description.into()),
23487 }),
23488 insert_text_mode: None,
23489 confirm: None,
23490 match_start: Some(start),
23491 snippet_deduplication_key: Some((snippet_index, prefix_index)),
23492 }
23493 }));
23494 }
23495
23496 Ok(CompletionResponse {
23497 completions,
23498 display_options: CompletionDisplayOptions::default(),
23499 is_incomplete,
23500 })
23501 })
23502}
23503
23504impl CompletionProvider for Entity<Project> {
23505 fn completions(
23506 &self,
23507 _excerpt_id: ExcerptId,
23508 buffer: &Entity<Buffer>,
23509 buffer_position: text::Anchor,
23510 options: CompletionContext,
23511 _window: &mut Window,
23512 cx: &mut Context<Editor>,
23513 ) -> Task<Result<Vec<CompletionResponse>>> {
23514 self.update(cx, |project, cx| {
23515 let task = project.completions(buffer, buffer_position, options, cx);
23516 cx.background_spawn(task)
23517 })
23518 }
23519
23520 fn resolve_completions(
23521 &self,
23522 buffer: Entity<Buffer>,
23523 completion_indices: Vec<usize>,
23524 completions: Rc<RefCell<Box<[Completion]>>>,
23525 cx: &mut Context<Editor>,
23526 ) -> Task<Result<bool>> {
23527 self.update(cx, |project, cx| {
23528 project.lsp_store().update(cx, |lsp_store, cx| {
23529 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23530 })
23531 })
23532 }
23533
23534 fn apply_additional_edits_for_completion(
23535 &self,
23536 buffer: Entity<Buffer>,
23537 completions: Rc<RefCell<Box<[Completion]>>>,
23538 completion_index: usize,
23539 push_to_history: bool,
23540 cx: &mut Context<Editor>,
23541 ) -> Task<Result<Option<language::Transaction>>> {
23542 self.update(cx, |project, cx| {
23543 project.lsp_store().update(cx, |lsp_store, cx| {
23544 lsp_store.apply_additional_edits_for_completion(
23545 buffer,
23546 completions,
23547 completion_index,
23548 push_to_history,
23549 cx,
23550 )
23551 })
23552 })
23553 }
23554
23555 fn is_completion_trigger(
23556 &self,
23557 buffer: &Entity<Buffer>,
23558 position: language::Anchor,
23559 text: &str,
23560 trigger_in_words: bool,
23561 menu_is_open: bool,
23562 cx: &mut Context<Editor>,
23563 ) -> bool {
23564 let mut chars = text.chars();
23565 let char = if let Some(char) = chars.next() {
23566 char
23567 } else {
23568 return false;
23569 };
23570 if chars.next().is_some() {
23571 return false;
23572 }
23573
23574 let buffer = buffer.read(cx);
23575 let snapshot = buffer.snapshot();
23576 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23577 return false;
23578 }
23579 let classifier = snapshot
23580 .char_classifier_at(position)
23581 .scope_context(Some(CharScopeContext::Completion));
23582 if trigger_in_words && classifier.is_word(char) {
23583 return true;
23584 }
23585
23586 buffer.completion_triggers().contains(text)
23587 }
23588
23589 fn show_snippets(&self) -> bool {
23590 true
23591 }
23592}
23593
23594impl SemanticsProvider for Entity<Project> {
23595 fn hover(
23596 &self,
23597 buffer: &Entity<Buffer>,
23598 position: text::Anchor,
23599 cx: &mut App,
23600 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23601 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23602 }
23603
23604 fn document_highlights(
23605 &self,
23606 buffer: &Entity<Buffer>,
23607 position: text::Anchor,
23608 cx: &mut App,
23609 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23610 Some(self.update(cx, |project, cx| {
23611 project.document_highlights(buffer, position, cx)
23612 }))
23613 }
23614
23615 fn definitions(
23616 &self,
23617 buffer: &Entity<Buffer>,
23618 position: text::Anchor,
23619 kind: GotoDefinitionKind,
23620 cx: &mut App,
23621 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23622 Some(self.update(cx, |project, cx| match kind {
23623 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23624 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23625 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23626 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23627 }))
23628 }
23629
23630 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23631 self.update(cx, |project, cx| {
23632 if project
23633 .active_debug_session(cx)
23634 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23635 {
23636 return true;
23637 }
23638
23639 buffer.update(cx, |buffer, cx| {
23640 project.any_language_server_supports_inlay_hints(buffer, cx)
23641 })
23642 })
23643 }
23644
23645 fn inline_values(
23646 &self,
23647 buffer_handle: Entity<Buffer>,
23648 range: Range<text::Anchor>,
23649 cx: &mut App,
23650 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23651 self.update(cx, |project, cx| {
23652 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23653
23654 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23655 })
23656 }
23657
23658 fn applicable_inlay_chunks(
23659 &self,
23660 buffer: &Entity<Buffer>,
23661 ranges: &[Range<text::Anchor>],
23662 cx: &mut App,
23663 ) -> Vec<Range<BufferRow>> {
23664 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23665 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23666 })
23667 }
23668
23669 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23670 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23671 lsp_store.invalidate_inlay_hints(for_buffers)
23672 });
23673 }
23674
23675 fn inlay_hints(
23676 &self,
23677 invalidate: InvalidationStrategy,
23678 buffer: Entity<Buffer>,
23679 ranges: Vec<Range<text::Anchor>>,
23680 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23681 cx: &mut App,
23682 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23683 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23684 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23685 }))
23686 }
23687
23688 fn range_for_rename(
23689 &self,
23690 buffer: &Entity<Buffer>,
23691 position: text::Anchor,
23692 cx: &mut App,
23693 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23694 Some(self.update(cx, |project, cx| {
23695 let buffer = buffer.clone();
23696 let task = project.prepare_rename(buffer.clone(), position, cx);
23697 cx.spawn(async move |_, cx| {
23698 Ok(match task.await? {
23699 PrepareRenameResponse::Success(range) => Some(range),
23700 PrepareRenameResponse::InvalidPosition => None,
23701 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23702 // Fallback on using TreeSitter info to determine identifier range
23703 buffer.read_with(cx, |buffer, _| {
23704 let snapshot = buffer.snapshot();
23705 let (range, kind) = snapshot.surrounding_word(position, None);
23706 if kind != Some(CharKind::Word) {
23707 return None;
23708 }
23709 Some(
23710 snapshot.anchor_before(range.start)
23711 ..snapshot.anchor_after(range.end),
23712 )
23713 })?
23714 }
23715 })
23716 })
23717 }))
23718 }
23719
23720 fn perform_rename(
23721 &self,
23722 buffer: &Entity<Buffer>,
23723 position: text::Anchor,
23724 new_name: String,
23725 cx: &mut App,
23726 ) -> Option<Task<Result<ProjectTransaction>>> {
23727 Some(self.update(cx, |project, cx| {
23728 project.perform_rename(buffer.clone(), position, new_name, cx)
23729 }))
23730 }
23731}
23732
23733fn consume_contiguous_rows(
23734 contiguous_row_selections: &mut Vec<Selection<Point>>,
23735 selection: &Selection<Point>,
23736 display_map: &DisplaySnapshot,
23737 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23738) -> (MultiBufferRow, MultiBufferRow) {
23739 contiguous_row_selections.push(selection.clone());
23740 let start_row = starting_row(selection, display_map);
23741 let mut end_row = ending_row(selection, display_map);
23742
23743 while let Some(next_selection) = selections.peek() {
23744 if next_selection.start.row <= end_row.0 {
23745 end_row = ending_row(next_selection, display_map);
23746 contiguous_row_selections.push(selections.next().unwrap().clone());
23747 } else {
23748 break;
23749 }
23750 }
23751 (start_row, end_row)
23752}
23753
23754fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23755 if selection.start.column > 0 {
23756 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23757 } else {
23758 MultiBufferRow(selection.start.row)
23759 }
23760}
23761
23762fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23763 if next_selection.end.column > 0 || next_selection.is_empty() {
23764 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23765 } else {
23766 MultiBufferRow(next_selection.end.row)
23767 }
23768}
23769
23770impl EditorSnapshot {
23771 pub fn remote_selections_in_range<'a>(
23772 &'a self,
23773 range: &'a Range<Anchor>,
23774 collaboration_hub: &dyn CollaborationHub,
23775 cx: &'a App,
23776 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23777 let participant_names = collaboration_hub.user_names(cx);
23778 let participant_indices = collaboration_hub.user_participant_indices(cx);
23779 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23780 let collaborators_by_replica_id = collaborators_by_peer_id
23781 .values()
23782 .map(|collaborator| (collaborator.replica_id, collaborator))
23783 .collect::<HashMap<_, _>>();
23784 self.buffer_snapshot()
23785 .selections_in_range(range, false)
23786 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23787 if replica_id == ReplicaId::AGENT {
23788 Some(RemoteSelection {
23789 replica_id,
23790 selection,
23791 cursor_shape,
23792 line_mode,
23793 collaborator_id: CollaboratorId::Agent,
23794 user_name: Some("Agent".into()),
23795 color: cx.theme().players().agent(),
23796 })
23797 } else {
23798 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23799 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23800 let user_name = participant_names.get(&collaborator.user_id).cloned();
23801 Some(RemoteSelection {
23802 replica_id,
23803 selection,
23804 cursor_shape,
23805 line_mode,
23806 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23807 user_name,
23808 color: if let Some(index) = participant_index {
23809 cx.theme().players().color_for_participant(index.0)
23810 } else {
23811 cx.theme().players().absent()
23812 },
23813 })
23814 }
23815 })
23816 }
23817
23818 pub fn hunks_for_ranges(
23819 &self,
23820 ranges: impl IntoIterator<Item = Range<Point>>,
23821 ) -> Vec<MultiBufferDiffHunk> {
23822 let mut hunks = Vec::new();
23823 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23824 HashMap::default();
23825 for query_range in ranges {
23826 let query_rows =
23827 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23828 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23829 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23830 ) {
23831 // Include deleted hunks that are adjacent to the query range, because
23832 // otherwise they would be missed.
23833 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23834 if hunk.status().is_deleted() {
23835 intersects_range |= hunk.row_range.start == query_rows.end;
23836 intersects_range |= hunk.row_range.end == query_rows.start;
23837 }
23838 if intersects_range {
23839 if !processed_buffer_rows
23840 .entry(hunk.buffer_id)
23841 .or_default()
23842 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23843 {
23844 continue;
23845 }
23846 hunks.push(hunk);
23847 }
23848 }
23849 }
23850
23851 hunks
23852 }
23853
23854 fn display_diff_hunks_for_rows<'a>(
23855 &'a self,
23856 display_rows: Range<DisplayRow>,
23857 folded_buffers: &'a HashSet<BufferId>,
23858 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23859 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23860 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23861
23862 self.buffer_snapshot()
23863 .diff_hunks_in_range(buffer_start..buffer_end)
23864 .filter_map(|hunk| {
23865 if folded_buffers.contains(&hunk.buffer_id) {
23866 return None;
23867 }
23868
23869 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23870 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23871
23872 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23873 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23874
23875 let display_hunk = if hunk_display_start.column() != 0 {
23876 DisplayDiffHunk::Folded {
23877 display_row: hunk_display_start.row(),
23878 }
23879 } else {
23880 let mut end_row = hunk_display_end.row();
23881 if hunk_display_end.column() > 0 {
23882 end_row.0 += 1;
23883 }
23884 let is_created_file = hunk.is_created_file();
23885 DisplayDiffHunk::Unfolded {
23886 status: hunk.status(),
23887 diff_base_byte_range: hunk.diff_base_byte_range,
23888 display_row_range: hunk_display_start.row()..end_row,
23889 multi_buffer_range: Anchor::range_in_buffer(
23890 hunk.excerpt_id,
23891 hunk.buffer_id,
23892 hunk.buffer_range,
23893 ),
23894 is_created_file,
23895 }
23896 };
23897
23898 Some(display_hunk)
23899 })
23900 }
23901
23902 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23903 self.display_snapshot
23904 .buffer_snapshot()
23905 .language_at(position)
23906 }
23907
23908 pub fn is_focused(&self) -> bool {
23909 self.is_focused
23910 }
23911
23912 pub fn placeholder_text(&self) -> Option<String> {
23913 self.placeholder_display_snapshot
23914 .as_ref()
23915 .map(|display_map| display_map.text())
23916 }
23917
23918 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23919 self.scroll_anchor.scroll_position(&self.display_snapshot)
23920 }
23921
23922 fn gutter_dimensions(
23923 &self,
23924 font_id: FontId,
23925 font_size: Pixels,
23926 max_line_number_width: Pixels,
23927 cx: &App,
23928 ) -> Option<GutterDimensions> {
23929 if !self.show_gutter {
23930 return None;
23931 }
23932
23933 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23934 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23935
23936 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23937 matches!(
23938 ProjectSettings::get_global(cx).git.git_gutter,
23939 GitGutterSetting::TrackedFiles
23940 )
23941 });
23942 let gutter_settings = EditorSettings::get_global(cx).gutter;
23943 let show_line_numbers = self
23944 .show_line_numbers
23945 .unwrap_or(gutter_settings.line_numbers);
23946 let line_gutter_width = if show_line_numbers {
23947 // Avoid flicker-like gutter resizes when the line number gains another digit by
23948 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23949 let min_width_for_number_on_gutter =
23950 ch_advance * gutter_settings.min_line_number_digits as f32;
23951 max_line_number_width.max(min_width_for_number_on_gutter)
23952 } else {
23953 0.0.into()
23954 };
23955
23956 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23957 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23958
23959 let git_blame_entries_width =
23960 self.git_blame_gutter_max_author_length
23961 .map(|max_author_length| {
23962 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23963 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23964
23965 /// The number of characters to dedicate to gaps and margins.
23966 const SPACING_WIDTH: usize = 4;
23967
23968 let max_char_count = max_author_length.min(renderer.max_author_length())
23969 + ::git::SHORT_SHA_LENGTH
23970 + MAX_RELATIVE_TIMESTAMP.len()
23971 + SPACING_WIDTH;
23972
23973 ch_advance * max_char_count
23974 });
23975
23976 let is_singleton = self.buffer_snapshot().is_singleton();
23977
23978 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23979 left_padding += if !is_singleton {
23980 ch_width * 4.0
23981 } else if show_runnables || show_breakpoints {
23982 ch_width * 3.0
23983 } else if show_git_gutter && show_line_numbers {
23984 ch_width * 2.0
23985 } else if show_git_gutter || show_line_numbers {
23986 ch_width
23987 } else {
23988 px(0.)
23989 };
23990
23991 let shows_folds = is_singleton && gutter_settings.folds;
23992
23993 let right_padding = if shows_folds && show_line_numbers {
23994 ch_width * 4.0
23995 } else if shows_folds || (!is_singleton && show_line_numbers) {
23996 ch_width * 3.0
23997 } else if show_line_numbers {
23998 ch_width
23999 } else {
24000 px(0.)
24001 };
24002
24003 Some(GutterDimensions {
24004 left_padding,
24005 right_padding,
24006 width: line_gutter_width + left_padding + right_padding,
24007 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24008 git_blame_entries_width,
24009 })
24010 }
24011
24012 pub fn render_crease_toggle(
24013 &self,
24014 buffer_row: MultiBufferRow,
24015 row_contains_cursor: bool,
24016 editor: Entity<Editor>,
24017 window: &mut Window,
24018 cx: &mut App,
24019 ) -> Option<AnyElement> {
24020 let folded = self.is_line_folded(buffer_row);
24021 let mut is_foldable = false;
24022
24023 if let Some(crease) = self
24024 .crease_snapshot
24025 .query_row(buffer_row, self.buffer_snapshot())
24026 {
24027 is_foldable = true;
24028 match crease {
24029 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24030 if let Some(render_toggle) = render_toggle {
24031 let toggle_callback =
24032 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24033 if folded {
24034 editor.update(cx, |editor, cx| {
24035 editor.fold_at(buffer_row, window, cx)
24036 });
24037 } else {
24038 editor.update(cx, |editor, cx| {
24039 editor.unfold_at(buffer_row, window, cx)
24040 });
24041 }
24042 });
24043 return Some((render_toggle)(
24044 buffer_row,
24045 folded,
24046 toggle_callback,
24047 window,
24048 cx,
24049 ));
24050 }
24051 }
24052 }
24053 }
24054
24055 is_foldable |= self.starts_indent(buffer_row);
24056
24057 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24058 Some(
24059 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24060 .toggle_state(folded)
24061 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24062 if folded {
24063 this.unfold_at(buffer_row, window, cx);
24064 } else {
24065 this.fold_at(buffer_row, window, cx);
24066 }
24067 }))
24068 .into_any_element(),
24069 )
24070 } else {
24071 None
24072 }
24073 }
24074
24075 pub fn render_crease_trailer(
24076 &self,
24077 buffer_row: MultiBufferRow,
24078 window: &mut Window,
24079 cx: &mut App,
24080 ) -> Option<AnyElement> {
24081 let folded = self.is_line_folded(buffer_row);
24082 if let Crease::Inline { render_trailer, .. } = self
24083 .crease_snapshot
24084 .query_row(buffer_row, self.buffer_snapshot())?
24085 {
24086 let render_trailer = render_trailer.as_ref()?;
24087 Some(render_trailer(buffer_row, folded, window, cx))
24088 } else {
24089 None
24090 }
24091 }
24092}
24093
24094impl Deref for EditorSnapshot {
24095 type Target = DisplaySnapshot;
24096
24097 fn deref(&self) -> &Self::Target {
24098 &self.display_snapshot
24099 }
24100}
24101
24102#[derive(Clone, Debug, PartialEq, Eq)]
24103pub enum EditorEvent {
24104 InputIgnored {
24105 text: Arc<str>,
24106 },
24107 InputHandled {
24108 utf16_range_to_replace: Option<Range<isize>>,
24109 text: Arc<str>,
24110 },
24111 ExcerptsAdded {
24112 buffer: Entity<Buffer>,
24113 predecessor: ExcerptId,
24114 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
24115 },
24116 ExcerptsRemoved {
24117 ids: Vec<ExcerptId>,
24118 removed_buffer_ids: Vec<BufferId>,
24119 },
24120 BufferFoldToggled {
24121 ids: Vec<ExcerptId>,
24122 folded: bool,
24123 },
24124 ExcerptsEdited {
24125 ids: Vec<ExcerptId>,
24126 },
24127 ExcerptsExpanded {
24128 ids: Vec<ExcerptId>,
24129 },
24130 BufferEdited,
24131 Edited {
24132 transaction_id: clock::Lamport,
24133 },
24134 Reparsed(BufferId),
24135 Focused,
24136 FocusedIn,
24137 Blurred,
24138 DirtyChanged,
24139 Saved,
24140 TitleChanged,
24141 SelectionsChanged {
24142 local: bool,
24143 },
24144 ScrollPositionChanged {
24145 local: bool,
24146 autoscroll: bool,
24147 },
24148 TransactionUndone {
24149 transaction_id: clock::Lamport,
24150 },
24151 TransactionBegun {
24152 transaction_id: clock::Lamport,
24153 },
24154 CursorShapeChanged,
24155 BreadcrumbsChanged,
24156 PushedToNavHistory {
24157 anchor: Anchor,
24158 is_deactivate: bool,
24159 },
24160}
24161
24162impl EventEmitter<EditorEvent> for Editor {}
24163
24164impl Focusable for Editor {
24165 fn focus_handle(&self, _cx: &App) -> FocusHandle {
24166 self.focus_handle.clone()
24167 }
24168}
24169
24170impl Render for Editor {
24171 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24172 let settings = ThemeSettings::get_global(cx);
24173
24174 let mut text_style = match self.mode {
24175 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24176 color: cx.theme().colors().editor_foreground,
24177 font_family: settings.ui_font.family.clone(),
24178 font_features: settings.ui_font.features.clone(),
24179 font_fallbacks: settings.ui_font.fallbacks.clone(),
24180 font_size: rems(0.875).into(),
24181 font_weight: settings.ui_font.weight,
24182 line_height: relative(settings.buffer_line_height.value()),
24183 ..Default::default()
24184 },
24185 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24186 color: cx.theme().colors().editor_foreground,
24187 font_family: settings.buffer_font.family.clone(),
24188 font_features: settings.buffer_font.features.clone(),
24189 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24190 font_size: settings.buffer_font_size(cx).into(),
24191 font_weight: settings.buffer_font.weight,
24192 line_height: relative(settings.buffer_line_height.value()),
24193 ..Default::default()
24194 },
24195 };
24196 if let Some(text_style_refinement) = &self.text_style_refinement {
24197 text_style.refine(text_style_refinement)
24198 }
24199
24200 let background = match self.mode {
24201 EditorMode::SingleLine => cx.theme().system().transparent,
24202 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24203 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24204 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24205 };
24206
24207 EditorElement::new(
24208 &cx.entity(),
24209 EditorStyle {
24210 background,
24211 border: cx.theme().colors().border,
24212 local_player: cx.theme().players().local(),
24213 text: text_style,
24214 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24215 syntax: cx.theme().syntax().clone(),
24216 status: cx.theme().status().clone(),
24217 inlay_hints_style: make_inlay_hints_style(cx),
24218 edit_prediction_styles: make_suggestion_styles(cx),
24219 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
24220 show_underlines: self.diagnostics_enabled(),
24221 },
24222 )
24223 }
24224}
24225
24226impl EntityInputHandler for Editor {
24227 fn text_for_range(
24228 &mut self,
24229 range_utf16: Range<usize>,
24230 adjusted_range: &mut Option<Range<usize>>,
24231 _: &mut Window,
24232 cx: &mut Context<Self>,
24233 ) -> Option<String> {
24234 let snapshot = self.buffer.read(cx).read(cx);
24235 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
24236 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
24237 if (start.0..end.0) != range_utf16 {
24238 adjusted_range.replace(start.0..end.0);
24239 }
24240 Some(snapshot.text_for_range(start..end).collect())
24241 }
24242
24243 fn selected_text_range(
24244 &mut self,
24245 ignore_disabled_input: bool,
24246 _: &mut Window,
24247 cx: &mut Context<Self>,
24248 ) -> Option<UTF16Selection> {
24249 // Prevent the IME menu from appearing when holding down an alphabetic key
24250 // while input is disabled.
24251 if !ignore_disabled_input && !self.input_enabled {
24252 return None;
24253 }
24254
24255 let selection = self
24256 .selections
24257 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
24258 let range = selection.range();
24259
24260 Some(UTF16Selection {
24261 range: range.start.0..range.end.0,
24262 reversed: selection.reversed,
24263 })
24264 }
24265
24266 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24267 let snapshot = self.buffer.read(cx).read(cx);
24268 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24269 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
24270 }
24271
24272 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24273 self.clear_highlights::<InputComposition>(cx);
24274 self.ime_transaction.take();
24275 }
24276
24277 fn replace_text_in_range(
24278 &mut self,
24279 range_utf16: Option<Range<usize>>,
24280 text: &str,
24281 window: &mut Window,
24282 cx: &mut Context<Self>,
24283 ) {
24284 if !self.input_enabled {
24285 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24286 return;
24287 }
24288
24289 self.transact(window, cx, |this, window, cx| {
24290 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24291 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24292 Some(this.selection_replacement_ranges(range_utf16, cx))
24293 } else {
24294 this.marked_text_ranges(cx)
24295 };
24296
24297 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24298 let newest_selection_id = this.selections.newest_anchor().id;
24299 this.selections
24300 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24301 .iter()
24302 .zip(ranges_to_replace.iter())
24303 .find_map(|(selection, range)| {
24304 if selection.id == newest_selection_id {
24305 Some(
24306 (range.start.0 as isize - selection.head().0 as isize)
24307 ..(range.end.0 as isize - selection.head().0 as isize),
24308 )
24309 } else {
24310 None
24311 }
24312 })
24313 });
24314
24315 cx.emit(EditorEvent::InputHandled {
24316 utf16_range_to_replace: range_to_replace,
24317 text: text.into(),
24318 });
24319
24320 if let Some(new_selected_ranges) = new_selected_ranges {
24321 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24322 selections.select_ranges(new_selected_ranges)
24323 });
24324 this.backspace(&Default::default(), window, cx);
24325 }
24326
24327 this.handle_input(text, window, cx);
24328 });
24329
24330 if let Some(transaction) = self.ime_transaction {
24331 self.buffer.update(cx, |buffer, cx| {
24332 buffer.group_until_transaction(transaction, cx);
24333 });
24334 }
24335
24336 self.unmark_text(window, cx);
24337 }
24338
24339 fn replace_and_mark_text_in_range(
24340 &mut self,
24341 range_utf16: Option<Range<usize>>,
24342 text: &str,
24343 new_selected_range_utf16: Option<Range<usize>>,
24344 window: &mut Window,
24345 cx: &mut Context<Self>,
24346 ) {
24347 if !self.input_enabled {
24348 return;
24349 }
24350
24351 let transaction = self.transact(window, cx, |this, window, cx| {
24352 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24353 let snapshot = this.buffer.read(cx).read(cx);
24354 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24355 for marked_range in &mut marked_ranges {
24356 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24357 marked_range.start.0 += relative_range_utf16.start;
24358 marked_range.start =
24359 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24360 marked_range.end =
24361 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24362 }
24363 }
24364 Some(marked_ranges)
24365 } else if let Some(range_utf16) = range_utf16 {
24366 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24367 Some(this.selection_replacement_ranges(range_utf16, cx))
24368 } else {
24369 None
24370 };
24371
24372 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24373 let newest_selection_id = this.selections.newest_anchor().id;
24374 this.selections
24375 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24376 .iter()
24377 .zip(ranges_to_replace.iter())
24378 .find_map(|(selection, range)| {
24379 if selection.id == newest_selection_id {
24380 Some(
24381 (range.start.0 as isize - selection.head().0 as isize)
24382 ..(range.end.0 as isize - selection.head().0 as isize),
24383 )
24384 } else {
24385 None
24386 }
24387 })
24388 });
24389
24390 cx.emit(EditorEvent::InputHandled {
24391 utf16_range_to_replace: range_to_replace,
24392 text: text.into(),
24393 });
24394
24395 if let Some(ranges) = ranges_to_replace {
24396 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24397 s.select_ranges(ranges)
24398 });
24399 }
24400
24401 let marked_ranges = {
24402 let snapshot = this.buffer.read(cx).read(cx);
24403 this.selections
24404 .disjoint_anchors_arc()
24405 .iter()
24406 .map(|selection| {
24407 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24408 })
24409 .collect::<Vec<_>>()
24410 };
24411
24412 if text.is_empty() {
24413 this.unmark_text(window, cx);
24414 } else {
24415 this.highlight_text::<InputComposition>(
24416 marked_ranges.clone(),
24417 HighlightStyle {
24418 underline: Some(UnderlineStyle {
24419 thickness: px(1.),
24420 color: None,
24421 wavy: false,
24422 }),
24423 ..Default::default()
24424 },
24425 cx,
24426 );
24427 }
24428
24429 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24430 let use_autoclose = this.use_autoclose;
24431 let use_auto_surround = this.use_auto_surround;
24432 this.set_use_autoclose(false);
24433 this.set_use_auto_surround(false);
24434 this.handle_input(text, window, cx);
24435 this.set_use_autoclose(use_autoclose);
24436 this.set_use_auto_surround(use_auto_surround);
24437
24438 if let Some(new_selected_range) = new_selected_range_utf16 {
24439 let snapshot = this.buffer.read(cx).read(cx);
24440 let new_selected_ranges = marked_ranges
24441 .into_iter()
24442 .map(|marked_range| {
24443 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24444 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24445 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24446 snapshot.clip_offset_utf16(new_start, Bias::Left)
24447 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24448 })
24449 .collect::<Vec<_>>();
24450
24451 drop(snapshot);
24452 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24453 selections.select_ranges(new_selected_ranges)
24454 });
24455 }
24456 });
24457
24458 self.ime_transaction = self.ime_transaction.or(transaction);
24459 if let Some(transaction) = self.ime_transaction {
24460 self.buffer.update(cx, |buffer, cx| {
24461 buffer.group_until_transaction(transaction, cx);
24462 });
24463 }
24464
24465 if self.text_highlights::<InputComposition>(cx).is_none() {
24466 self.ime_transaction.take();
24467 }
24468 }
24469
24470 fn bounds_for_range(
24471 &mut self,
24472 range_utf16: Range<usize>,
24473 element_bounds: gpui::Bounds<Pixels>,
24474 window: &mut Window,
24475 cx: &mut Context<Self>,
24476 ) -> Option<gpui::Bounds<Pixels>> {
24477 let text_layout_details = self.text_layout_details(window);
24478 let CharacterDimensions {
24479 em_width,
24480 em_advance,
24481 line_height,
24482 } = self.character_dimensions(window);
24483
24484 let snapshot = self.snapshot(window, cx);
24485 let scroll_position = snapshot.scroll_position();
24486 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24487
24488 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24489 let x = Pixels::from(
24490 ScrollOffset::from(
24491 snapshot.x_for_display_point(start, &text_layout_details)
24492 + self.gutter_dimensions.full_width(),
24493 ) - scroll_left,
24494 );
24495 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24496
24497 Some(Bounds {
24498 origin: element_bounds.origin + point(x, y),
24499 size: size(em_width, line_height),
24500 })
24501 }
24502
24503 fn character_index_for_point(
24504 &mut self,
24505 point: gpui::Point<Pixels>,
24506 _window: &mut Window,
24507 _cx: &mut Context<Self>,
24508 ) -> Option<usize> {
24509 let position_map = self.last_position_map.as_ref()?;
24510 if !position_map.text_hitbox.contains(&point) {
24511 return None;
24512 }
24513 let display_point = position_map.point_for_position(point).previous_valid;
24514 let anchor = position_map
24515 .snapshot
24516 .display_point_to_anchor(display_point, Bias::Left);
24517 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24518 Some(utf16_offset.0)
24519 }
24520
24521 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24522 self.input_enabled
24523 }
24524}
24525
24526trait SelectionExt {
24527 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24528 fn spanned_rows(
24529 &self,
24530 include_end_if_at_line_start: bool,
24531 map: &DisplaySnapshot,
24532 ) -> Range<MultiBufferRow>;
24533}
24534
24535impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24536 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24537 let start = self
24538 .start
24539 .to_point(map.buffer_snapshot())
24540 .to_display_point(map);
24541 let end = self
24542 .end
24543 .to_point(map.buffer_snapshot())
24544 .to_display_point(map);
24545 if self.reversed {
24546 end..start
24547 } else {
24548 start..end
24549 }
24550 }
24551
24552 fn spanned_rows(
24553 &self,
24554 include_end_if_at_line_start: bool,
24555 map: &DisplaySnapshot,
24556 ) -> Range<MultiBufferRow> {
24557 let start = self.start.to_point(map.buffer_snapshot());
24558 let mut end = self.end.to_point(map.buffer_snapshot());
24559 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24560 end.row -= 1;
24561 }
24562
24563 let buffer_start = map.prev_line_boundary(start).0;
24564 let buffer_end = map.next_line_boundary(end).0;
24565 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24566 }
24567}
24568
24569impl<T: InvalidationRegion> InvalidationStack<T> {
24570 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24571 where
24572 S: Clone + ToOffset,
24573 {
24574 while let Some(region) = self.last() {
24575 let all_selections_inside_invalidation_ranges =
24576 if selections.len() == region.ranges().len() {
24577 selections
24578 .iter()
24579 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24580 .all(|(selection, invalidation_range)| {
24581 let head = selection.head().to_offset(buffer);
24582 invalidation_range.start <= head && invalidation_range.end >= head
24583 })
24584 } else {
24585 false
24586 };
24587
24588 if all_selections_inside_invalidation_ranges {
24589 break;
24590 } else {
24591 self.pop();
24592 }
24593 }
24594 }
24595}
24596
24597impl<T> Default for InvalidationStack<T> {
24598 fn default() -> Self {
24599 Self(Default::default())
24600 }
24601}
24602
24603impl<T> Deref for InvalidationStack<T> {
24604 type Target = Vec<T>;
24605
24606 fn deref(&self) -> &Self::Target {
24607 &self.0
24608 }
24609}
24610
24611impl<T> DerefMut for InvalidationStack<T> {
24612 fn deref_mut(&mut self) -> &mut Self::Target {
24613 &mut self.0
24614 }
24615}
24616
24617impl InvalidationRegion for SnippetState {
24618 fn ranges(&self) -> &[Range<Anchor>] {
24619 &self.ranges[self.active_index]
24620 }
24621}
24622
24623fn edit_prediction_edit_text(
24624 current_snapshot: &BufferSnapshot,
24625 edits: &[(Range<Anchor>, impl AsRef<str>)],
24626 edit_preview: &EditPreview,
24627 include_deletions: bool,
24628 cx: &App,
24629) -> HighlightedText {
24630 let edits = edits
24631 .iter()
24632 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24633 .collect::<Vec<_>>();
24634
24635 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24636}
24637
24638fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24639 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24640 // Just show the raw edit text with basic styling
24641 let mut text = String::new();
24642 let mut highlights = Vec::new();
24643
24644 let insertion_highlight_style = HighlightStyle {
24645 color: Some(cx.theme().colors().text),
24646 ..Default::default()
24647 };
24648
24649 for (_, edit_text) in edits {
24650 let start_offset = text.len();
24651 text.push_str(edit_text);
24652 let end_offset = text.len();
24653
24654 if start_offset < end_offset {
24655 highlights.push((start_offset..end_offset, insertion_highlight_style));
24656 }
24657 }
24658
24659 HighlightedText {
24660 text: text.into(),
24661 highlights,
24662 }
24663}
24664
24665pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24666 match severity {
24667 lsp::DiagnosticSeverity::ERROR => colors.error,
24668 lsp::DiagnosticSeverity::WARNING => colors.warning,
24669 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24670 lsp::DiagnosticSeverity::HINT => colors.info,
24671 _ => colors.ignored,
24672 }
24673}
24674
24675pub fn styled_runs_for_code_label<'a>(
24676 label: &'a CodeLabel,
24677 syntax_theme: &'a theme::SyntaxTheme,
24678) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24679 let fade_out = HighlightStyle {
24680 fade_out: Some(0.35),
24681 ..Default::default()
24682 };
24683
24684 let mut prev_end = label.filter_range.end;
24685 label
24686 .runs
24687 .iter()
24688 .enumerate()
24689 .flat_map(move |(ix, (range, highlight_id))| {
24690 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24691 style
24692 } else {
24693 return Default::default();
24694 };
24695 let muted_style = style.highlight(fade_out);
24696
24697 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24698 if range.start >= label.filter_range.end {
24699 if range.start > prev_end {
24700 runs.push((prev_end..range.start, fade_out));
24701 }
24702 runs.push((range.clone(), muted_style));
24703 } else if range.end <= label.filter_range.end {
24704 runs.push((range.clone(), style));
24705 } else {
24706 runs.push((range.start..label.filter_range.end, style));
24707 runs.push((label.filter_range.end..range.end, muted_style));
24708 }
24709 prev_end = cmp::max(prev_end, range.end);
24710
24711 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24712 runs.push((prev_end..label.text.len(), fade_out));
24713 }
24714
24715 runs
24716 })
24717}
24718
24719pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24720 let mut prev_index = 0;
24721 let mut prev_codepoint: Option<char> = None;
24722 text.char_indices()
24723 .chain([(text.len(), '\0')])
24724 .filter_map(move |(index, codepoint)| {
24725 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24726 let is_boundary = index == text.len()
24727 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24728 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24729 if is_boundary {
24730 let chunk = &text[prev_index..index];
24731 prev_index = index;
24732 Some(chunk)
24733 } else {
24734 None
24735 }
24736 })
24737}
24738
24739/// Given a string of text immediately before the cursor, iterates over possible
24740/// strings a snippet could match to. More precisely: returns an iterator over
24741/// suffixes of `text` created by splitting at word boundaries (before & after
24742/// every non-word character).
24743///
24744/// Shorter suffixes are returned first.
24745pub(crate) fn snippet_candidate_suffixes(
24746 text: &str,
24747 is_word_char: impl Fn(char) -> bool,
24748) -> impl std::iter::Iterator<Item = &str> {
24749 let mut prev_index = text.len();
24750 let mut prev_codepoint = None;
24751 text.char_indices()
24752 .rev()
24753 .chain([(0, '\0')])
24754 .filter_map(move |(index, codepoint)| {
24755 let prev_index = std::mem::replace(&mut prev_index, index);
24756 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24757 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
24758 None
24759 } else {
24760 let chunk = &text[prev_index..]; // go to end of string
24761 Some(chunk)
24762 }
24763 })
24764}
24765
24766pub trait RangeToAnchorExt: Sized {
24767 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24768
24769 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24770 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24771 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24772 }
24773}
24774
24775impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24776 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24777 let start_offset = self.start.to_offset(snapshot);
24778 let end_offset = self.end.to_offset(snapshot);
24779 if start_offset == end_offset {
24780 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24781 } else {
24782 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24783 }
24784 }
24785}
24786
24787pub trait RowExt {
24788 fn as_f64(&self) -> f64;
24789
24790 fn next_row(&self) -> Self;
24791
24792 fn previous_row(&self) -> Self;
24793
24794 fn minus(&self, other: Self) -> u32;
24795}
24796
24797impl RowExt for DisplayRow {
24798 fn as_f64(&self) -> f64 {
24799 self.0 as _
24800 }
24801
24802 fn next_row(&self) -> Self {
24803 Self(self.0 + 1)
24804 }
24805
24806 fn previous_row(&self) -> Self {
24807 Self(self.0.saturating_sub(1))
24808 }
24809
24810 fn minus(&self, other: Self) -> u32 {
24811 self.0 - other.0
24812 }
24813}
24814
24815impl RowExt for MultiBufferRow {
24816 fn as_f64(&self) -> f64 {
24817 self.0 as _
24818 }
24819
24820 fn next_row(&self) -> Self {
24821 Self(self.0 + 1)
24822 }
24823
24824 fn previous_row(&self) -> Self {
24825 Self(self.0.saturating_sub(1))
24826 }
24827
24828 fn minus(&self, other: Self) -> u32 {
24829 self.0 - other.0
24830 }
24831}
24832
24833trait RowRangeExt {
24834 type Row;
24835
24836 fn len(&self) -> usize;
24837
24838 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24839}
24840
24841impl RowRangeExt for Range<MultiBufferRow> {
24842 type Row = MultiBufferRow;
24843
24844 fn len(&self) -> usize {
24845 (self.end.0 - self.start.0) as usize
24846 }
24847
24848 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24849 (self.start.0..self.end.0).map(MultiBufferRow)
24850 }
24851}
24852
24853impl RowRangeExt for Range<DisplayRow> {
24854 type Row = DisplayRow;
24855
24856 fn len(&self) -> usize {
24857 (self.end.0 - self.start.0) as usize
24858 }
24859
24860 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24861 (self.start.0..self.end.0).map(DisplayRow)
24862 }
24863}
24864
24865/// If select range has more than one line, we
24866/// just point the cursor to range.start.
24867fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24868 if range.start.row == range.end.row {
24869 range
24870 } else {
24871 range.start..range.start
24872 }
24873}
24874pub struct KillRing(ClipboardItem);
24875impl Global for KillRing {}
24876
24877const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24878
24879enum BreakpointPromptEditAction {
24880 Log,
24881 Condition,
24882 HitCondition,
24883}
24884
24885struct BreakpointPromptEditor {
24886 pub(crate) prompt: Entity<Editor>,
24887 editor: WeakEntity<Editor>,
24888 breakpoint_anchor: Anchor,
24889 breakpoint: Breakpoint,
24890 edit_action: BreakpointPromptEditAction,
24891 block_ids: HashSet<CustomBlockId>,
24892 editor_margins: Arc<Mutex<EditorMargins>>,
24893 _subscriptions: Vec<Subscription>,
24894}
24895
24896impl BreakpointPromptEditor {
24897 const MAX_LINES: u8 = 4;
24898
24899 fn new(
24900 editor: WeakEntity<Editor>,
24901 breakpoint_anchor: Anchor,
24902 breakpoint: Breakpoint,
24903 edit_action: BreakpointPromptEditAction,
24904 window: &mut Window,
24905 cx: &mut Context<Self>,
24906 ) -> Self {
24907 let base_text = match edit_action {
24908 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24909 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24910 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24911 }
24912 .map(|msg| msg.to_string())
24913 .unwrap_or_default();
24914
24915 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24916 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24917
24918 let prompt = cx.new(|cx| {
24919 let mut prompt = Editor::new(
24920 EditorMode::AutoHeight {
24921 min_lines: 1,
24922 max_lines: Some(Self::MAX_LINES as usize),
24923 },
24924 buffer,
24925 None,
24926 window,
24927 cx,
24928 );
24929 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24930 prompt.set_show_cursor_when_unfocused(false, cx);
24931 prompt.set_placeholder_text(
24932 match edit_action {
24933 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24934 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24935 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24936 },
24937 window,
24938 cx,
24939 );
24940
24941 prompt
24942 });
24943
24944 Self {
24945 prompt,
24946 editor,
24947 breakpoint_anchor,
24948 breakpoint,
24949 edit_action,
24950 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24951 block_ids: Default::default(),
24952 _subscriptions: vec![],
24953 }
24954 }
24955
24956 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24957 self.block_ids.extend(block_ids)
24958 }
24959
24960 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24961 if let Some(editor) = self.editor.upgrade() {
24962 let message = self
24963 .prompt
24964 .read(cx)
24965 .buffer
24966 .read(cx)
24967 .as_singleton()
24968 .expect("A multi buffer in breakpoint prompt isn't possible")
24969 .read(cx)
24970 .as_rope()
24971 .to_string();
24972
24973 editor.update(cx, |editor, cx| {
24974 editor.edit_breakpoint_at_anchor(
24975 self.breakpoint_anchor,
24976 self.breakpoint.clone(),
24977 match self.edit_action {
24978 BreakpointPromptEditAction::Log => {
24979 BreakpointEditAction::EditLogMessage(message.into())
24980 }
24981 BreakpointPromptEditAction::Condition => {
24982 BreakpointEditAction::EditCondition(message.into())
24983 }
24984 BreakpointPromptEditAction::HitCondition => {
24985 BreakpointEditAction::EditHitCondition(message.into())
24986 }
24987 },
24988 cx,
24989 );
24990
24991 editor.remove_blocks(self.block_ids.clone(), None, cx);
24992 cx.focus_self(window);
24993 });
24994 }
24995 }
24996
24997 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24998 self.editor
24999 .update(cx, |editor, cx| {
25000 editor.remove_blocks(self.block_ids.clone(), None, cx);
25001 window.focus(&editor.focus_handle);
25002 })
25003 .log_err();
25004 }
25005
25006 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25007 let settings = ThemeSettings::get_global(cx);
25008 let text_style = TextStyle {
25009 color: if self.prompt.read(cx).read_only(cx) {
25010 cx.theme().colors().text_disabled
25011 } else {
25012 cx.theme().colors().text
25013 },
25014 font_family: settings.buffer_font.family.clone(),
25015 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25016 font_size: settings.buffer_font_size(cx).into(),
25017 font_weight: settings.buffer_font.weight,
25018 line_height: relative(settings.buffer_line_height.value()),
25019 ..Default::default()
25020 };
25021 EditorElement::new(
25022 &self.prompt,
25023 EditorStyle {
25024 background: cx.theme().colors().editor_background,
25025 local_player: cx.theme().players().local(),
25026 text: text_style,
25027 ..Default::default()
25028 },
25029 )
25030 }
25031}
25032
25033impl Render for BreakpointPromptEditor {
25034 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25035 let editor_margins = *self.editor_margins.lock();
25036 let gutter_dimensions = editor_margins.gutter;
25037 h_flex()
25038 .key_context("Editor")
25039 .bg(cx.theme().colors().editor_background)
25040 .border_y_1()
25041 .border_color(cx.theme().status().info_border)
25042 .size_full()
25043 .py(window.line_height() / 2.5)
25044 .on_action(cx.listener(Self::confirm))
25045 .on_action(cx.listener(Self::cancel))
25046 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25047 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25048 }
25049}
25050
25051impl Focusable for BreakpointPromptEditor {
25052 fn focus_handle(&self, cx: &App) -> FocusHandle {
25053 self.prompt.focus_handle(cx)
25054 }
25055}
25056
25057fn all_edits_insertions_or_deletions(
25058 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25059 snapshot: &MultiBufferSnapshot,
25060) -> bool {
25061 let mut all_insertions = true;
25062 let mut all_deletions = true;
25063
25064 for (range, new_text) in edits.iter() {
25065 let range_is_empty = range.to_offset(snapshot).is_empty();
25066 let text_is_empty = new_text.is_empty();
25067
25068 if range_is_empty != text_is_empty {
25069 if range_is_empty {
25070 all_deletions = false;
25071 } else {
25072 all_insertions = false;
25073 }
25074 } else {
25075 return false;
25076 }
25077
25078 if !all_insertions && !all_deletions {
25079 return false;
25080 }
25081 }
25082 all_insertions || all_deletions
25083}
25084
25085struct MissingEditPredictionKeybindingTooltip;
25086
25087impl Render for MissingEditPredictionKeybindingTooltip {
25088 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25089 ui::tooltip_container(cx, |container, cx| {
25090 container
25091 .flex_shrink_0()
25092 .max_w_80()
25093 .min_h(rems_from_px(124.))
25094 .justify_between()
25095 .child(
25096 v_flex()
25097 .flex_1()
25098 .text_ui_sm(cx)
25099 .child(Label::new("Conflict with Accept Keybinding"))
25100 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25101 )
25102 .child(
25103 h_flex()
25104 .pb_1()
25105 .gap_1()
25106 .items_end()
25107 .w_full()
25108 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
25109 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
25110 }))
25111 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
25112 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
25113 })),
25114 )
25115 })
25116 }
25117}
25118
25119#[derive(Debug, Clone, Copy, PartialEq)]
25120pub struct LineHighlight {
25121 pub background: Background,
25122 pub border: Option<gpui::Hsla>,
25123 pub include_gutter: bool,
25124 pub type_id: Option<TypeId>,
25125}
25126
25127struct LineManipulationResult {
25128 pub new_text: String,
25129 pub line_count_before: usize,
25130 pub line_count_after: usize,
25131}
25132
25133fn render_diff_hunk_controls(
25134 row: u32,
25135 status: &DiffHunkStatus,
25136 hunk_range: Range<Anchor>,
25137 is_created_file: bool,
25138 line_height: Pixels,
25139 editor: &Entity<Editor>,
25140 _window: &mut Window,
25141 cx: &mut App,
25142) -> AnyElement {
25143 h_flex()
25144 .h(line_height)
25145 .mr_1()
25146 .gap_1()
25147 .px_0p5()
25148 .pb_1()
25149 .border_x_1()
25150 .border_b_1()
25151 .border_color(cx.theme().colors().border_variant)
25152 .rounded_b_lg()
25153 .bg(cx.theme().colors().editor_background)
25154 .gap_1()
25155 .block_mouse_except_scroll()
25156 .shadow_md()
25157 .child(if status.has_secondary_hunk() {
25158 Button::new(("stage", row as u64), "Stage")
25159 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25160 .tooltip({
25161 let focus_handle = editor.focus_handle(cx);
25162 move |_window, cx| {
25163 Tooltip::for_action_in(
25164 "Stage Hunk",
25165 &::git::ToggleStaged,
25166 &focus_handle,
25167 cx,
25168 )
25169 }
25170 })
25171 .on_click({
25172 let editor = editor.clone();
25173 move |_event, _window, cx| {
25174 editor.update(cx, |editor, cx| {
25175 editor.stage_or_unstage_diff_hunks(
25176 true,
25177 vec![hunk_range.start..hunk_range.start],
25178 cx,
25179 );
25180 });
25181 }
25182 })
25183 } else {
25184 Button::new(("unstage", row as u64), "Unstage")
25185 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25186 .tooltip({
25187 let focus_handle = editor.focus_handle(cx);
25188 move |_window, cx| {
25189 Tooltip::for_action_in(
25190 "Unstage Hunk",
25191 &::git::ToggleStaged,
25192 &focus_handle,
25193 cx,
25194 )
25195 }
25196 })
25197 .on_click({
25198 let editor = editor.clone();
25199 move |_event, _window, cx| {
25200 editor.update(cx, |editor, cx| {
25201 editor.stage_or_unstage_diff_hunks(
25202 false,
25203 vec![hunk_range.start..hunk_range.start],
25204 cx,
25205 );
25206 });
25207 }
25208 })
25209 })
25210 .child(
25211 Button::new(("restore", row as u64), "Restore")
25212 .tooltip({
25213 let focus_handle = editor.focus_handle(cx);
25214 move |_window, cx| {
25215 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
25216 }
25217 })
25218 .on_click({
25219 let editor = editor.clone();
25220 move |_event, window, cx| {
25221 editor.update(cx, |editor, cx| {
25222 let snapshot = editor.snapshot(window, cx);
25223 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
25224 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
25225 });
25226 }
25227 })
25228 .disabled(is_created_file),
25229 )
25230 .when(
25231 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
25232 |el| {
25233 el.child(
25234 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
25235 .shape(IconButtonShape::Square)
25236 .icon_size(IconSize::Small)
25237 // .disabled(!has_multiple_hunks)
25238 .tooltip({
25239 let focus_handle = editor.focus_handle(cx);
25240 move |_window, cx| {
25241 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
25242 }
25243 })
25244 .on_click({
25245 let editor = editor.clone();
25246 move |_event, window, cx| {
25247 editor.update(cx, |editor, cx| {
25248 let snapshot = editor.snapshot(window, cx);
25249 let position =
25250 hunk_range.end.to_point(&snapshot.buffer_snapshot());
25251 editor.go_to_hunk_before_or_after_position(
25252 &snapshot,
25253 position,
25254 Direction::Next,
25255 window,
25256 cx,
25257 );
25258 editor.expand_selected_diff_hunks(cx);
25259 });
25260 }
25261 }),
25262 )
25263 .child(
25264 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25265 .shape(IconButtonShape::Square)
25266 .icon_size(IconSize::Small)
25267 // .disabled(!has_multiple_hunks)
25268 .tooltip({
25269 let focus_handle = editor.focus_handle(cx);
25270 move |_window, cx| {
25271 Tooltip::for_action_in(
25272 "Previous Hunk",
25273 &GoToPreviousHunk,
25274 &focus_handle,
25275 cx,
25276 )
25277 }
25278 })
25279 .on_click({
25280 let editor = editor.clone();
25281 move |_event, window, cx| {
25282 editor.update(cx, |editor, cx| {
25283 let snapshot = editor.snapshot(window, cx);
25284 let point =
25285 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25286 editor.go_to_hunk_before_or_after_position(
25287 &snapshot,
25288 point,
25289 Direction::Prev,
25290 window,
25291 cx,
25292 );
25293 editor.expand_selected_diff_hunks(cx);
25294 });
25295 }
25296 }),
25297 )
25298 },
25299 )
25300 .into_any_element()
25301}
25302
25303pub fn multibuffer_context_lines(cx: &App) -> u32 {
25304 EditorSettings::try_get(cx)
25305 .map(|settings| settings.excerpt_context_lines)
25306 .unwrap_or(2)
25307 .min(32)
25308}