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 collapse_matches: bool,
1103 autoindent_mode: Option<AutoindentMode>,
1104 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1105 input_enabled: bool,
1106 use_modal_editing: bool,
1107 read_only: bool,
1108 leader_id: Option<CollaboratorId>,
1109 remote_id: Option<ViewId>,
1110 pub hover_state: HoverState,
1111 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1112 gutter_hovered: bool,
1113 hovered_link_state: Option<HoveredLinkState>,
1114 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1115 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1116 active_edit_prediction: Option<EditPredictionState>,
1117 /// Used to prevent flickering as the user types while the menu is open
1118 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1119 edit_prediction_settings: EditPredictionSettings,
1120 edit_predictions_hidden_for_vim_mode: bool,
1121 show_edit_predictions_override: Option<bool>,
1122 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1123 edit_prediction_preview: EditPredictionPreview,
1124 edit_prediction_indent_conflict: bool,
1125 edit_prediction_requires_modifier_in_indent_conflict: bool,
1126 next_inlay_id: usize,
1127 next_color_inlay_id: usize,
1128 _subscriptions: Vec<Subscription>,
1129 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1130 gutter_dimensions: GutterDimensions,
1131 style: Option<EditorStyle>,
1132 text_style_refinement: Option<TextStyleRefinement>,
1133 next_editor_action_id: EditorActionId,
1134 editor_actions: Rc<
1135 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1136 >,
1137 use_autoclose: bool,
1138 use_auto_surround: bool,
1139 auto_replace_emoji_shortcode: bool,
1140 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1141 show_git_blame_gutter: bool,
1142 show_git_blame_inline: bool,
1143 show_git_blame_inline_delay_task: Option<Task<()>>,
1144 git_blame_inline_enabled: bool,
1145 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1146 buffer_serialization: Option<BufferSerialization>,
1147 show_selection_menu: Option<bool>,
1148 blame: Option<Entity<GitBlame>>,
1149 blame_subscription: Option<Subscription>,
1150 custom_context_menu: Option<
1151 Box<
1152 dyn 'static
1153 + Fn(
1154 &mut Self,
1155 DisplayPoint,
1156 &mut Window,
1157 &mut Context<Self>,
1158 ) -> Option<Entity<ui::ContextMenu>>,
1159 >,
1160 >,
1161 last_bounds: Option<Bounds<Pixels>>,
1162 last_position_map: Option<Rc<PositionMap>>,
1163 expect_bounds_change: Option<Bounds<Pixels>>,
1164 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1165 tasks_update_task: Option<Task<()>>,
1166 breakpoint_store: Option<Entity<BreakpointStore>>,
1167 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1168 hovered_diff_hunk_row: Option<DisplayRow>,
1169 pull_diagnostics_task: Task<()>,
1170 in_project_search: bool,
1171 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1172 breadcrumb_header: Option<String>,
1173 focused_block: Option<FocusedBlock>,
1174 next_scroll_position: NextScrollCursorCenterTopBottom,
1175 addons: HashMap<TypeId, Box<dyn Addon>>,
1176 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1177 load_diff_task: Option<Shared<Task<()>>>,
1178 /// Whether we are temporarily displaying a diff other than git's
1179 temporary_diff_override: bool,
1180 selection_mark_mode: bool,
1181 toggle_fold_multiple_buffers: Task<()>,
1182 _scroll_cursor_center_top_bottom_task: Task<()>,
1183 serialize_selections: Task<()>,
1184 serialize_folds: Task<()>,
1185 mouse_cursor_hidden: bool,
1186 minimap: Option<Entity<Self>>,
1187 hide_mouse_mode: HideMouseMode,
1188 pub change_list: ChangeList,
1189 inline_value_cache: InlineValueCache,
1190
1191 selection_drag_state: SelectionDragState,
1192 colors: Option<LspColorData>,
1193 post_scroll_update: Task<()>,
1194 refresh_colors_task: Task<()>,
1195 inlay_hints: Option<LspInlayHintData>,
1196 folding_newlines: Task<()>,
1197 select_next_is_case_sensitive: Option<bool>,
1198 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1199}
1200
1201fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1202 if debounce_ms > 0 {
1203 Some(Duration::from_millis(debounce_ms))
1204 } else {
1205 None
1206 }
1207}
1208
1209#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1210enum NextScrollCursorCenterTopBottom {
1211 #[default]
1212 Center,
1213 Top,
1214 Bottom,
1215}
1216
1217impl NextScrollCursorCenterTopBottom {
1218 fn next(&self) -> Self {
1219 match self {
1220 Self::Center => Self::Top,
1221 Self::Top => Self::Bottom,
1222 Self::Bottom => Self::Center,
1223 }
1224 }
1225}
1226
1227#[derive(Clone)]
1228pub struct EditorSnapshot {
1229 pub mode: EditorMode,
1230 show_gutter: bool,
1231 show_line_numbers: Option<bool>,
1232 show_git_diff_gutter: Option<bool>,
1233 show_code_actions: Option<bool>,
1234 show_runnables: Option<bool>,
1235 show_breakpoints: Option<bool>,
1236 git_blame_gutter_max_author_length: Option<usize>,
1237 pub display_snapshot: DisplaySnapshot,
1238 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1239 is_focused: bool,
1240 scroll_anchor: ScrollAnchor,
1241 ongoing_scroll: OngoingScroll,
1242 current_line_highlight: CurrentLineHighlight,
1243 gutter_hovered: bool,
1244}
1245
1246#[derive(Default, Debug, Clone, Copy)]
1247pub struct GutterDimensions {
1248 pub left_padding: Pixels,
1249 pub right_padding: Pixels,
1250 pub width: Pixels,
1251 pub margin: Pixels,
1252 pub git_blame_entries_width: Option<Pixels>,
1253}
1254
1255impl GutterDimensions {
1256 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1257 Self {
1258 margin: Self::default_gutter_margin(font_id, font_size, cx),
1259 ..Default::default()
1260 }
1261 }
1262
1263 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1264 -cx.text_system().descent(font_id, font_size)
1265 }
1266 /// The full width of the space taken up by the gutter.
1267 pub fn full_width(&self) -> Pixels {
1268 self.margin + self.width
1269 }
1270
1271 /// The width of the space reserved for the fold indicators,
1272 /// use alongside 'justify_end' and `gutter_width` to
1273 /// right align content with the line numbers
1274 pub fn fold_area_width(&self) -> Pixels {
1275 self.margin + self.right_padding
1276 }
1277}
1278
1279struct CharacterDimensions {
1280 em_width: Pixels,
1281 em_advance: Pixels,
1282 line_height: Pixels,
1283}
1284
1285#[derive(Debug)]
1286pub struct RemoteSelection {
1287 pub replica_id: ReplicaId,
1288 pub selection: Selection<Anchor>,
1289 pub cursor_shape: CursorShape,
1290 pub collaborator_id: CollaboratorId,
1291 pub line_mode: bool,
1292 pub user_name: Option<SharedString>,
1293 pub color: PlayerColor,
1294}
1295
1296#[derive(Clone, Debug)]
1297struct SelectionHistoryEntry {
1298 selections: Arc<[Selection<Anchor>]>,
1299 select_next_state: Option<SelectNextState>,
1300 select_prev_state: Option<SelectNextState>,
1301 add_selections_state: Option<AddSelectionsState>,
1302}
1303
1304#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1305enum SelectionHistoryMode {
1306 #[default]
1307 Normal,
1308 Undoing,
1309 Redoing,
1310 Skipping,
1311}
1312
1313#[derive(Clone, PartialEq, Eq, Hash)]
1314struct HoveredCursor {
1315 replica_id: ReplicaId,
1316 selection_id: usize,
1317}
1318
1319#[derive(Debug)]
1320/// SelectionEffects controls the side-effects of updating the selection.
1321///
1322/// The default behaviour does "what you mostly want":
1323/// - it pushes to the nav history if the cursor moved by >10 lines
1324/// - it re-triggers completion requests
1325/// - it scrolls to fit
1326///
1327/// You might want to modify these behaviours. For example when doing a "jump"
1328/// like go to definition, we always want to add to nav history; but when scrolling
1329/// in vim mode we never do.
1330///
1331/// Similarly, you might want to disable scrolling if you don't want the viewport to
1332/// move.
1333#[derive(Clone)]
1334pub struct SelectionEffects {
1335 nav_history: Option<bool>,
1336 completions: bool,
1337 scroll: Option<Autoscroll>,
1338}
1339
1340impl Default for SelectionEffects {
1341 fn default() -> Self {
1342 Self {
1343 nav_history: None,
1344 completions: true,
1345 scroll: Some(Autoscroll::fit()),
1346 }
1347 }
1348}
1349impl SelectionEffects {
1350 pub fn scroll(scroll: Autoscroll) -> Self {
1351 Self {
1352 scroll: Some(scroll),
1353 ..Default::default()
1354 }
1355 }
1356
1357 pub fn no_scroll() -> Self {
1358 Self {
1359 scroll: None,
1360 ..Default::default()
1361 }
1362 }
1363
1364 pub fn completions(self, completions: bool) -> Self {
1365 Self {
1366 completions,
1367 ..self
1368 }
1369 }
1370
1371 pub fn nav_history(self, nav_history: bool) -> Self {
1372 Self {
1373 nav_history: Some(nav_history),
1374 ..self
1375 }
1376 }
1377}
1378
1379struct DeferredSelectionEffectsState {
1380 changed: bool,
1381 effects: SelectionEffects,
1382 old_cursor_position: Anchor,
1383 history_entry: SelectionHistoryEntry,
1384}
1385
1386#[derive(Default)]
1387struct SelectionHistory {
1388 #[allow(clippy::type_complexity)]
1389 selections_by_transaction:
1390 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1391 mode: SelectionHistoryMode,
1392 undo_stack: VecDeque<SelectionHistoryEntry>,
1393 redo_stack: VecDeque<SelectionHistoryEntry>,
1394}
1395
1396impl SelectionHistory {
1397 #[track_caller]
1398 fn insert_transaction(
1399 &mut self,
1400 transaction_id: TransactionId,
1401 selections: Arc<[Selection<Anchor>]>,
1402 ) {
1403 if selections.is_empty() {
1404 log::error!(
1405 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1406 std::panic::Location::caller()
1407 );
1408 return;
1409 }
1410 self.selections_by_transaction
1411 .insert(transaction_id, (selections, None));
1412 }
1413
1414 #[allow(clippy::type_complexity)]
1415 fn transaction(
1416 &self,
1417 transaction_id: TransactionId,
1418 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1419 self.selections_by_transaction.get(&transaction_id)
1420 }
1421
1422 #[allow(clippy::type_complexity)]
1423 fn transaction_mut(
1424 &mut self,
1425 transaction_id: TransactionId,
1426 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1427 self.selections_by_transaction.get_mut(&transaction_id)
1428 }
1429
1430 fn push(&mut self, entry: SelectionHistoryEntry) {
1431 if !entry.selections.is_empty() {
1432 match self.mode {
1433 SelectionHistoryMode::Normal => {
1434 self.push_undo(entry);
1435 self.redo_stack.clear();
1436 }
1437 SelectionHistoryMode::Undoing => self.push_redo(entry),
1438 SelectionHistoryMode::Redoing => self.push_undo(entry),
1439 SelectionHistoryMode::Skipping => {}
1440 }
1441 }
1442 }
1443
1444 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1445 if self
1446 .undo_stack
1447 .back()
1448 .is_none_or(|e| e.selections != entry.selections)
1449 {
1450 self.undo_stack.push_back(entry);
1451 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1452 self.undo_stack.pop_front();
1453 }
1454 }
1455 }
1456
1457 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1458 if self
1459 .redo_stack
1460 .back()
1461 .is_none_or(|e| e.selections != entry.selections)
1462 {
1463 self.redo_stack.push_back(entry);
1464 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1465 self.redo_stack.pop_front();
1466 }
1467 }
1468 }
1469}
1470
1471#[derive(Clone, Copy)]
1472pub struct RowHighlightOptions {
1473 pub autoscroll: bool,
1474 pub include_gutter: bool,
1475}
1476
1477impl Default for RowHighlightOptions {
1478 fn default() -> Self {
1479 Self {
1480 autoscroll: Default::default(),
1481 include_gutter: true,
1482 }
1483 }
1484}
1485
1486struct RowHighlight {
1487 index: usize,
1488 range: Range<Anchor>,
1489 color: Hsla,
1490 options: RowHighlightOptions,
1491 type_id: TypeId,
1492}
1493
1494#[derive(Clone, Debug)]
1495struct AddSelectionsState {
1496 groups: Vec<AddSelectionsGroup>,
1497}
1498
1499#[derive(Clone, Debug)]
1500struct AddSelectionsGroup {
1501 above: bool,
1502 stack: Vec<usize>,
1503}
1504
1505#[derive(Clone)]
1506struct SelectNextState {
1507 query: AhoCorasick,
1508 wordwise: bool,
1509 done: bool,
1510}
1511
1512impl std::fmt::Debug for SelectNextState {
1513 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1514 f.debug_struct(std::any::type_name::<Self>())
1515 .field("wordwise", &self.wordwise)
1516 .field("done", &self.done)
1517 .finish()
1518 }
1519}
1520
1521#[derive(Debug)]
1522struct AutocloseRegion {
1523 selection_id: usize,
1524 range: Range<Anchor>,
1525 pair: BracketPair,
1526}
1527
1528#[derive(Debug)]
1529struct SnippetState {
1530 ranges: Vec<Vec<Range<Anchor>>>,
1531 active_index: usize,
1532 choices: Vec<Option<Vec<String>>>,
1533}
1534
1535#[doc(hidden)]
1536pub struct RenameState {
1537 pub range: Range<Anchor>,
1538 pub old_name: Arc<str>,
1539 pub editor: Entity<Editor>,
1540 block_id: CustomBlockId,
1541}
1542
1543struct InvalidationStack<T>(Vec<T>);
1544
1545struct RegisteredEditPredictionProvider {
1546 provider: Arc<dyn EditPredictionProviderHandle>,
1547 _subscription: Subscription,
1548}
1549
1550#[derive(Debug, PartialEq, Eq)]
1551pub struct ActiveDiagnosticGroup {
1552 pub active_range: Range<Anchor>,
1553 pub active_message: String,
1554 pub group_id: usize,
1555 pub blocks: HashSet<CustomBlockId>,
1556}
1557
1558#[derive(Debug, PartialEq, Eq)]
1559
1560pub(crate) enum ActiveDiagnostic {
1561 None,
1562 All,
1563 Group(ActiveDiagnosticGroup),
1564}
1565
1566#[derive(Serialize, Deserialize, Clone, Debug)]
1567pub struct ClipboardSelection {
1568 /// The number of bytes in this selection.
1569 pub len: usize,
1570 /// Whether this was a full-line selection.
1571 pub is_entire_line: bool,
1572 /// The indentation of the first line when this content was originally copied.
1573 pub first_line_indent: u32,
1574}
1575
1576// selections, scroll behavior, was newest selection reversed
1577type SelectSyntaxNodeHistoryState = (
1578 Box<[Selection<usize>]>,
1579 SelectSyntaxNodeScrollBehavior,
1580 bool,
1581);
1582
1583#[derive(Default)]
1584struct SelectSyntaxNodeHistory {
1585 stack: Vec<SelectSyntaxNodeHistoryState>,
1586 // disable temporarily to allow changing selections without losing the stack
1587 pub disable_clearing: bool,
1588}
1589
1590impl SelectSyntaxNodeHistory {
1591 pub fn try_clear(&mut self) {
1592 if !self.disable_clearing {
1593 self.stack.clear();
1594 }
1595 }
1596
1597 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1598 self.stack.push(selection);
1599 }
1600
1601 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1602 self.stack.pop()
1603 }
1604}
1605
1606enum SelectSyntaxNodeScrollBehavior {
1607 CursorTop,
1608 FitSelection,
1609 CursorBottom,
1610}
1611
1612#[derive(Debug)]
1613pub(crate) struct NavigationData {
1614 cursor_anchor: Anchor,
1615 cursor_position: Point,
1616 scroll_anchor: ScrollAnchor,
1617 scroll_top_row: u32,
1618}
1619
1620#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1621pub enum GotoDefinitionKind {
1622 Symbol,
1623 Declaration,
1624 Type,
1625 Implementation,
1626}
1627
1628pub enum FormatTarget {
1629 Buffers(HashSet<Entity<Buffer>>),
1630 Ranges(Vec<Range<MultiBufferPoint>>),
1631}
1632
1633pub(crate) struct FocusedBlock {
1634 id: BlockId,
1635 focus_handle: WeakFocusHandle,
1636}
1637
1638#[derive(Clone)]
1639enum JumpData {
1640 MultiBufferRow {
1641 row: MultiBufferRow,
1642 line_offset_from_top: u32,
1643 },
1644 MultiBufferPoint {
1645 excerpt_id: ExcerptId,
1646 position: Point,
1647 anchor: text::Anchor,
1648 line_offset_from_top: u32,
1649 },
1650}
1651
1652pub enum MultibufferSelectionMode {
1653 First,
1654 All,
1655}
1656
1657#[derive(Clone, Copy, Debug, Default)]
1658pub struct RewrapOptions {
1659 pub override_language_settings: bool,
1660 pub preserve_existing_whitespace: bool,
1661}
1662
1663impl Editor {
1664 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1665 let buffer = cx.new(|cx| Buffer::local("", cx));
1666 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1667 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1668 }
1669
1670 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1671 let buffer = cx.new(|cx| Buffer::local("", cx));
1672 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1673 Self::new(EditorMode::full(), buffer, None, window, cx)
1674 }
1675
1676 pub fn auto_height(
1677 min_lines: usize,
1678 max_lines: usize,
1679 window: &mut Window,
1680 cx: &mut Context<Self>,
1681 ) -> Self {
1682 let buffer = cx.new(|cx| Buffer::local("", cx));
1683 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1684 Self::new(
1685 EditorMode::AutoHeight {
1686 min_lines,
1687 max_lines: Some(max_lines),
1688 },
1689 buffer,
1690 None,
1691 window,
1692 cx,
1693 )
1694 }
1695
1696 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1697 /// The editor grows as tall as needed to fit its content.
1698 pub fn auto_height_unbounded(
1699 min_lines: usize,
1700 window: &mut Window,
1701 cx: &mut Context<Self>,
1702 ) -> Self {
1703 let buffer = cx.new(|cx| Buffer::local("", cx));
1704 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1705 Self::new(
1706 EditorMode::AutoHeight {
1707 min_lines,
1708 max_lines: None,
1709 },
1710 buffer,
1711 None,
1712 window,
1713 cx,
1714 )
1715 }
1716
1717 pub fn for_buffer(
1718 buffer: Entity<Buffer>,
1719 project: Option<Entity<Project>>,
1720 window: &mut Window,
1721 cx: &mut Context<Self>,
1722 ) -> Self {
1723 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1724 Self::new(EditorMode::full(), buffer, project, window, cx)
1725 }
1726
1727 pub fn for_multibuffer(
1728 buffer: Entity<MultiBuffer>,
1729 project: Option<Entity<Project>>,
1730 window: &mut Window,
1731 cx: &mut Context<Self>,
1732 ) -> Self {
1733 Self::new(EditorMode::full(), buffer, project, window, cx)
1734 }
1735
1736 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1737 let mut clone = Self::new(
1738 self.mode.clone(),
1739 self.buffer.clone(),
1740 self.project.clone(),
1741 window,
1742 cx,
1743 );
1744 self.display_map.update(cx, |display_map, cx| {
1745 let snapshot = display_map.snapshot(cx);
1746 clone.display_map.update(cx, |display_map, cx| {
1747 display_map.set_state(&snapshot, cx);
1748 });
1749 });
1750 clone.folds_did_change(cx);
1751 clone.selections.clone_state(&self.selections);
1752 clone.scroll_manager.clone_state(&self.scroll_manager);
1753 clone.searchable = self.searchable;
1754 clone.read_only = self.read_only;
1755 clone
1756 }
1757
1758 pub fn new(
1759 mode: EditorMode,
1760 buffer: Entity<MultiBuffer>,
1761 project: Option<Entity<Project>>,
1762 window: &mut Window,
1763 cx: &mut Context<Self>,
1764 ) -> Self {
1765 Editor::new_internal(mode, buffer, project, None, window, cx)
1766 }
1767
1768 pub fn sticky_headers(&self, cx: &App) -> Option<Vec<OutlineItem<Anchor>>> {
1769 let multi_buffer = self.buffer().read(cx);
1770 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1771 let multi_buffer_visible_start = self
1772 .scroll_manager
1773 .anchor()
1774 .anchor
1775 .to_point(&multi_buffer_snapshot);
1776 let max_row = multi_buffer_snapshot.max_point().row;
1777
1778 let start_row = (multi_buffer_visible_start.row).min(max_row);
1779 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1780
1781 if let Some((excerpt_id, buffer_id, buffer)) = multi_buffer.read(cx).as_singleton() {
1782 let outline_items = buffer
1783 .outline_items_containing(
1784 Point::new(start_row, 0)..Point::new(end_row, 0),
1785 true,
1786 self.style().map(|style| style.syntax.as_ref()),
1787 )
1788 .into_iter()
1789 .map(|outline_item| OutlineItem {
1790 depth: outline_item.depth,
1791 range: Anchor::range_in_buffer(*excerpt_id, buffer_id, outline_item.range),
1792 source_range_for_text: Anchor::range_in_buffer(
1793 *excerpt_id,
1794 buffer_id,
1795 outline_item.source_range_for_text,
1796 ),
1797 text: outline_item.text,
1798 highlight_ranges: outline_item.highlight_ranges,
1799 name_ranges: outline_item.name_ranges,
1800 body_range: outline_item
1801 .body_range
1802 .map(|range| Anchor::range_in_buffer(*excerpt_id, buffer_id, range)),
1803 annotation_range: outline_item
1804 .annotation_range
1805 .map(|range| Anchor::range_in_buffer(*excerpt_id, buffer_id, range)),
1806 });
1807 return Some(outline_items.collect());
1808 }
1809
1810 None
1811 }
1812
1813 fn new_internal(
1814 mode: EditorMode,
1815 multi_buffer: Entity<MultiBuffer>,
1816 project: Option<Entity<Project>>,
1817 display_map: Option<Entity<DisplayMap>>,
1818 window: &mut Window,
1819 cx: &mut Context<Self>,
1820 ) -> Self {
1821 debug_assert!(
1822 display_map.is_none() || mode.is_minimap(),
1823 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1824 );
1825
1826 let full_mode = mode.is_full();
1827 let is_minimap = mode.is_minimap();
1828 let diagnostics_max_severity = if full_mode {
1829 EditorSettings::get_global(cx)
1830 .diagnostics_max_severity
1831 .unwrap_or(DiagnosticSeverity::Hint)
1832 } else {
1833 DiagnosticSeverity::Off
1834 };
1835 let style = window.text_style();
1836 let font_size = style.font_size.to_pixels(window.rem_size());
1837 let editor = cx.entity().downgrade();
1838 let fold_placeholder = FoldPlaceholder {
1839 constrain_width: false,
1840 render: Arc::new(move |fold_id, fold_range, cx| {
1841 let editor = editor.clone();
1842 div()
1843 .id(fold_id)
1844 .bg(cx.theme().colors().ghost_element_background)
1845 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1846 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1847 .rounded_xs()
1848 .size_full()
1849 .cursor_pointer()
1850 .child("⋯")
1851 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1852 .on_click(move |_, _window, cx| {
1853 editor
1854 .update(cx, |editor, cx| {
1855 editor.unfold_ranges(
1856 &[fold_range.start..fold_range.end],
1857 true,
1858 false,
1859 cx,
1860 );
1861 cx.stop_propagation();
1862 })
1863 .ok();
1864 })
1865 .into_any()
1866 }),
1867 merge_adjacent: true,
1868 ..FoldPlaceholder::default()
1869 };
1870 let display_map = display_map.unwrap_or_else(|| {
1871 cx.new(|cx| {
1872 DisplayMap::new(
1873 multi_buffer.clone(),
1874 style.font(),
1875 font_size,
1876 None,
1877 FILE_HEADER_HEIGHT,
1878 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1879 fold_placeholder,
1880 diagnostics_max_severity,
1881 cx,
1882 )
1883 })
1884 });
1885
1886 let selections = SelectionsCollection::new();
1887
1888 let blink_manager = cx.new(|cx| {
1889 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1890 if is_minimap {
1891 blink_manager.disable(cx);
1892 }
1893 blink_manager
1894 });
1895
1896 let soft_wrap_mode_override =
1897 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1898
1899 let mut project_subscriptions = Vec::new();
1900 if full_mode && let Some(project) = project.as_ref() {
1901 project_subscriptions.push(cx.subscribe_in(
1902 project,
1903 window,
1904 |editor, _, event, window, cx| match event {
1905 project::Event::RefreshCodeLens => {
1906 // we always query lens with actions, without storing them, always refreshing them
1907 }
1908 project::Event::RefreshInlayHints {
1909 server_id,
1910 request_id,
1911 } => {
1912 editor.refresh_inlay_hints(
1913 InlayHintRefreshReason::RefreshRequested {
1914 server_id: *server_id,
1915 request_id: *request_id,
1916 },
1917 cx,
1918 );
1919 }
1920 project::Event::LanguageServerRemoved(..) => {
1921 if editor.tasks_update_task.is_none() {
1922 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1923 }
1924 editor.registered_buffers.clear();
1925 editor.register_visible_buffers(cx);
1926 }
1927 project::Event::LanguageServerAdded(..) => {
1928 if editor.tasks_update_task.is_none() {
1929 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1930 }
1931 }
1932 project::Event::SnippetEdit(id, snippet_edits) => {
1933 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1934 let focus_handle = editor.focus_handle(cx);
1935 if focus_handle.is_focused(window) {
1936 let snapshot = buffer.read(cx).snapshot();
1937 for (range, snippet) in snippet_edits {
1938 let editor_range =
1939 language::range_from_lsp(*range).to_offset(&snapshot);
1940 editor
1941 .insert_snippet(
1942 &[editor_range],
1943 snippet.clone(),
1944 window,
1945 cx,
1946 )
1947 .ok();
1948 }
1949 }
1950 }
1951 }
1952 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1953 let buffer_id = *buffer_id;
1954 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1955 editor.register_buffer(buffer_id, cx);
1956 editor.update_lsp_data(Some(buffer_id), window, cx);
1957 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1958 refresh_linked_ranges(editor, window, cx);
1959 editor.refresh_code_actions(window, cx);
1960 editor.refresh_document_highlights(cx);
1961 }
1962 }
1963
1964 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
1965 let Some(workspace) = editor.workspace() else {
1966 return;
1967 };
1968 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1969 else {
1970 return;
1971 };
1972
1973 if active_editor.entity_id() == cx.entity_id() {
1974 let entity_id = cx.entity_id();
1975 workspace.update(cx, |this, cx| {
1976 this.panes_mut()
1977 .iter_mut()
1978 .filter(|pane| pane.entity_id() != entity_id)
1979 .for_each(|p| {
1980 p.update(cx, |pane, _| {
1981 pane.nav_history_mut().rename_item(
1982 entity_id,
1983 project_path.clone(),
1984 abs_path.clone().into(),
1985 );
1986 })
1987 });
1988 });
1989 let edited_buffers_already_open = {
1990 let other_editors: Vec<Entity<Editor>> = workspace
1991 .read(cx)
1992 .panes()
1993 .iter()
1994 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1995 .filter(|editor| editor.entity_id() != cx.entity_id())
1996 .collect();
1997
1998 transaction.0.keys().all(|buffer| {
1999 other_editors.iter().any(|editor| {
2000 let multi_buffer = editor.read(cx).buffer();
2001 multi_buffer.read(cx).is_singleton()
2002 && multi_buffer.read(cx).as_singleton().map_or(
2003 false,
2004 |singleton| {
2005 singleton.entity_id() == buffer.entity_id()
2006 },
2007 )
2008 })
2009 })
2010 };
2011 if !edited_buffers_already_open {
2012 let workspace = workspace.downgrade();
2013 let transaction = transaction.clone();
2014 cx.defer_in(window, move |_, window, cx| {
2015 cx.spawn_in(window, async move |editor, cx| {
2016 Self::open_project_transaction(
2017 &editor,
2018 workspace,
2019 transaction,
2020 "Rename".to_string(),
2021 cx,
2022 )
2023 .await
2024 .ok()
2025 })
2026 .detach();
2027 });
2028 }
2029 }
2030 }
2031
2032 _ => {}
2033 },
2034 ));
2035 if let Some(task_inventory) = project
2036 .read(cx)
2037 .task_store()
2038 .read(cx)
2039 .task_inventory()
2040 .cloned()
2041 {
2042 project_subscriptions.push(cx.observe_in(
2043 &task_inventory,
2044 window,
2045 |editor, _, window, cx| {
2046 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2047 },
2048 ));
2049 };
2050
2051 project_subscriptions.push(cx.subscribe_in(
2052 &project.read(cx).breakpoint_store(),
2053 window,
2054 |editor, _, event, window, cx| match event {
2055 BreakpointStoreEvent::ClearDebugLines => {
2056 editor.clear_row_highlights::<ActiveDebugLine>();
2057 editor.refresh_inline_values(cx);
2058 }
2059 BreakpointStoreEvent::SetDebugLine => {
2060 if editor.go_to_active_debug_line(window, cx) {
2061 cx.stop_propagation();
2062 }
2063
2064 editor.refresh_inline_values(cx);
2065 }
2066 _ => {}
2067 },
2068 ));
2069 let git_store = project.read(cx).git_store().clone();
2070 let project = project.clone();
2071 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2072 if let GitStoreEvent::RepositoryAdded = event {
2073 this.load_diff_task = Some(
2074 update_uncommitted_diff_for_buffer(
2075 cx.entity(),
2076 &project,
2077 this.buffer.read(cx).all_buffers(),
2078 this.buffer.clone(),
2079 cx,
2080 )
2081 .shared(),
2082 );
2083 }
2084 }));
2085 }
2086
2087 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2088
2089 let inlay_hint_settings =
2090 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2091 let focus_handle = cx.focus_handle();
2092 if !is_minimap {
2093 cx.on_focus(&focus_handle, window, Self::handle_focus)
2094 .detach();
2095 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2096 .detach();
2097 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2098 .detach();
2099 cx.on_blur(&focus_handle, window, Self::handle_blur)
2100 .detach();
2101 cx.observe_pending_input(window, Self::observe_pending_input)
2102 .detach();
2103 }
2104
2105 let show_indent_guides =
2106 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2107 Some(false)
2108 } else {
2109 None
2110 };
2111
2112 let breakpoint_store = match (&mode, project.as_ref()) {
2113 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2114 _ => None,
2115 };
2116
2117 let mut code_action_providers = Vec::new();
2118 let mut load_uncommitted_diff = None;
2119 if let Some(project) = project.clone() {
2120 load_uncommitted_diff = Some(
2121 update_uncommitted_diff_for_buffer(
2122 cx.entity(),
2123 &project,
2124 multi_buffer.read(cx).all_buffers(),
2125 multi_buffer.clone(),
2126 cx,
2127 )
2128 .shared(),
2129 );
2130 code_action_providers.push(Rc::new(project) as Rc<_>);
2131 }
2132
2133 let mut editor = Self {
2134 focus_handle,
2135 show_cursor_when_unfocused: false,
2136 last_focused_descendant: None,
2137 buffer: multi_buffer.clone(),
2138 display_map: display_map.clone(),
2139 placeholder_display_map: None,
2140 selections,
2141 scroll_manager: ScrollManager::new(cx),
2142 columnar_selection_state: None,
2143 add_selections_state: None,
2144 select_next_state: None,
2145 select_prev_state: None,
2146 selection_history: SelectionHistory::default(),
2147 defer_selection_effects: false,
2148 deferred_selection_effects_state: None,
2149 autoclose_regions: Vec::new(),
2150 snippet_stack: InvalidationStack::default(),
2151 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2152 ime_transaction: None,
2153 active_diagnostics: ActiveDiagnostic::None,
2154 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2155 inline_diagnostics_update: Task::ready(()),
2156 inline_diagnostics: Vec::new(),
2157 soft_wrap_mode_override,
2158 diagnostics_max_severity,
2159 hard_wrap: None,
2160 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2161 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2162 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2163 project,
2164 blink_manager: blink_manager.clone(),
2165 show_local_selections: true,
2166 show_scrollbars: ScrollbarAxes {
2167 horizontal: full_mode,
2168 vertical: full_mode,
2169 },
2170 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2171 offset_content: !matches!(mode, EditorMode::SingleLine),
2172 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2173 show_gutter: full_mode,
2174 show_line_numbers: (!full_mode).then_some(false),
2175 use_relative_line_numbers: None,
2176 disable_expand_excerpt_buttons: !full_mode,
2177 show_git_diff_gutter: None,
2178 show_code_actions: None,
2179 show_runnables: None,
2180 show_breakpoints: None,
2181 show_wrap_guides: None,
2182 show_indent_guides,
2183 highlight_order: 0,
2184 highlighted_rows: HashMap::default(),
2185 background_highlights: HashMap::default(),
2186 gutter_highlights: HashMap::default(),
2187 scrollbar_marker_state: ScrollbarMarkerState::default(),
2188 active_indent_guides_state: ActiveIndentGuidesState::default(),
2189 nav_history: None,
2190 context_menu: RefCell::new(None),
2191 context_menu_options: None,
2192 mouse_context_menu: None,
2193 completion_tasks: Vec::new(),
2194 inline_blame_popover: None,
2195 inline_blame_popover_show_task: None,
2196 signature_help_state: SignatureHelpState::default(),
2197 auto_signature_help: None,
2198 find_all_references_task_sources: Vec::new(),
2199 next_completion_id: 0,
2200 next_inlay_id: 0,
2201 code_action_providers,
2202 available_code_actions: None,
2203 code_actions_task: None,
2204 quick_selection_highlight_task: None,
2205 debounced_selection_highlight_task: None,
2206 document_highlights_task: None,
2207 linked_editing_range_task: None,
2208 pending_rename: None,
2209 searchable: !is_minimap,
2210 cursor_shape: EditorSettings::get_global(cx)
2211 .cursor_shape
2212 .unwrap_or_default(),
2213 current_line_highlight: None,
2214 autoindent_mode: Some(AutoindentMode::EachLine),
2215 collapse_matches: false,
2216 workspace: None,
2217 input_enabled: !is_minimap,
2218 use_modal_editing: full_mode,
2219 read_only: is_minimap,
2220 use_autoclose: true,
2221 use_auto_surround: true,
2222 auto_replace_emoji_shortcode: false,
2223 jsx_tag_auto_close_enabled_in_any_buffer: false,
2224 leader_id: None,
2225 remote_id: None,
2226 hover_state: HoverState::default(),
2227 pending_mouse_down: None,
2228 hovered_link_state: None,
2229 edit_prediction_provider: None,
2230 active_edit_prediction: None,
2231 stale_edit_prediction_in_menu: None,
2232 edit_prediction_preview: EditPredictionPreview::Inactive {
2233 released_too_fast: false,
2234 },
2235 inline_diagnostics_enabled: full_mode,
2236 diagnostics_enabled: full_mode,
2237 word_completions_enabled: full_mode,
2238 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2239 gutter_hovered: false,
2240 pixel_position_of_newest_cursor: None,
2241 last_bounds: None,
2242 last_position_map: None,
2243 expect_bounds_change: None,
2244 gutter_dimensions: GutterDimensions::default(),
2245 style: None,
2246 show_cursor_names: false,
2247 hovered_cursors: HashMap::default(),
2248 next_editor_action_id: EditorActionId::default(),
2249 editor_actions: Rc::default(),
2250 edit_predictions_hidden_for_vim_mode: false,
2251 show_edit_predictions_override: None,
2252 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2253 edit_prediction_settings: EditPredictionSettings::Disabled,
2254 edit_prediction_indent_conflict: false,
2255 edit_prediction_requires_modifier_in_indent_conflict: true,
2256 custom_context_menu: None,
2257 show_git_blame_gutter: false,
2258 show_git_blame_inline: false,
2259 show_selection_menu: None,
2260 show_git_blame_inline_delay_task: None,
2261 git_blame_inline_enabled: full_mode
2262 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2263 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2264 buffer_serialization: is_minimap.not().then(|| {
2265 BufferSerialization::new(
2266 ProjectSettings::get_global(cx)
2267 .session
2268 .restore_unsaved_buffers,
2269 )
2270 }),
2271 blame: None,
2272 blame_subscription: None,
2273 tasks: BTreeMap::default(),
2274
2275 breakpoint_store,
2276 gutter_breakpoint_indicator: (None, None),
2277 hovered_diff_hunk_row: None,
2278 _subscriptions: (!is_minimap)
2279 .then(|| {
2280 vec![
2281 cx.observe(&multi_buffer, Self::on_buffer_changed),
2282 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2283 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2284 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2285 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2286 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2287 cx.observe_window_activation(window, |editor, window, cx| {
2288 let active = window.is_window_active();
2289 editor.blink_manager.update(cx, |blink_manager, cx| {
2290 if active {
2291 blink_manager.enable(cx);
2292 } else {
2293 blink_manager.disable(cx);
2294 }
2295 });
2296 if active {
2297 editor.show_mouse_cursor(cx);
2298 }
2299 }),
2300 ]
2301 })
2302 .unwrap_or_default(),
2303 tasks_update_task: None,
2304 pull_diagnostics_task: Task::ready(()),
2305 colors: None,
2306 refresh_colors_task: Task::ready(()),
2307 inlay_hints: None,
2308 next_color_inlay_id: 0,
2309 post_scroll_update: Task::ready(()),
2310 linked_edit_ranges: Default::default(),
2311 in_project_search: false,
2312 previous_search_ranges: None,
2313 breadcrumb_header: None,
2314 focused_block: None,
2315 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2316 addons: HashMap::default(),
2317 registered_buffers: HashMap::default(),
2318 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2319 selection_mark_mode: false,
2320 toggle_fold_multiple_buffers: Task::ready(()),
2321 serialize_selections: Task::ready(()),
2322 serialize_folds: Task::ready(()),
2323 text_style_refinement: None,
2324 load_diff_task: load_uncommitted_diff,
2325 temporary_diff_override: false,
2326 mouse_cursor_hidden: false,
2327 minimap: None,
2328 hide_mouse_mode: EditorSettings::get_global(cx)
2329 .hide_mouse
2330 .unwrap_or_default(),
2331 change_list: ChangeList::new(),
2332 mode,
2333 selection_drag_state: SelectionDragState::None,
2334 folding_newlines: Task::ready(()),
2335 lookup_key: None,
2336 select_next_is_case_sensitive: None,
2337 };
2338
2339 if is_minimap {
2340 return editor;
2341 }
2342
2343 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2344 editor
2345 ._subscriptions
2346 .push(cx.observe(breakpoints, |_, _, cx| {
2347 cx.notify();
2348 }));
2349 }
2350 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2351 editor._subscriptions.extend(project_subscriptions);
2352
2353 editor._subscriptions.push(cx.subscribe_in(
2354 &cx.entity(),
2355 window,
2356 |editor, _, e: &EditorEvent, window, cx| match e {
2357 EditorEvent::ScrollPositionChanged { local, .. } => {
2358 if *local {
2359 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2360 editor.inline_blame_popover.take();
2361 let new_anchor = editor.scroll_manager.anchor();
2362 let snapshot = editor.snapshot(window, cx);
2363 editor.update_restoration_data(cx, move |data| {
2364 data.scroll_position = (
2365 new_anchor.top_row(snapshot.buffer_snapshot()),
2366 new_anchor.offset,
2367 );
2368 });
2369
2370 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2371 cx.background_executor()
2372 .timer(Duration::from_millis(50))
2373 .await;
2374 editor
2375 .update_in(cx, |editor, window, cx| {
2376 editor.register_visible_buffers(cx);
2377 editor.refresh_colors_for_visible_range(None, window, cx);
2378 editor.refresh_inlay_hints(
2379 InlayHintRefreshReason::NewLinesShown,
2380 cx,
2381 );
2382 })
2383 .ok();
2384 });
2385 }
2386 }
2387 EditorEvent::Edited { .. } => {
2388 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2389 .map(|vim_mode| vim_mode.0)
2390 .unwrap_or(false);
2391 if !vim_mode {
2392 let display_map = editor.display_snapshot(cx);
2393 let selections = editor.selections.all_adjusted_display(&display_map);
2394 let pop_state = editor
2395 .change_list
2396 .last()
2397 .map(|previous| {
2398 previous.len() == selections.len()
2399 && previous.iter().enumerate().all(|(ix, p)| {
2400 p.to_display_point(&display_map).row()
2401 == selections[ix].head().row()
2402 })
2403 })
2404 .unwrap_or(false);
2405 let new_positions = selections
2406 .into_iter()
2407 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2408 .collect();
2409 editor
2410 .change_list
2411 .push_to_change_list(pop_state, new_positions);
2412 }
2413 }
2414 _ => (),
2415 },
2416 ));
2417
2418 if let Some(dap_store) = editor
2419 .project
2420 .as_ref()
2421 .map(|project| project.read(cx).dap_store())
2422 {
2423 let weak_editor = cx.weak_entity();
2424
2425 editor
2426 ._subscriptions
2427 .push(
2428 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2429 let session_entity = cx.entity();
2430 weak_editor
2431 .update(cx, |editor, cx| {
2432 editor._subscriptions.push(
2433 cx.subscribe(&session_entity, Self::on_debug_session_event),
2434 );
2435 })
2436 .ok();
2437 }),
2438 );
2439
2440 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2441 editor
2442 ._subscriptions
2443 .push(cx.subscribe(&session, Self::on_debug_session_event));
2444 }
2445 }
2446
2447 // skip adding the initial selection to selection history
2448 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2449 editor.end_selection(window, cx);
2450 editor.selection_history.mode = SelectionHistoryMode::Normal;
2451
2452 editor.scroll_manager.show_scrollbars(window, cx);
2453 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2454
2455 if full_mode {
2456 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2457 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2458
2459 if editor.git_blame_inline_enabled {
2460 editor.start_git_blame_inline(false, window, cx);
2461 }
2462
2463 editor.go_to_active_debug_line(window, cx);
2464
2465 editor.minimap =
2466 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2467 editor.colors = Some(LspColorData::new(cx));
2468 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2469
2470 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2471 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2472 }
2473 editor.update_lsp_data(None, window, cx);
2474 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2475 }
2476
2477 editor
2478 }
2479
2480 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2481 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2482 }
2483
2484 pub fn deploy_mouse_context_menu(
2485 &mut self,
2486 position: gpui::Point<Pixels>,
2487 context_menu: Entity<ContextMenu>,
2488 window: &mut Window,
2489 cx: &mut Context<Self>,
2490 ) {
2491 self.mouse_context_menu = Some(MouseContextMenu::new(
2492 self,
2493 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2494 context_menu,
2495 window,
2496 cx,
2497 ));
2498 }
2499
2500 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2501 self.mouse_context_menu
2502 .as_ref()
2503 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2504 }
2505
2506 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2507 if self
2508 .selections
2509 .pending_anchor()
2510 .is_some_and(|pending_selection| {
2511 let snapshot = self.buffer().read(cx).snapshot(cx);
2512 pending_selection.range().includes(range, &snapshot)
2513 })
2514 {
2515 return true;
2516 }
2517
2518 self.selections
2519 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2520 .into_iter()
2521 .any(|selection| {
2522 // This is needed to cover a corner case, if we just check for an existing
2523 // selection in the fold range, having a cursor at the start of the fold
2524 // marks it as selected. Non-empty selections don't cause this.
2525 let length = selection.end - selection.start;
2526 length > 0
2527 })
2528 }
2529
2530 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2531 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2532 }
2533
2534 fn key_context_internal(
2535 &self,
2536 has_active_edit_prediction: bool,
2537 window: &mut Window,
2538 cx: &mut App,
2539 ) -> KeyContext {
2540 let mut key_context = KeyContext::new_with_defaults();
2541 key_context.add("Editor");
2542 let mode = match self.mode {
2543 EditorMode::SingleLine => "single_line",
2544 EditorMode::AutoHeight { .. } => "auto_height",
2545 EditorMode::Minimap { .. } => "minimap",
2546 EditorMode::Full { .. } => "full",
2547 };
2548
2549 if EditorSettings::jupyter_enabled(cx) {
2550 key_context.add("jupyter");
2551 }
2552
2553 key_context.set("mode", mode);
2554 if self.pending_rename.is_some() {
2555 key_context.add("renaming");
2556 }
2557
2558 if let Some(snippet_stack) = self.snippet_stack.last() {
2559 key_context.add("in_snippet");
2560
2561 if snippet_stack.active_index > 0 {
2562 key_context.add("has_previous_tabstop");
2563 }
2564
2565 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2566 key_context.add("has_next_tabstop");
2567 }
2568 }
2569
2570 match self.context_menu.borrow().as_ref() {
2571 Some(CodeContextMenu::Completions(menu)) => {
2572 if menu.visible() {
2573 key_context.add("menu");
2574 key_context.add("showing_completions");
2575 }
2576 }
2577 Some(CodeContextMenu::CodeActions(menu)) => {
2578 if menu.visible() {
2579 key_context.add("menu");
2580 key_context.add("showing_code_actions")
2581 }
2582 }
2583 None => {}
2584 }
2585
2586 if self.signature_help_state.has_multiple_signatures() {
2587 key_context.add("showing_signature_help");
2588 }
2589
2590 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2591 if !self.focus_handle(cx).contains_focused(window, cx)
2592 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2593 {
2594 for addon in self.addons.values() {
2595 addon.extend_key_context(&mut key_context, cx)
2596 }
2597 }
2598
2599 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2600 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2601 Some(
2602 file.full_path(cx)
2603 .extension()?
2604 .to_string_lossy()
2605 .into_owned(),
2606 )
2607 }) {
2608 key_context.set("extension", extension);
2609 }
2610 } else {
2611 key_context.add("multibuffer");
2612 }
2613
2614 if has_active_edit_prediction {
2615 if self.edit_prediction_in_conflict() {
2616 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2617 } else {
2618 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2619 key_context.add("copilot_suggestion");
2620 }
2621 }
2622
2623 if self.selection_mark_mode {
2624 key_context.add("selection_mode");
2625 }
2626
2627 let disjoint = self.selections.disjoint_anchors();
2628 let snapshot = self.snapshot(window, cx);
2629 let snapshot = snapshot.buffer_snapshot();
2630 if self.mode == EditorMode::SingleLine
2631 && let [selection] = disjoint
2632 && selection.start == selection.end
2633 && selection.end.to_offset(snapshot) == snapshot.len()
2634 {
2635 key_context.add("end_of_input");
2636 }
2637
2638 if self.has_any_expanded_diff_hunks(cx) {
2639 key_context.add("diffs_expanded");
2640 }
2641
2642 key_context
2643 }
2644
2645 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2646 self.last_bounds.as_ref()
2647 }
2648
2649 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2650 if self.mouse_cursor_hidden {
2651 self.mouse_cursor_hidden = false;
2652 cx.notify();
2653 }
2654 }
2655
2656 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2657 let hide_mouse_cursor = match origin {
2658 HideMouseCursorOrigin::TypingAction => {
2659 matches!(
2660 self.hide_mouse_mode,
2661 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2662 )
2663 }
2664 HideMouseCursorOrigin::MovementAction => {
2665 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2666 }
2667 };
2668 if self.mouse_cursor_hidden != hide_mouse_cursor {
2669 self.mouse_cursor_hidden = hide_mouse_cursor;
2670 cx.notify();
2671 }
2672 }
2673
2674 pub fn edit_prediction_in_conflict(&self) -> bool {
2675 if !self.show_edit_predictions_in_menu() {
2676 return false;
2677 }
2678
2679 let showing_completions = self
2680 .context_menu
2681 .borrow()
2682 .as_ref()
2683 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2684
2685 showing_completions
2686 || self.edit_prediction_requires_modifier()
2687 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2688 // bindings to insert tab characters.
2689 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2690 }
2691
2692 pub fn accept_edit_prediction_keybind(
2693 &self,
2694 accept_partial: bool,
2695 window: &mut Window,
2696 cx: &mut App,
2697 ) -> AcceptEditPredictionBinding {
2698 let key_context = self.key_context_internal(true, window, cx);
2699 let in_conflict = self.edit_prediction_in_conflict();
2700
2701 let bindings = if accept_partial {
2702 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2703 } else {
2704 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2705 };
2706
2707 // TODO: if the binding contains multiple keystrokes, display all of them, not
2708 // just the first one.
2709 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2710 !in_conflict
2711 || binding
2712 .keystrokes()
2713 .first()
2714 .is_some_and(|keystroke| keystroke.modifiers().modified())
2715 }))
2716 }
2717
2718 pub fn new_file(
2719 workspace: &mut Workspace,
2720 _: &workspace::NewFile,
2721 window: &mut Window,
2722 cx: &mut Context<Workspace>,
2723 ) {
2724 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2725 "Failed to create buffer",
2726 window,
2727 cx,
2728 |e, _, _| match e.error_code() {
2729 ErrorCode::RemoteUpgradeRequired => Some(format!(
2730 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2731 e.error_tag("required").unwrap_or("the latest version")
2732 )),
2733 _ => None,
2734 },
2735 );
2736 }
2737
2738 pub fn new_in_workspace(
2739 workspace: &mut Workspace,
2740 window: &mut Window,
2741 cx: &mut Context<Workspace>,
2742 ) -> Task<Result<Entity<Editor>>> {
2743 let project = workspace.project().clone();
2744 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2745
2746 cx.spawn_in(window, async move |workspace, cx| {
2747 let buffer = create.await?;
2748 workspace.update_in(cx, |workspace, window, cx| {
2749 let editor =
2750 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2751 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2752 editor
2753 })
2754 })
2755 }
2756
2757 fn new_file_vertical(
2758 workspace: &mut Workspace,
2759 _: &workspace::NewFileSplitVertical,
2760 window: &mut Window,
2761 cx: &mut Context<Workspace>,
2762 ) {
2763 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2764 }
2765
2766 fn new_file_horizontal(
2767 workspace: &mut Workspace,
2768 _: &workspace::NewFileSplitHorizontal,
2769 window: &mut Window,
2770 cx: &mut Context<Workspace>,
2771 ) {
2772 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2773 }
2774
2775 fn new_file_split(
2776 workspace: &mut Workspace,
2777 action: &workspace::NewFileSplit,
2778 window: &mut Window,
2779 cx: &mut Context<Workspace>,
2780 ) {
2781 Self::new_file_in_direction(workspace, action.0, window, cx)
2782 }
2783
2784 fn new_file_in_direction(
2785 workspace: &mut Workspace,
2786 direction: SplitDirection,
2787 window: &mut Window,
2788 cx: &mut Context<Workspace>,
2789 ) {
2790 let project = workspace.project().clone();
2791 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2792
2793 cx.spawn_in(window, async move |workspace, cx| {
2794 let buffer = create.await?;
2795 workspace.update_in(cx, move |workspace, window, cx| {
2796 workspace.split_item(
2797 direction,
2798 Box::new(
2799 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2800 ),
2801 window,
2802 cx,
2803 )
2804 })?;
2805 anyhow::Ok(())
2806 })
2807 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2808 match e.error_code() {
2809 ErrorCode::RemoteUpgradeRequired => Some(format!(
2810 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2811 e.error_tag("required").unwrap_or("the latest version")
2812 )),
2813 _ => None,
2814 }
2815 });
2816 }
2817
2818 pub fn leader_id(&self) -> Option<CollaboratorId> {
2819 self.leader_id
2820 }
2821
2822 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2823 &self.buffer
2824 }
2825
2826 pub fn project(&self) -> Option<&Entity<Project>> {
2827 self.project.as_ref()
2828 }
2829
2830 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2831 self.workspace.as_ref()?.0.upgrade()
2832 }
2833
2834 /// Returns the workspace serialization ID if this editor should be serialized.
2835 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2836 self.workspace
2837 .as_ref()
2838 .filter(|_| self.should_serialize_buffer())
2839 .and_then(|workspace| workspace.1)
2840 }
2841
2842 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2843 self.buffer().read(cx).title(cx)
2844 }
2845
2846 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2847 let git_blame_gutter_max_author_length = self
2848 .render_git_blame_gutter(cx)
2849 .then(|| {
2850 if let Some(blame) = self.blame.as_ref() {
2851 let max_author_length =
2852 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2853 Some(max_author_length)
2854 } else {
2855 None
2856 }
2857 })
2858 .flatten();
2859
2860 EditorSnapshot {
2861 mode: self.mode.clone(),
2862 show_gutter: self.show_gutter,
2863 show_line_numbers: self.show_line_numbers,
2864 show_git_diff_gutter: self.show_git_diff_gutter,
2865 show_code_actions: self.show_code_actions,
2866 show_runnables: self.show_runnables,
2867 show_breakpoints: self.show_breakpoints,
2868 git_blame_gutter_max_author_length,
2869 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2870 placeholder_display_snapshot: self
2871 .placeholder_display_map
2872 .as_ref()
2873 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2874 scroll_anchor: self.scroll_manager.anchor(),
2875 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2876 is_focused: self.focus_handle.is_focused(window),
2877 current_line_highlight: self
2878 .current_line_highlight
2879 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2880 gutter_hovered: self.gutter_hovered,
2881 }
2882 }
2883
2884 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2885 self.buffer.read(cx).language_at(point, cx)
2886 }
2887
2888 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2889 self.buffer.read(cx).read(cx).file_at(point).cloned()
2890 }
2891
2892 pub fn active_excerpt(
2893 &self,
2894 cx: &App,
2895 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2896 self.buffer
2897 .read(cx)
2898 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2899 }
2900
2901 pub fn mode(&self) -> &EditorMode {
2902 &self.mode
2903 }
2904
2905 pub fn set_mode(&mut self, mode: EditorMode) {
2906 self.mode = mode;
2907 }
2908
2909 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2910 self.collaboration_hub.as_deref()
2911 }
2912
2913 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2914 self.collaboration_hub = Some(hub);
2915 }
2916
2917 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2918 self.in_project_search = in_project_search;
2919 }
2920
2921 pub fn set_custom_context_menu(
2922 &mut self,
2923 f: impl 'static
2924 + Fn(
2925 &mut Self,
2926 DisplayPoint,
2927 &mut Window,
2928 &mut Context<Self>,
2929 ) -> Option<Entity<ui::ContextMenu>>,
2930 ) {
2931 self.custom_context_menu = Some(Box::new(f))
2932 }
2933
2934 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2935 self.completion_provider = provider;
2936 }
2937
2938 #[cfg(any(test, feature = "test-support"))]
2939 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2940 self.completion_provider.clone()
2941 }
2942
2943 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2944 self.semantics_provider.clone()
2945 }
2946
2947 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2948 self.semantics_provider = provider;
2949 }
2950
2951 pub fn set_edit_prediction_provider<T>(
2952 &mut self,
2953 provider: Option<Entity<T>>,
2954 window: &mut Window,
2955 cx: &mut Context<Self>,
2956 ) where
2957 T: EditPredictionProvider,
2958 {
2959 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2960 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2961 if this.focus_handle.is_focused(window) {
2962 this.update_visible_edit_prediction(window, cx);
2963 }
2964 }),
2965 provider: Arc::new(provider),
2966 });
2967 self.update_edit_prediction_settings(cx);
2968 self.refresh_edit_prediction(false, false, window, cx);
2969 }
2970
2971 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2972 self.placeholder_display_map
2973 .as_ref()
2974 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2975 }
2976
2977 pub fn set_placeholder_text(
2978 &mut self,
2979 placeholder_text: &str,
2980 window: &mut Window,
2981 cx: &mut Context<Self>,
2982 ) {
2983 let multibuffer = cx
2984 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2985
2986 let style = window.text_style();
2987
2988 self.placeholder_display_map = Some(cx.new(|cx| {
2989 DisplayMap::new(
2990 multibuffer,
2991 style.font(),
2992 style.font_size.to_pixels(window.rem_size()),
2993 None,
2994 FILE_HEADER_HEIGHT,
2995 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2996 Default::default(),
2997 DiagnosticSeverity::Off,
2998 cx,
2999 )
3000 }));
3001 cx.notify();
3002 }
3003
3004 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3005 self.cursor_shape = cursor_shape;
3006
3007 // Disrupt blink for immediate user feedback that the cursor shape has changed
3008 self.blink_manager.update(cx, BlinkManager::show_cursor);
3009
3010 cx.notify();
3011 }
3012
3013 pub fn set_current_line_highlight(
3014 &mut self,
3015 current_line_highlight: Option<CurrentLineHighlight>,
3016 ) {
3017 self.current_line_highlight = current_line_highlight;
3018 }
3019
3020 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3021 self.collapse_matches = collapse_matches;
3022 }
3023
3024 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3025 if self.collapse_matches {
3026 return range.start..range.start;
3027 }
3028 range.clone()
3029 }
3030
3031 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3032 self.display_map.read(cx).clip_at_line_ends
3033 }
3034
3035 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3036 if self.display_map.read(cx).clip_at_line_ends != clip {
3037 self.display_map
3038 .update(cx, |map, _| map.clip_at_line_ends = clip);
3039 }
3040 }
3041
3042 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3043 self.input_enabled = input_enabled;
3044 }
3045
3046 pub fn set_edit_predictions_hidden_for_vim_mode(
3047 &mut self,
3048 hidden: bool,
3049 window: &mut Window,
3050 cx: &mut Context<Self>,
3051 ) {
3052 if hidden != self.edit_predictions_hidden_for_vim_mode {
3053 self.edit_predictions_hidden_for_vim_mode = hidden;
3054 if hidden {
3055 self.update_visible_edit_prediction(window, cx);
3056 } else {
3057 self.refresh_edit_prediction(true, false, window, cx);
3058 }
3059 }
3060 }
3061
3062 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3063 self.menu_edit_predictions_policy = value;
3064 }
3065
3066 pub fn set_autoindent(&mut self, autoindent: bool) {
3067 if autoindent {
3068 self.autoindent_mode = Some(AutoindentMode::EachLine);
3069 } else {
3070 self.autoindent_mode = None;
3071 }
3072 }
3073
3074 pub fn read_only(&self, cx: &App) -> bool {
3075 self.read_only || self.buffer.read(cx).read_only()
3076 }
3077
3078 pub fn set_read_only(&mut self, read_only: bool) {
3079 self.read_only = read_only;
3080 }
3081
3082 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3083 self.use_autoclose = autoclose;
3084 }
3085
3086 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3087 self.use_auto_surround = auto_surround;
3088 }
3089
3090 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3091 self.auto_replace_emoji_shortcode = auto_replace;
3092 }
3093
3094 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3095 self.buffer_serialization = should_serialize.then(|| {
3096 BufferSerialization::new(
3097 ProjectSettings::get_global(cx)
3098 .session
3099 .restore_unsaved_buffers,
3100 )
3101 })
3102 }
3103
3104 fn should_serialize_buffer(&self) -> bool {
3105 self.buffer_serialization.is_some()
3106 }
3107
3108 pub fn toggle_edit_predictions(
3109 &mut self,
3110 _: &ToggleEditPrediction,
3111 window: &mut Window,
3112 cx: &mut Context<Self>,
3113 ) {
3114 if self.show_edit_predictions_override.is_some() {
3115 self.set_show_edit_predictions(None, window, cx);
3116 } else {
3117 let show_edit_predictions = !self.edit_predictions_enabled();
3118 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3119 }
3120 }
3121
3122 pub fn set_show_edit_predictions(
3123 &mut self,
3124 show_edit_predictions: Option<bool>,
3125 window: &mut Window,
3126 cx: &mut Context<Self>,
3127 ) {
3128 self.show_edit_predictions_override = show_edit_predictions;
3129 self.update_edit_prediction_settings(cx);
3130
3131 if let Some(false) = show_edit_predictions {
3132 self.discard_edit_prediction(false, cx);
3133 } else {
3134 self.refresh_edit_prediction(false, true, window, cx);
3135 }
3136 }
3137
3138 fn edit_predictions_disabled_in_scope(
3139 &self,
3140 buffer: &Entity<Buffer>,
3141 buffer_position: language::Anchor,
3142 cx: &App,
3143 ) -> bool {
3144 let snapshot = buffer.read(cx).snapshot();
3145 let settings = snapshot.settings_at(buffer_position, cx);
3146
3147 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3148 return false;
3149 };
3150
3151 scope.override_name().is_some_and(|scope_name| {
3152 settings
3153 .edit_predictions_disabled_in
3154 .iter()
3155 .any(|s| s == scope_name)
3156 })
3157 }
3158
3159 pub fn set_use_modal_editing(&mut self, to: bool) {
3160 self.use_modal_editing = to;
3161 }
3162
3163 pub fn use_modal_editing(&self) -> bool {
3164 self.use_modal_editing
3165 }
3166
3167 fn selections_did_change(
3168 &mut self,
3169 local: bool,
3170 old_cursor_position: &Anchor,
3171 effects: SelectionEffects,
3172 window: &mut Window,
3173 cx: &mut Context<Self>,
3174 ) {
3175 window.invalidate_character_coordinates();
3176
3177 // Copy selections to primary selection buffer
3178 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3179 if local {
3180 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3181 let buffer_handle = self.buffer.read(cx).read(cx);
3182
3183 let mut text = String::new();
3184 for (index, selection) in selections.iter().enumerate() {
3185 let text_for_selection = buffer_handle
3186 .text_for_range(selection.start..selection.end)
3187 .collect::<String>();
3188
3189 text.push_str(&text_for_selection);
3190 if index != selections.len() - 1 {
3191 text.push('\n');
3192 }
3193 }
3194
3195 if !text.is_empty() {
3196 cx.write_to_primary(ClipboardItem::new_string(text));
3197 }
3198 }
3199
3200 let selection_anchors = self.selections.disjoint_anchors_arc();
3201
3202 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3203 self.buffer.update(cx, |buffer, cx| {
3204 buffer.set_active_selections(
3205 &selection_anchors,
3206 self.selections.line_mode(),
3207 self.cursor_shape,
3208 cx,
3209 )
3210 });
3211 }
3212 let display_map = self
3213 .display_map
3214 .update(cx, |display_map, cx| display_map.snapshot(cx));
3215 let buffer = display_map.buffer_snapshot();
3216 if self.selections.count() == 1 {
3217 self.add_selections_state = None;
3218 }
3219 self.select_next_state = None;
3220 self.select_prev_state = None;
3221 self.select_syntax_node_history.try_clear();
3222 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3223 self.snippet_stack.invalidate(&selection_anchors, buffer);
3224 self.take_rename(false, window, cx);
3225
3226 let newest_selection = self.selections.newest_anchor();
3227 let new_cursor_position = newest_selection.head();
3228 let selection_start = newest_selection.start;
3229
3230 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3231 self.push_to_nav_history(
3232 *old_cursor_position,
3233 Some(new_cursor_position.to_point(buffer)),
3234 false,
3235 effects.nav_history == Some(true),
3236 cx,
3237 );
3238 }
3239
3240 if local {
3241 if let Some(buffer_id) = new_cursor_position.buffer_id {
3242 self.register_buffer(buffer_id, cx);
3243 }
3244
3245 let mut context_menu = self.context_menu.borrow_mut();
3246 let completion_menu = match context_menu.as_ref() {
3247 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3248 Some(CodeContextMenu::CodeActions(_)) => {
3249 *context_menu = None;
3250 None
3251 }
3252 None => None,
3253 };
3254 let completion_position = completion_menu.map(|menu| menu.initial_position);
3255 drop(context_menu);
3256
3257 if effects.completions
3258 && let Some(completion_position) = completion_position
3259 {
3260 let start_offset = selection_start.to_offset(buffer);
3261 let position_matches = start_offset == completion_position.to_offset(buffer);
3262 let continue_showing = if position_matches {
3263 if self.snippet_stack.is_empty() {
3264 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3265 == Some(CharKind::Word)
3266 } else {
3267 // Snippet choices can be shown even when the cursor is in whitespace.
3268 // Dismissing the menu with actions like backspace is handled by
3269 // invalidation regions.
3270 true
3271 }
3272 } else {
3273 false
3274 };
3275
3276 if continue_showing {
3277 self.open_or_update_completions_menu(None, None, false, window, cx);
3278 } else {
3279 self.hide_context_menu(window, cx);
3280 }
3281 }
3282
3283 hide_hover(self, cx);
3284
3285 if old_cursor_position.to_display_point(&display_map).row()
3286 != new_cursor_position.to_display_point(&display_map).row()
3287 {
3288 self.available_code_actions.take();
3289 }
3290 self.refresh_code_actions(window, cx);
3291 self.refresh_document_highlights(cx);
3292 refresh_linked_ranges(self, window, cx);
3293
3294 self.refresh_selected_text_highlights(false, window, cx);
3295 self.refresh_matching_bracket_highlights(window, cx);
3296 self.update_visible_edit_prediction(window, cx);
3297 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3298 self.inline_blame_popover.take();
3299 if self.git_blame_inline_enabled {
3300 self.start_inline_blame_timer(window, cx);
3301 }
3302 }
3303
3304 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3305 cx.emit(EditorEvent::SelectionsChanged { local });
3306
3307 let selections = &self.selections.disjoint_anchors_arc();
3308 if selections.len() == 1 {
3309 cx.emit(SearchEvent::ActiveMatchChanged)
3310 }
3311 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3312 let inmemory_selections = selections
3313 .iter()
3314 .map(|s| {
3315 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3316 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3317 })
3318 .collect();
3319 self.update_restoration_data(cx, |data| {
3320 data.selections = inmemory_selections;
3321 });
3322
3323 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3324 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3325 {
3326 let snapshot = self.buffer().read(cx).snapshot(cx);
3327 let selections = selections.clone();
3328 let background_executor = cx.background_executor().clone();
3329 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3330 self.serialize_selections = cx.background_spawn(async move {
3331 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3332 let db_selections = selections
3333 .iter()
3334 .map(|selection| {
3335 (
3336 selection.start.to_offset(&snapshot),
3337 selection.end.to_offset(&snapshot),
3338 )
3339 })
3340 .collect();
3341
3342 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3343 .await
3344 .with_context(|| {
3345 format!(
3346 "persisting editor selections for editor {editor_id}, \
3347 workspace {workspace_id:?}"
3348 )
3349 })
3350 .log_err();
3351 });
3352 }
3353 }
3354
3355 cx.notify();
3356 }
3357
3358 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3359 use text::ToOffset as _;
3360 use text::ToPoint as _;
3361
3362 if self.mode.is_minimap()
3363 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3364 {
3365 return;
3366 }
3367
3368 if !self.buffer().read(cx).is_singleton() {
3369 return;
3370 }
3371
3372 let display_snapshot = self
3373 .display_map
3374 .update(cx, |display_map, cx| display_map.snapshot(cx));
3375 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3376 return;
3377 };
3378 let inmemory_folds = display_snapshot
3379 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3380 .map(|fold| {
3381 fold.range.start.text_anchor.to_point(&snapshot)
3382 ..fold.range.end.text_anchor.to_point(&snapshot)
3383 })
3384 .collect();
3385 self.update_restoration_data(cx, |data| {
3386 data.folds = inmemory_folds;
3387 });
3388
3389 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3390 return;
3391 };
3392 let background_executor = cx.background_executor().clone();
3393 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3394 let db_folds = display_snapshot
3395 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3396 .map(|fold| {
3397 (
3398 fold.range.start.text_anchor.to_offset(&snapshot),
3399 fold.range.end.text_anchor.to_offset(&snapshot),
3400 )
3401 })
3402 .collect();
3403 self.serialize_folds = cx.background_spawn(async move {
3404 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3405 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3406 .await
3407 .with_context(|| {
3408 format!(
3409 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3410 )
3411 })
3412 .log_err();
3413 });
3414 }
3415
3416 pub fn sync_selections(
3417 &mut self,
3418 other: Entity<Editor>,
3419 cx: &mut Context<Self>,
3420 ) -> gpui::Subscription {
3421 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3422 if !other_selections.is_empty() {
3423 self.selections
3424 .change_with(&self.display_snapshot(cx), |selections| {
3425 selections.select_anchors(other_selections);
3426 });
3427 }
3428
3429 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3430 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3431 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3432 if other_selections.is_empty() {
3433 return;
3434 }
3435 let snapshot = this.display_snapshot(cx);
3436 this.selections.change_with(&snapshot, |selections| {
3437 selections.select_anchors(other_selections);
3438 });
3439 }
3440 });
3441
3442 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3443 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3444 let these_selections = this.selections.disjoint_anchors().to_vec();
3445 if these_selections.is_empty() {
3446 return;
3447 }
3448 other.update(cx, |other_editor, cx| {
3449 let snapshot = other_editor.display_snapshot(cx);
3450 other_editor
3451 .selections
3452 .change_with(&snapshot, |selections| {
3453 selections.select_anchors(these_selections);
3454 })
3455 });
3456 }
3457 });
3458
3459 Subscription::join(other_subscription, this_subscription)
3460 }
3461
3462 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3463 if self.buffer().read(cx).is_singleton() {
3464 return;
3465 }
3466 let snapshot = self.buffer.read(cx).snapshot(cx);
3467 let buffer_ids: HashSet<BufferId> = self
3468 .selections
3469 .disjoint_anchor_ranges()
3470 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3471 .collect();
3472 for buffer_id in buffer_ids {
3473 self.unfold_buffer(buffer_id, cx);
3474 }
3475 }
3476
3477 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3478 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3479 /// effects of selection change occur at the end of the transaction.
3480 pub fn change_selections<R>(
3481 &mut self,
3482 effects: SelectionEffects,
3483 window: &mut Window,
3484 cx: &mut Context<Self>,
3485 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3486 ) -> R {
3487 let snapshot = self.display_snapshot(cx);
3488 if let Some(state) = &mut self.deferred_selection_effects_state {
3489 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3490 state.effects.completions = effects.completions;
3491 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3492 let (changed, result) = self.selections.change_with(&snapshot, change);
3493 state.changed |= changed;
3494 return result;
3495 }
3496 let mut state = DeferredSelectionEffectsState {
3497 changed: false,
3498 effects,
3499 old_cursor_position: self.selections.newest_anchor().head(),
3500 history_entry: SelectionHistoryEntry {
3501 selections: self.selections.disjoint_anchors_arc(),
3502 select_next_state: self.select_next_state.clone(),
3503 select_prev_state: self.select_prev_state.clone(),
3504 add_selections_state: self.add_selections_state.clone(),
3505 },
3506 };
3507 let (changed, result) = self.selections.change_with(&snapshot, change);
3508 state.changed = state.changed || changed;
3509 if self.defer_selection_effects {
3510 self.deferred_selection_effects_state = Some(state);
3511 } else {
3512 self.apply_selection_effects(state, window, cx);
3513 }
3514 result
3515 }
3516
3517 /// Defers the effects of selection change, so that the effects of multiple calls to
3518 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3519 /// to selection history and the state of popovers based on selection position aren't
3520 /// erroneously updated.
3521 pub fn with_selection_effects_deferred<R>(
3522 &mut self,
3523 window: &mut Window,
3524 cx: &mut Context<Self>,
3525 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3526 ) -> R {
3527 let already_deferred = self.defer_selection_effects;
3528 self.defer_selection_effects = true;
3529 let result = update(self, window, cx);
3530 if !already_deferred {
3531 self.defer_selection_effects = false;
3532 if let Some(state) = self.deferred_selection_effects_state.take() {
3533 self.apply_selection_effects(state, window, cx);
3534 }
3535 }
3536 result
3537 }
3538
3539 fn apply_selection_effects(
3540 &mut self,
3541 state: DeferredSelectionEffectsState,
3542 window: &mut Window,
3543 cx: &mut Context<Self>,
3544 ) {
3545 if state.changed {
3546 self.selection_history.push(state.history_entry);
3547
3548 if let Some(autoscroll) = state.effects.scroll {
3549 self.request_autoscroll(autoscroll, cx);
3550 }
3551
3552 let old_cursor_position = &state.old_cursor_position;
3553
3554 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3555
3556 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3557 self.show_signature_help(&ShowSignatureHelp, window, cx);
3558 }
3559 }
3560 }
3561
3562 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3563 where
3564 I: IntoIterator<Item = (Range<S>, T)>,
3565 S: ToOffset,
3566 T: Into<Arc<str>>,
3567 {
3568 if self.read_only(cx) {
3569 return;
3570 }
3571
3572 self.buffer
3573 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3574 }
3575
3576 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3577 where
3578 I: IntoIterator<Item = (Range<S>, T)>,
3579 S: ToOffset,
3580 T: Into<Arc<str>>,
3581 {
3582 if self.read_only(cx) {
3583 return;
3584 }
3585
3586 self.buffer.update(cx, |buffer, cx| {
3587 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3588 });
3589 }
3590
3591 pub fn edit_with_block_indent<I, S, T>(
3592 &mut self,
3593 edits: I,
3594 original_indent_columns: Vec<Option<u32>>,
3595 cx: &mut Context<Self>,
3596 ) where
3597 I: IntoIterator<Item = (Range<S>, T)>,
3598 S: ToOffset,
3599 T: Into<Arc<str>>,
3600 {
3601 if self.read_only(cx) {
3602 return;
3603 }
3604
3605 self.buffer.update(cx, |buffer, cx| {
3606 buffer.edit(
3607 edits,
3608 Some(AutoindentMode::Block {
3609 original_indent_columns,
3610 }),
3611 cx,
3612 )
3613 });
3614 }
3615
3616 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3617 self.hide_context_menu(window, cx);
3618
3619 match phase {
3620 SelectPhase::Begin {
3621 position,
3622 add,
3623 click_count,
3624 } => self.begin_selection(position, add, click_count, window, cx),
3625 SelectPhase::BeginColumnar {
3626 position,
3627 goal_column,
3628 reset,
3629 mode,
3630 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3631 SelectPhase::Extend {
3632 position,
3633 click_count,
3634 } => self.extend_selection(position, click_count, window, cx),
3635 SelectPhase::Update {
3636 position,
3637 goal_column,
3638 scroll_delta,
3639 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3640 SelectPhase::End => self.end_selection(window, cx),
3641 }
3642 }
3643
3644 fn extend_selection(
3645 &mut self,
3646 position: DisplayPoint,
3647 click_count: usize,
3648 window: &mut Window,
3649 cx: &mut Context<Self>,
3650 ) {
3651 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3652 let tail = self.selections.newest::<usize>(&display_map).tail();
3653 let click_count = click_count.max(match self.selections.select_mode() {
3654 SelectMode::Character => 1,
3655 SelectMode::Word(_) => 2,
3656 SelectMode::Line(_) => 3,
3657 SelectMode::All => 4,
3658 });
3659 self.begin_selection(position, false, click_count, window, cx);
3660
3661 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3662
3663 let current_selection = match self.selections.select_mode() {
3664 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3665 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3666 };
3667
3668 let mut pending_selection = self
3669 .selections
3670 .pending_anchor()
3671 .cloned()
3672 .expect("extend_selection not called with pending selection");
3673
3674 if pending_selection
3675 .start
3676 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3677 == Ordering::Greater
3678 {
3679 pending_selection.start = current_selection.start;
3680 }
3681 if pending_selection
3682 .end
3683 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3684 == Ordering::Less
3685 {
3686 pending_selection.end = current_selection.end;
3687 pending_selection.reversed = true;
3688 }
3689
3690 let mut pending_mode = self.selections.pending_mode().unwrap();
3691 match &mut pending_mode {
3692 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3693 _ => {}
3694 }
3695
3696 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3697 SelectionEffects::scroll(Autoscroll::fit())
3698 } else {
3699 SelectionEffects::no_scroll()
3700 };
3701
3702 self.change_selections(effects, window, cx, |s| {
3703 s.set_pending(pending_selection.clone(), pending_mode);
3704 s.set_is_extending(true);
3705 });
3706 }
3707
3708 fn begin_selection(
3709 &mut self,
3710 position: DisplayPoint,
3711 add: bool,
3712 click_count: usize,
3713 window: &mut Window,
3714 cx: &mut Context<Self>,
3715 ) {
3716 if !self.focus_handle.is_focused(window) {
3717 self.last_focused_descendant = None;
3718 window.focus(&self.focus_handle);
3719 }
3720
3721 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3722 let buffer = display_map.buffer_snapshot();
3723 let position = display_map.clip_point(position, Bias::Left);
3724
3725 let start;
3726 let end;
3727 let mode;
3728 let mut auto_scroll;
3729 match click_count {
3730 1 => {
3731 start = buffer.anchor_before(position.to_point(&display_map));
3732 end = start;
3733 mode = SelectMode::Character;
3734 auto_scroll = true;
3735 }
3736 2 => {
3737 let position = display_map
3738 .clip_point(position, Bias::Left)
3739 .to_offset(&display_map, Bias::Left);
3740 let (range, _) = buffer.surrounding_word(position, None);
3741 start = buffer.anchor_before(range.start);
3742 end = buffer.anchor_before(range.end);
3743 mode = SelectMode::Word(start..end);
3744 auto_scroll = true;
3745 }
3746 3 => {
3747 let position = display_map
3748 .clip_point(position, Bias::Left)
3749 .to_point(&display_map);
3750 let line_start = display_map.prev_line_boundary(position).0;
3751 let next_line_start = buffer.clip_point(
3752 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3753 Bias::Left,
3754 );
3755 start = buffer.anchor_before(line_start);
3756 end = buffer.anchor_before(next_line_start);
3757 mode = SelectMode::Line(start..end);
3758 auto_scroll = true;
3759 }
3760 _ => {
3761 start = buffer.anchor_before(0);
3762 end = buffer.anchor_before(buffer.len());
3763 mode = SelectMode::All;
3764 auto_scroll = false;
3765 }
3766 }
3767 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3768
3769 let point_to_delete: Option<usize> = {
3770 let selected_points: Vec<Selection<Point>> =
3771 self.selections.disjoint_in_range(start..end, &display_map);
3772
3773 if !add || click_count > 1 {
3774 None
3775 } else if !selected_points.is_empty() {
3776 Some(selected_points[0].id)
3777 } else {
3778 let clicked_point_already_selected =
3779 self.selections.disjoint_anchors().iter().find(|selection| {
3780 selection.start.to_point(buffer) == start.to_point(buffer)
3781 || selection.end.to_point(buffer) == end.to_point(buffer)
3782 });
3783
3784 clicked_point_already_selected.map(|selection| selection.id)
3785 }
3786 };
3787
3788 let selections_count = self.selections.count();
3789 let effects = if auto_scroll {
3790 SelectionEffects::default()
3791 } else {
3792 SelectionEffects::no_scroll()
3793 };
3794
3795 self.change_selections(effects, window, cx, |s| {
3796 if let Some(point_to_delete) = point_to_delete {
3797 s.delete(point_to_delete);
3798
3799 if selections_count == 1 {
3800 s.set_pending_anchor_range(start..end, mode);
3801 }
3802 } else {
3803 if !add {
3804 s.clear_disjoint();
3805 }
3806
3807 s.set_pending_anchor_range(start..end, mode);
3808 }
3809 });
3810 }
3811
3812 fn begin_columnar_selection(
3813 &mut self,
3814 position: DisplayPoint,
3815 goal_column: u32,
3816 reset: bool,
3817 mode: ColumnarMode,
3818 window: &mut Window,
3819 cx: &mut Context<Self>,
3820 ) {
3821 if !self.focus_handle.is_focused(window) {
3822 self.last_focused_descendant = None;
3823 window.focus(&self.focus_handle);
3824 }
3825
3826 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3827
3828 if reset {
3829 let pointer_position = display_map
3830 .buffer_snapshot()
3831 .anchor_before(position.to_point(&display_map));
3832
3833 self.change_selections(
3834 SelectionEffects::scroll(Autoscroll::newest()),
3835 window,
3836 cx,
3837 |s| {
3838 s.clear_disjoint();
3839 s.set_pending_anchor_range(
3840 pointer_position..pointer_position,
3841 SelectMode::Character,
3842 );
3843 },
3844 );
3845 };
3846
3847 let tail = self.selections.newest::<Point>(&display_map).tail();
3848 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3849 self.columnar_selection_state = match mode {
3850 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3851 selection_tail: selection_anchor,
3852 display_point: if reset {
3853 if position.column() != goal_column {
3854 Some(DisplayPoint::new(position.row(), goal_column))
3855 } else {
3856 None
3857 }
3858 } else {
3859 None
3860 },
3861 }),
3862 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3863 selection_tail: selection_anchor,
3864 }),
3865 };
3866
3867 if !reset {
3868 self.select_columns(position, goal_column, &display_map, window, cx);
3869 }
3870 }
3871
3872 fn update_selection(
3873 &mut self,
3874 position: DisplayPoint,
3875 goal_column: u32,
3876 scroll_delta: gpui::Point<f32>,
3877 window: &mut Window,
3878 cx: &mut Context<Self>,
3879 ) {
3880 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3881
3882 if self.columnar_selection_state.is_some() {
3883 self.select_columns(position, goal_column, &display_map, window, cx);
3884 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3885 let buffer = display_map.buffer_snapshot();
3886 let head;
3887 let tail;
3888 let mode = self.selections.pending_mode().unwrap();
3889 match &mode {
3890 SelectMode::Character => {
3891 head = position.to_point(&display_map);
3892 tail = pending.tail().to_point(buffer);
3893 }
3894 SelectMode::Word(original_range) => {
3895 let offset = display_map
3896 .clip_point(position, Bias::Left)
3897 .to_offset(&display_map, Bias::Left);
3898 let original_range = original_range.to_offset(buffer);
3899
3900 let head_offset = if buffer.is_inside_word(offset, None)
3901 || original_range.contains(&offset)
3902 {
3903 let (word_range, _) = buffer.surrounding_word(offset, None);
3904 if word_range.start < original_range.start {
3905 word_range.start
3906 } else {
3907 word_range.end
3908 }
3909 } else {
3910 offset
3911 };
3912
3913 head = head_offset.to_point(buffer);
3914 if head_offset <= original_range.start {
3915 tail = original_range.end.to_point(buffer);
3916 } else {
3917 tail = original_range.start.to_point(buffer);
3918 }
3919 }
3920 SelectMode::Line(original_range) => {
3921 let original_range = original_range.to_point(display_map.buffer_snapshot());
3922
3923 let position = display_map
3924 .clip_point(position, Bias::Left)
3925 .to_point(&display_map);
3926 let line_start = display_map.prev_line_boundary(position).0;
3927 let next_line_start = buffer.clip_point(
3928 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3929 Bias::Left,
3930 );
3931
3932 if line_start < original_range.start {
3933 head = line_start
3934 } else {
3935 head = next_line_start
3936 }
3937
3938 if head <= original_range.start {
3939 tail = original_range.end;
3940 } else {
3941 tail = original_range.start;
3942 }
3943 }
3944 SelectMode::All => {
3945 return;
3946 }
3947 };
3948
3949 if head < tail {
3950 pending.start = buffer.anchor_before(head);
3951 pending.end = buffer.anchor_before(tail);
3952 pending.reversed = true;
3953 } else {
3954 pending.start = buffer.anchor_before(tail);
3955 pending.end = buffer.anchor_before(head);
3956 pending.reversed = false;
3957 }
3958
3959 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3960 s.set_pending(pending.clone(), mode);
3961 });
3962 } else {
3963 log::error!("update_selection dispatched with no pending selection");
3964 return;
3965 }
3966
3967 self.apply_scroll_delta(scroll_delta, window, cx);
3968 cx.notify();
3969 }
3970
3971 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3972 self.columnar_selection_state.take();
3973 if let Some(pending_mode) = self.selections.pending_mode() {
3974 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3975 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3976 s.select(selections);
3977 s.clear_pending();
3978 if s.is_extending() {
3979 s.set_is_extending(false);
3980 } else {
3981 s.set_select_mode(pending_mode);
3982 }
3983 });
3984 }
3985 }
3986
3987 fn select_columns(
3988 &mut self,
3989 head: DisplayPoint,
3990 goal_column: u32,
3991 display_map: &DisplaySnapshot,
3992 window: &mut Window,
3993 cx: &mut Context<Self>,
3994 ) {
3995 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3996 return;
3997 };
3998
3999 let tail = match columnar_state {
4000 ColumnarSelectionState::FromMouse {
4001 selection_tail,
4002 display_point,
4003 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4004 ColumnarSelectionState::FromSelection { selection_tail } => {
4005 selection_tail.to_display_point(display_map)
4006 }
4007 };
4008
4009 let start_row = cmp::min(tail.row(), head.row());
4010 let end_row = cmp::max(tail.row(), head.row());
4011 let start_column = cmp::min(tail.column(), goal_column);
4012 let end_column = cmp::max(tail.column(), goal_column);
4013 let reversed = start_column < tail.column();
4014
4015 let selection_ranges = (start_row.0..=end_row.0)
4016 .map(DisplayRow)
4017 .filter_map(|row| {
4018 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4019 || start_column <= display_map.line_len(row))
4020 && !display_map.is_block_line(row)
4021 {
4022 let start = display_map
4023 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4024 .to_point(display_map);
4025 let end = display_map
4026 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4027 .to_point(display_map);
4028 if reversed {
4029 Some(end..start)
4030 } else {
4031 Some(start..end)
4032 }
4033 } else {
4034 None
4035 }
4036 })
4037 .collect::<Vec<_>>();
4038 if selection_ranges.is_empty() {
4039 return;
4040 }
4041
4042 let ranges = match columnar_state {
4043 ColumnarSelectionState::FromMouse { .. } => {
4044 let mut non_empty_ranges = selection_ranges
4045 .iter()
4046 .filter(|selection_range| selection_range.start != selection_range.end)
4047 .peekable();
4048 if non_empty_ranges.peek().is_some() {
4049 non_empty_ranges.cloned().collect()
4050 } else {
4051 selection_ranges
4052 }
4053 }
4054 _ => selection_ranges,
4055 };
4056
4057 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4058 s.select_ranges(ranges);
4059 });
4060 cx.notify();
4061 }
4062
4063 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4064 self.selections
4065 .all_adjusted(snapshot)
4066 .iter()
4067 .any(|selection| !selection.is_empty())
4068 }
4069
4070 pub fn has_pending_nonempty_selection(&self) -> bool {
4071 let pending_nonempty_selection = match self.selections.pending_anchor() {
4072 Some(Selection { start, end, .. }) => start != end,
4073 None => false,
4074 };
4075
4076 pending_nonempty_selection
4077 || (self.columnar_selection_state.is_some()
4078 && self.selections.disjoint_anchors().len() > 1)
4079 }
4080
4081 pub fn has_pending_selection(&self) -> bool {
4082 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4083 }
4084
4085 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4086 self.selection_mark_mode = false;
4087 self.selection_drag_state = SelectionDragState::None;
4088
4089 if self.dismiss_menus_and_popups(true, window, cx) {
4090 cx.notify();
4091 return;
4092 }
4093 if self.clear_expanded_diff_hunks(cx) {
4094 cx.notify();
4095 return;
4096 }
4097 if self.show_git_blame_gutter {
4098 self.show_git_blame_gutter = false;
4099 cx.notify();
4100 return;
4101 }
4102
4103 if self.mode.is_full()
4104 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4105 {
4106 cx.notify();
4107 return;
4108 }
4109
4110 cx.propagate();
4111 }
4112
4113 pub fn dismiss_menus_and_popups(
4114 &mut self,
4115 is_user_requested: bool,
4116 window: &mut Window,
4117 cx: &mut Context<Self>,
4118 ) -> bool {
4119 if self.take_rename(false, window, cx).is_some() {
4120 return true;
4121 }
4122
4123 if self.hide_blame_popover(true, cx) {
4124 return true;
4125 }
4126
4127 if hide_hover(self, cx) {
4128 return true;
4129 }
4130
4131 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
4132 return true;
4133 }
4134
4135 if self.hide_context_menu(window, cx).is_some() {
4136 return true;
4137 }
4138
4139 if self.mouse_context_menu.take().is_some() {
4140 return true;
4141 }
4142
4143 if is_user_requested && self.discard_edit_prediction(true, cx) {
4144 return true;
4145 }
4146
4147 if self.snippet_stack.pop().is_some() {
4148 return true;
4149 }
4150
4151 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4152 self.dismiss_diagnostics(cx);
4153 return true;
4154 }
4155
4156 false
4157 }
4158
4159 fn linked_editing_ranges_for(
4160 &self,
4161 selection: Range<text::Anchor>,
4162 cx: &App,
4163 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4164 if self.linked_edit_ranges.is_empty() {
4165 return None;
4166 }
4167 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4168 selection.end.buffer_id.and_then(|end_buffer_id| {
4169 if selection.start.buffer_id != Some(end_buffer_id) {
4170 return None;
4171 }
4172 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4173 let snapshot = buffer.read(cx).snapshot();
4174 self.linked_edit_ranges
4175 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4176 .map(|ranges| (ranges, snapshot, buffer))
4177 })?;
4178 use text::ToOffset as TO;
4179 // find offset from the start of current range to current cursor position
4180 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4181
4182 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4183 let start_difference = start_offset - start_byte_offset;
4184 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4185 let end_difference = end_offset - start_byte_offset;
4186 // Current range has associated linked ranges.
4187 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4188 for range in linked_ranges.iter() {
4189 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4190 let end_offset = start_offset + end_difference;
4191 let start_offset = start_offset + start_difference;
4192 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4193 continue;
4194 }
4195 if self.selections.disjoint_anchor_ranges().any(|s| {
4196 if s.start.buffer_id != selection.start.buffer_id
4197 || s.end.buffer_id != selection.end.buffer_id
4198 {
4199 return false;
4200 }
4201 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4202 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4203 }) {
4204 continue;
4205 }
4206 let start = buffer_snapshot.anchor_after(start_offset);
4207 let end = buffer_snapshot.anchor_after(end_offset);
4208 linked_edits
4209 .entry(buffer.clone())
4210 .or_default()
4211 .push(start..end);
4212 }
4213 Some(linked_edits)
4214 }
4215
4216 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4217 let text: Arc<str> = text.into();
4218
4219 if self.read_only(cx) {
4220 return;
4221 }
4222
4223 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4224
4225 self.unfold_buffers_with_selections(cx);
4226
4227 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4228 let mut bracket_inserted = false;
4229 let mut edits = Vec::new();
4230 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4231 let mut new_selections = Vec::with_capacity(selections.len());
4232 let mut new_autoclose_regions = Vec::new();
4233 let snapshot = self.buffer.read(cx).read(cx);
4234 let mut clear_linked_edit_ranges = false;
4235
4236 for (selection, autoclose_region) in
4237 self.selections_with_autoclose_regions(selections, &snapshot)
4238 {
4239 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4240 // Determine if the inserted text matches the opening or closing
4241 // bracket of any of this language's bracket pairs.
4242 let mut bracket_pair = None;
4243 let mut is_bracket_pair_start = false;
4244 let mut is_bracket_pair_end = false;
4245 if !text.is_empty() {
4246 let mut bracket_pair_matching_end = None;
4247 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4248 // and they are removing the character that triggered IME popup.
4249 for (pair, enabled) in scope.brackets() {
4250 if !pair.close && !pair.surround {
4251 continue;
4252 }
4253
4254 if enabled && pair.start.ends_with(text.as_ref()) {
4255 let prefix_len = pair.start.len() - text.len();
4256 let preceding_text_matches_prefix = prefix_len == 0
4257 || (selection.start.column >= (prefix_len as u32)
4258 && snapshot.contains_str_at(
4259 Point::new(
4260 selection.start.row,
4261 selection.start.column - (prefix_len as u32),
4262 ),
4263 &pair.start[..prefix_len],
4264 ));
4265 if preceding_text_matches_prefix {
4266 bracket_pair = Some(pair.clone());
4267 is_bracket_pair_start = true;
4268 break;
4269 }
4270 }
4271 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4272 {
4273 // take first bracket pair matching end, but don't break in case a later bracket
4274 // pair matches start
4275 bracket_pair_matching_end = Some(pair.clone());
4276 }
4277 }
4278 if let Some(end) = bracket_pair_matching_end
4279 && bracket_pair.is_none()
4280 {
4281 bracket_pair = Some(end);
4282 is_bracket_pair_end = true;
4283 }
4284 }
4285
4286 if let Some(bracket_pair) = bracket_pair {
4287 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4288 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4289 let auto_surround =
4290 self.use_auto_surround && snapshot_settings.use_auto_surround;
4291 if selection.is_empty() {
4292 if is_bracket_pair_start {
4293 // If the inserted text is a suffix of an opening bracket and the
4294 // selection is preceded by the rest of the opening bracket, then
4295 // insert the closing bracket.
4296 let following_text_allows_autoclose = snapshot
4297 .chars_at(selection.start)
4298 .next()
4299 .is_none_or(|c| scope.should_autoclose_before(c));
4300
4301 let preceding_text_allows_autoclose = selection.start.column == 0
4302 || snapshot
4303 .reversed_chars_at(selection.start)
4304 .next()
4305 .is_none_or(|c| {
4306 bracket_pair.start != bracket_pair.end
4307 || !snapshot
4308 .char_classifier_at(selection.start)
4309 .is_word(c)
4310 });
4311
4312 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4313 && bracket_pair.start.len() == 1
4314 {
4315 let target = bracket_pair.start.chars().next().unwrap();
4316 let current_line_count = snapshot
4317 .reversed_chars_at(selection.start)
4318 .take_while(|&c| c != '\n')
4319 .filter(|&c| c == target)
4320 .count();
4321 current_line_count % 2 == 1
4322 } else {
4323 false
4324 };
4325
4326 if autoclose
4327 && bracket_pair.close
4328 && following_text_allows_autoclose
4329 && preceding_text_allows_autoclose
4330 && !is_closing_quote
4331 {
4332 let anchor = snapshot.anchor_before(selection.end);
4333 new_selections.push((selection.map(|_| anchor), text.len()));
4334 new_autoclose_regions.push((
4335 anchor,
4336 text.len(),
4337 selection.id,
4338 bracket_pair.clone(),
4339 ));
4340 edits.push((
4341 selection.range(),
4342 format!("{}{}", text, bracket_pair.end).into(),
4343 ));
4344 bracket_inserted = true;
4345 continue;
4346 }
4347 }
4348
4349 if let Some(region) = autoclose_region {
4350 // If the selection is followed by an auto-inserted closing bracket,
4351 // then don't insert that closing bracket again; just move the selection
4352 // past the closing bracket.
4353 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4354 && text.as_ref() == region.pair.end.as_str()
4355 && snapshot.contains_str_at(region.range.end, text.as_ref());
4356 if should_skip {
4357 let anchor = snapshot.anchor_after(selection.end);
4358 new_selections
4359 .push((selection.map(|_| anchor), region.pair.end.len()));
4360 continue;
4361 }
4362 }
4363
4364 let always_treat_brackets_as_autoclosed = snapshot
4365 .language_settings_at(selection.start, cx)
4366 .always_treat_brackets_as_autoclosed;
4367 if always_treat_brackets_as_autoclosed
4368 && is_bracket_pair_end
4369 && snapshot.contains_str_at(selection.end, text.as_ref())
4370 {
4371 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4372 // and the inserted text is a closing bracket and the selection is followed
4373 // by the closing bracket then move the selection past the closing bracket.
4374 let anchor = snapshot.anchor_after(selection.end);
4375 new_selections.push((selection.map(|_| anchor), text.len()));
4376 continue;
4377 }
4378 }
4379 // If an opening bracket is 1 character long and is typed while
4380 // text is selected, then surround that text with the bracket pair.
4381 else if auto_surround
4382 && bracket_pair.surround
4383 && is_bracket_pair_start
4384 && bracket_pair.start.chars().count() == 1
4385 {
4386 edits.push((selection.start..selection.start, text.clone()));
4387 edits.push((
4388 selection.end..selection.end,
4389 bracket_pair.end.as_str().into(),
4390 ));
4391 bracket_inserted = true;
4392 new_selections.push((
4393 Selection {
4394 id: selection.id,
4395 start: snapshot.anchor_after(selection.start),
4396 end: snapshot.anchor_before(selection.end),
4397 reversed: selection.reversed,
4398 goal: selection.goal,
4399 },
4400 0,
4401 ));
4402 continue;
4403 }
4404 }
4405 }
4406
4407 if self.auto_replace_emoji_shortcode
4408 && selection.is_empty()
4409 && text.as_ref().ends_with(':')
4410 && let Some(possible_emoji_short_code) =
4411 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4412 && !possible_emoji_short_code.is_empty()
4413 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4414 {
4415 let emoji_shortcode_start = Point::new(
4416 selection.start.row,
4417 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4418 );
4419
4420 // Remove shortcode from buffer
4421 edits.push((
4422 emoji_shortcode_start..selection.start,
4423 "".to_string().into(),
4424 ));
4425 new_selections.push((
4426 Selection {
4427 id: selection.id,
4428 start: snapshot.anchor_after(emoji_shortcode_start),
4429 end: snapshot.anchor_before(selection.start),
4430 reversed: selection.reversed,
4431 goal: selection.goal,
4432 },
4433 0,
4434 ));
4435
4436 // Insert emoji
4437 let selection_start_anchor = snapshot.anchor_after(selection.start);
4438 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4439 edits.push((selection.start..selection.end, emoji.to_string().into()));
4440
4441 continue;
4442 }
4443
4444 // If not handling any auto-close operation, then just replace the selected
4445 // text with the given input and move the selection to the end of the
4446 // newly inserted text.
4447 let anchor = snapshot.anchor_after(selection.end);
4448 if !self.linked_edit_ranges.is_empty() {
4449 let start_anchor = snapshot.anchor_before(selection.start);
4450
4451 let is_word_char = text.chars().next().is_none_or(|char| {
4452 let classifier = snapshot
4453 .char_classifier_at(start_anchor.to_offset(&snapshot))
4454 .scope_context(Some(CharScopeContext::LinkedEdit));
4455 classifier.is_word(char)
4456 });
4457
4458 if is_word_char {
4459 if let Some(ranges) = self
4460 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4461 {
4462 for (buffer, edits) in ranges {
4463 linked_edits
4464 .entry(buffer.clone())
4465 .or_default()
4466 .extend(edits.into_iter().map(|range| (range, text.clone())));
4467 }
4468 }
4469 } else {
4470 clear_linked_edit_ranges = true;
4471 }
4472 }
4473
4474 new_selections.push((selection.map(|_| anchor), 0));
4475 edits.push((selection.start..selection.end, text.clone()));
4476 }
4477
4478 drop(snapshot);
4479
4480 self.transact(window, cx, |this, window, cx| {
4481 if clear_linked_edit_ranges {
4482 this.linked_edit_ranges.clear();
4483 }
4484 let initial_buffer_versions =
4485 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4486
4487 this.buffer.update(cx, |buffer, cx| {
4488 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4489 });
4490 for (buffer, edits) in linked_edits {
4491 buffer.update(cx, |buffer, cx| {
4492 let snapshot = buffer.snapshot();
4493 let edits = edits
4494 .into_iter()
4495 .map(|(range, text)| {
4496 use text::ToPoint as TP;
4497 let end_point = TP::to_point(&range.end, &snapshot);
4498 let start_point = TP::to_point(&range.start, &snapshot);
4499 (start_point..end_point, text)
4500 })
4501 .sorted_by_key(|(range, _)| range.start);
4502 buffer.edit(edits, None, cx);
4503 })
4504 }
4505 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4506 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4507 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4508 let new_selections =
4509 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4510 .zip(new_selection_deltas)
4511 .map(|(selection, delta)| Selection {
4512 id: selection.id,
4513 start: selection.start + delta,
4514 end: selection.end + delta,
4515 reversed: selection.reversed,
4516 goal: SelectionGoal::None,
4517 })
4518 .collect::<Vec<_>>();
4519
4520 let mut i = 0;
4521 for (position, delta, selection_id, pair) in new_autoclose_regions {
4522 let position = position.to_offset(map.buffer_snapshot()) + delta;
4523 let start = map.buffer_snapshot().anchor_before(position);
4524 let end = map.buffer_snapshot().anchor_after(position);
4525 while let Some(existing_state) = this.autoclose_regions.get(i) {
4526 match existing_state
4527 .range
4528 .start
4529 .cmp(&start, map.buffer_snapshot())
4530 {
4531 Ordering::Less => i += 1,
4532 Ordering::Greater => break,
4533 Ordering::Equal => {
4534 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4535 Ordering::Less => i += 1,
4536 Ordering::Equal => break,
4537 Ordering::Greater => break,
4538 }
4539 }
4540 }
4541 }
4542 this.autoclose_regions.insert(
4543 i,
4544 AutocloseRegion {
4545 selection_id,
4546 range: start..end,
4547 pair,
4548 },
4549 );
4550 }
4551
4552 let had_active_edit_prediction = this.has_active_edit_prediction();
4553 this.change_selections(
4554 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4555 window,
4556 cx,
4557 |s| s.select(new_selections),
4558 );
4559
4560 if !bracket_inserted
4561 && let Some(on_type_format_task) =
4562 this.trigger_on_type_formatting(text.to_string(), window, cx)
4563 {
4564 on_type_format_task.detach_and_log_err(cx);
4565 }
4566
4567 let editor_settings = EditorSettings::get_global(cx);
4568 if bracket_inserted
4569 && (editor_settings.auto_signature_help
4570 || editor_settings.show_signature_help_after_edits)
4571 {
4572 this.show_signature_help(&ShowSignatureHelp, window, cx);
4573 }
4574
4575 let trigger_in_words =
4576 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4577 if this.hard_wrap.is_some() {
4578 let latest: Range<Point> = this.selections.newest(&map).range();
4579 if latest.is_empty()
4580 && this
4581 .buffer()
4582 .read(cx)
4583 .snapshot(cx)
4584 .line_len(MultiBufferRow(latest.start.row))
4585 == latest.start.column
4586 {
4587 this.rewrap_impl(
4588 RewrapOptions {
4589 override_language_settings: true,
4590 preserve_existing_whitespace: true,
4591 },
4592 cx,
4593 )
4594 }
4595 }
4596 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4597 refresh_linked_ranges(this, window, cx);
4598 this.refresh_edit_prediction(true, false, window, cx);
4599 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4600 });
4601 }
4602
4603 fn find_possible_emoji_shortcode_at_position(
4604 snapshot: &MultiBufferSnapshot,
4605 position: Point,
4606 ) -> Option<String> {
4607 let mut chars = Vec::new();
4608 let mut found_colon = false;
4609 for char in snapshot.reversed_chars_at(position).take(100) {
4610 // Found a possible emoji shortcode in the middle of the buffer
4611 if found_colon {
4612 if char.is_whitespace() {
4613 chars.reverse();
4614 return Some(chars.iter().collect());
4615 }
4616 // If the previous character is not a whitespace, we are in the middle of a word
4617 // and we only want to complete the shortcode if the word is made up of other emojis
4618 let mut containing_word = String::new();
4619 for ch in snapshot
4620 .reversed_chars_at(position)
4621 .skip(chars.len() + 1)
4622 .take(100)
4623 {
4624 if ch.is_whitespace() {
4625 break;
4626 }
4627 containing_word.push(ch);
4628 }
4629 let containing_word = containing_word.chars().rev().collect::<String>();
4630 if util::word_consists_of_emojis(containing_word.as_str()) {
4631 chars.reverse();
4632 return Some(chars.iter().collect());
4633 }
4634 }
4635
4636 if char.is_whitespace() || !char.is_ascii() {
4637 return None;
4638 }
4639 if char == ':' {
4640 found_colon = true;
4641 } else {
4642 chars.push(char);
4643 }
4644 }
4645 // Found a possible emoji shortcode at the beginning of the buffer
4646 chars.reverse();
4647 Some(chars.iter().collect())
4648 }
4649
4650 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4651 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4652 self.transact(window, cx, |this, window, cx| {
4653 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4654 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4655 let multi_buffer = this.buffer.read(cx);
4656 let buffer = multi_buffer.snapshot(cx);
4657 selections
4658 .iter()
4659 .map(|selection| {
4660 let start_point = selection.start.to_point(&buffer);
4661 let mut existing_indent =
4662 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4663 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4664 let start = selection.start;
4665 let end = selection.end;
4666 let selection_is_empty = start == end;
4667 let language_scope = buffer.language_scope_at(start);
4668 let (
4669 comment_delimiter,
4670 doc_delimiter,
4671 insert_extra_newline,
4672 indent_on_newline,
4673 indent_on_extra_newline,
4674 ) = if let Some(language) = &language_scope {
4675 let mut insert_extra_newline =
4676 insert_extra_newline_brackets(&buffer, start..end, language)
4677 || insert_extra_newline_tree_sitter(&buffer, start..end);
4678
4679 // Comment extension on newline is allowed only for cursor selections
4680 let comment_delimiter = maybe!({
4681 if !selection_is_empty {
4682 return None;
4683 }
4684
4685 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4686 return None;
4687 }
4688
4689 let delimiters = language.line_comment_prefixes();
4690 let max_len_of_delimiter =
4691 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4692 let (snapshot, range) =
4693 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4694
4695 let num_of_whitespaces = snapshot
4696 .chars_for_range(range.clone())
4697 .take_while(|c| c.is_whitespace())
4698 .count();
4699 let comment_candidate = snapshot
4700 .chars_for_range(range.clone())
4701 .skip(num_of_whitespaces)
4702 .take(max_len_of_delimiter)
4703 .collect::<String>();
4704 let (delimiter, trimmed_len) = delimiters
4705 .iter()
4706 .filter_map(|delimiter| {
4707 let prefix = delimiter.trim_end();
4708 if comment_candidate.starts_with(prefix) {
4709 Some((delimiter, prefix.len()))
4710 } else {
4711 None
4712 }
4713 })
4714 .max_by_key(|(_, len)| *len)?;
4715
4716 if let Some(BlockCommentConfig {
4717 start: block_start, ..
4718 }) = language.block_comment()
4719 {
4720 let block_start_trimmed = block_start.trim_end();
4721 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4722 let line_content = snapshot
4723 .chars_for_range(range)
4724 .skip(num_of_whitespaces)
4725 .take(block_start_trimmed.len())
4726 .collect::<String>();
4727
4728 if line_content.starts_with(block_start_trimmed) {
4729 return None;
4730 }
4731 }
4732 }
4733
4734 let cursor_is_placed_after_comment_marker =
4735 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4736 if cursor_is_placed_after_comment_marker {
4737 Some(delimiter.clone())
4738 } else {
4739 None
4740 }
4741 });
4742
4743 let mut indent_on_newline = IndentSize::spaces(0);
4744 let mut indent_on_extra_newline = IndentSize::spaces(0);
4745
4746 let doc_delimiter = maybe!({
4747 if !selection_is_empty {
4748 return None;
4749 }
4750
4751 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4752 return None;
4753 }
4754
4755 let BlockCommentConfig {
4756 start: start_tag,
4757 end: end_tag,
4758 prefix: delimiter,
4759 tab_size: len,
4760 } = language.documentation_comment()?;
4761 let is_within_block_comment = buffer
4762 .language_scope_at(start_point)
4763 .is_some_and(|scope| scope.override_name() == Some("comment"));
4764 if !is_within_block_comment {
4765 return None;
4766 }
4767
4768 let (snapshot, range) =
4769 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4770
4771 let num_of_whitespaces = snapshot
4772 .chars_for_range(range.clone())
4773 .take_while(|c| c.is_whitespace())
4774 .count();
4775
4776 // 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.
4777 let column = start_point.column;
4778 let cursor_is_after_start_tag = {
4779 let start_tag_len = start_tag.len();
4780 let start_tag_line = snapshot
4781 .chars_for_range(range.clone())
4782 .skip(num_of_whitespaces)
4783 .take(start_tag_len)
4784 .collect::<String>();
4785 if start_tag_line.starts_with(start_tag.as_ref()) {
4786 num_of_whitespaces + start_tag_len <= column as usize
4787 } else {
4788 false
4789 }
4790 };
4791
4792 let cursor_is_after_delimiter = {
4793 let delimiter_trim = delimiter.trim_end();
4794 let delimiter_line = snapshot
4795 .chars_for_range(range.clone())
4796 .skip(num_of_whitespaces)
4797 .take(delimiter_trim.len())
4798 .collect::<String>();
4799 if delimiter_line.starts_with(delimiter_trim) {
4800 num_of_whitespaces + delimiter_trim.len() <= column as usize
4801 } else {
4802 false
4803 }
4804 };
4805
4806 let cursor_is_before_end_tag_if_exists = {
4807 let mut char_position = 0u32;
4808 let mut end_tag_offset = None;
4809
4810 'outer: for chunk in snapshot.text_for_range(range) {
4811 if let Some(byte_pos) = chunk.find(&**end_tag) {
4812 let chars_before_match =
4813 chunk[..byte_pos].chars().count() as u32;
4814 end_tag_offset =
4815 Some(char_position + chars_before_match);
4816 break 'outer;
4817 }
4818 char_position += chunk.chars().count() as u32;
4819 }
4820
4821 if let Some(end_tag_offset) = end_tag_offset {
4822 let cursor_is_before_end_tag = column <= end_tag_offset;
4823 if cursor_is_after_start_tag {
4824 if cursor_is_before_end_tag {
4825 insert_extra_newline = true;
4826 }
4827 let cursor_is_at_start_of_end_tag =
4828 column == end_tag_offset;
4829 if cursor_is_at_start_of_end_tag {
4830 indent_on_extra_newline.len = *len;
4831 }
4832 }
4833 cursor_is_before_end_tag
4834 } else {
4835 true
4836 }
4837 };
4838
4839 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4840 && cursor_is_before_end_tag_if_exists
4841 {
4842 if cursor_is_after_start_tag {
4843 indent_on_newline.len = *len;
4844 }
4845 Some(delimiter.clone())
4846 } else {
4847 None
4848 }
4849 });
4850
4851 (
4852 comment_delimiter,
4853 doc_delimiter,
4854 insert_extra_newline,
4855 indent_on_newline,
4856 indent_on_extra_newline,
4857 )
4858 } else {
4859 (
4860 None,
4861 None,
4862 false,
4863 IndentSize::default(),
4864 IndentSize::default(),
4865 )
4866 };
4867
4868 let prevent_auto_indent = doc_delimiter.is_some();
4869 let delimiter = comment_delimiter.or(doc_delimiter);
4870
4871 let capacity_for_delimiter =
4872 delimiter.as_deref().map(str::len).unwrap_or_default();
4873 let mut new_text = String::with_capacity(
4874 1 + capacity_for_delimiter
4875 + existing_indent.len as usize
4876 + indent_on_newline.len as usize
4877 + indent_on_extra_newline.len as usize,
4878 );
4879 new_text.push('\n');
4880 new_text.extend(existing_indent.chars());
4881 new_text.extend(indent_on_newline.chars());
4882
4883 if let Some(delimiter) = &delimiter {
4884 new_text.push_str(delimiter);
4885 }
4886
4887 if insert_extra_newline {
4888 new_text.push('\n');
4889 new_text.extend(existing_indent.chars());
4890 new_text.extend(indent_on_extra_newline.chars());
4891 }
4892
4893 let anchor = buffer.anchor_after(end);
4894 let new_selection = selection.map(|_| anchor);
4895 (
4896 ((start..end, new_text), prevent_auto_indent),
4897 (insert_extra_newline, new_selection),
4898 )
4899 })
4900 .unzip()
4901 };
4902
4903 let mut auto_indent_edits = Vec::new();
4904 let mut edits = Vec::new();
4905 for (edit, prevent_auto_indent) in edits_with_flags {
4906 if prevent_auto_indent {
4907 edits.push(edit);
4908 } else {
4909 auto_indent_edits.push(edit);
4910 }
4911 }
4912 if !edits.is_empty() {
4913 this.edit(edits, cx);
4914 }
4915 if !auto_indent_edits.is_empty() {
4916 this.edit_with_autoindent(auto_indent_edits, cx);
4917 }
4918
4919 let buffer = this.buffer.read(cx).snapshot(cx);
4920 let new_selections = selection_info
4921 .into_iter()
4922 .map(|(extra_newline_inserted, new_selection)| {
4923 let mut cursor = new_selection.end.to_point(&buffer);
4924 if extra_newline_inserted {
4925 cursor.row -= 1;
4926 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4927 }
4928 new_selection.map(|_| cursor)
4929 })
4930 .collect();
4931
4932 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4933 this.refresh_edit_prediction(true, false, window, cx);
4934 });
4935 }
4936
4937 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4938 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4939
4940 let buffer = self.buffer.read(cx);
4941 let snapshot = buffer.snapshot(cx);
4942
4943 let mut edits = Vec::new();
4944 let mut rows = Vec::new();
4945
4946 for (rows_inserted, selection) in self
4947 .selections
4948 .all_adjusted(&self.display_snapshot(cx))
4949 .into_iter()
4950 .enumerate()
4951 {
4952 let cursor = selection.head();
4953 let row = cursor.row;
4954
4955 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4956
4957 let newline = "\n".to_string();
4958 edits.push((start_of_line..start_of_line, newline));
4959
4960 rows.push(row + rows_inserted as u32);
4961 }
4962
4963 self.transact(window, cx, |editor, window, cx| {
4964 editor.edit(edits, cx);
4965
4966 editor.change_selections(Default::default(), window, cx, |s| {
4967 let mut index = 0;
4968 s.move_cursors_with(|map, _, _| {
4969 let row = rows[index];
4970 index += 1;
4971
4972 let point = Point::new(row, 0);
4973 let boundary = map.next_line_boundary(point).1;
4974 let clipped = map.clip_point(boundary, Bias::Left);
4975
4976 (clipped, SelectionGoal::None)
4977 });
4978 });
4979
4980 let mut indent_edits = Vec::new();
4981 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4982 for row in rows {
4983 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4984 for (row, indent) in indents {
4985 if indent.len == 0 {
4986 continue;
4987 }
4988
4989 let text = match indent.kind {
4990 IndentKind::Space => " ".repeat(indent.len as usize),
4991 IndentKind::Tab => "\t".repeat(indent.len as usize),
4992 };
4993 let point = Point::new(row.0, 0);
4994 indent_edits.push((point..point, text));
4995 }
4996 }
4997 editor.edit(indent_edits, cx);
4998 });
4999 }
5000
5001 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5002 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5003
5004 let buffer = self.buffer.read(cx);
5005 let snapshot = buffer.snapshot(cx);
5006
5007 let mut edits = Vec::new();
5008 let mut rows = Vec::new();
5009 let mut rows_inserted = 0;
5010
5011 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5012 let cursor = selection.head();
5013 let row = cursor.row;
5014
5015 let point = Point::new(row + 1, 0);
5016 let start_of_line = snapshot.clip_point(point, Bias::Left);
5017
5018 let newline = "\n".to_string();
5019 edits.push((start_of_line..start_of_line, newline));
5020
5021 rows_inserted += 1;
5022 rows.push(row + rows_inserted);
5023 }
5024
5025 self.transact(window, cx, |editor, window, cx| {
5026 editor.edit(edits, cx);
5027
5028 editor.change_selections(Default::default(), window, cx, |s| {
5029 let mut index = 0;
5030 s.move_cursors_with(|map, _, _| {
5031 let row = rows[index];
5032 index += 1;
5033
5034 let point = Point::new(row, 0);
5035 let boundary = map.next_line_boundary(point).1;
5036 let clipped = map.clip_point(boundary, Bias::Left);
5037
5038 (clipped, SelectionGoal::None)
5039 });
5040 });
5041
5042 let mut indent_edits = Vec::new();
5043 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5044 for row in rows {
5045 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5046 for (row, indent) in indents {
5047 if indent.len == 0 {
5048 continue;
5049 }
5050
5051 let text = match indent.kind {
5052 IndentKind::Space => " ".repeat(indent.len as usize),
5053 IndentKind::Tab => "\t".repeat(indent.len as usize),
5054 };
5055 let point = Point::new(row.0, 0);
5056 indent_edits.push((point..point, text));
5057 }
5058 }
5059 editor.edit(indent_edits, cx);
5060 });
5061 }
5062
5063 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5064 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5065 original_indent_columns: Vec::new(),
5066 });
5067 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5068 }
5069
5070 fn insert_with_autoindent_mode(
5071 &mut self,
5072 text: &str,
5073 autoindent_mode: Option<AutoindentMode>,
5074 window: &mut Window,
5075 cx: &mut Context<Self>,
5076 ) {
5077 if self.read_only(cx) {
5078 return;
5079 }
5080
5081 let text: Arc<str> = text.into();
5082 self.transact(window, cx, |this, window, cx| {
5083 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5084 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5085 let anchors = {
5086 let snapshot = buffer.read(cx);
5087 old_selections
5088 .iter()
5089 .map(|s| {
5090 let anchor = snapshot.anchor_after(s.head());
5091 s.map(|_| anchor)
5092 })
5093 .collect::<Vec<_>>()
5094 };
5095 buffer.edit(
5096 old_selections
5097 .iter()
5098 .map(|s| (s.start..s.end, text.clone())),
5099 autoindent_mode,
5100 cx,
5101 );
5102 anchors
5103 });
5104
5105 this.change_selections(Default::default(), window, cx, |s| {
5106 s.select_anchors(selection_anchors);
5107 });
5108
5109 cx.notify();
5110 });
5111 }
5112
5113 fn trigger_completion_on_input(
5114 &mut self,
5115 text: &str,
5116 trigger_in_words: bool,
5117 window: &mut Window,
5118 cx: &mut Context<Self>,
5119 ) {
5120 let completions_source = self
5121 .context_menu
5122 .borrow()
5123 .as_ref()
5124 .and_then(|menu| match menu {
5125 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5126 CodeContextMenu::CodeActions(_) => None,
5127 });
5128
5129 match completions_source {
5130 Some(CompletionsMenuSource::Words { .. }) => {
5131 self.open_or_update_completions_menu(
5132 Some(CompletionsMenuSource::Words {
5133 ignore_threshold: false,
5134 }),
5135 None,
5136 trigger_in_words,
5137 window,
5138 cx,
5139 );
5140 }
5141 _ => self.open_or_update_completions_menu(
5142 None,
5143 Some(text.to_owned()).filter(|x| !x.is_empty()),
5144 true,
5145 window,
5146 cx,
5147 ),
5148 }
5149 }
5150
5151 /// If any empty selections is touching the start of its innermost containing autoclose
5152 /// region, expand it to select the brackets.
5153 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5154 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5155 let buffer = self.buffer.read(cx).read(cx);
5156 let new_selections = self
5157 .selections_with_autoclose_regions(selections, &buffer)
5158 .map(|(mut selection, region)| {
5159 if !selection.is_empty() {
5160 return selection;
5161 }
5162
5163 if let Some(region) = region {
5164 let mut range = region.range.to_offset(&buffer);
5165 if selection.start == range.start && range.start >= region.pair.start.len() {
5166 range.start -= region.pair.start.len();
5167 if buffer.contains_str_at(range.start, ®ion.pair.start)
5168 && buffer.contains_str_at(range.end, ®ion.pair.end)
5169 {
5170 range.end += region.pair.end.len();
5171 selection.start = range.start;
5172 selection.end = range.end;
5173
5174 return selection;
5175 }
5176 }
5177 }
5178
5179 let always_treat_brackets_as_autoclosed = buffer
5180 .language_settings_at(selection.start, cx)
5181 .always_treat_brackets_as_autoclosed;
5182
5183 if !always_treat_brackets_as_autoclosed {
5184 return selection;
5185 }
5186
5187 if let Some(scope) = buffer.language_scope_at(selection.start) {
5188 for (pair, enabled) in scope.brackets() {
5189 if !enabled || !pair.close {
5190 continue;
5191 }
5192
5193 if buffer.contains_str_at(selection.start, &pair.end) {
5194 let pair_start_len = pair.start.len();
5195 if buffer.contains_str_at(
5196 selection.start.saturating_sub(pair_start_len),
5197 &pair.start,
5198 ) {
5199 selection.start -= pair_start_len;
5200 selection.end += pair.end.len();
5201
5202 return selection;
5203 }
5204 }
5205 }
5206 }
5207
5208 selection
5209 })
5210 .collect();
5211
5212 drop(buffer);
5213 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5214 selections.select(new_selections)
5215 });
5216 }
5217
5218 /// Iterate the given selections, and for each one, find the smallest surrounding
5219 /// autoclose region. This uses the ordering of the selections and the autoclose
5220 /// regions to avoid repeated comparisons.
5221 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5222 &'a self,
5223 selections: impl IntoIterator<Item = Selection<D>>,
5224 buffer: &'a MultiBufferSnapshot,
5225 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5226 let mut i = 0;
5227 let mut regions = self.autoclose_regions.as_slice();
5228 selections.into_iter().map(move |selection| {
5229 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5230
5231 let mut enclosing = None;
5232 while let Some(pair_state) = regions.get(i) {
5233 if pair_state.range.end.to_offset(buffer) < range.start {
5234 regions = ®ions[i + 1..];
5235 i = 0;
5236 } else if pair_state.range.start.to_offset(buffer) > range.end {
5237 break;
5238 } else {
5239 if pair_state.selection_id == selection.id {
5240 enclosing = Some(pair_state);
5241 }
5242 i += 1;
5243 }
5244 }
5245
5246 (selection, enclosing)
5247 })
5248 }
5249
5250 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5251 fn invalidate_autoclose_regions(
5252 &mut self,
5253 mut selections: &[Selection<Anchor>],
5254 buffer: &MultiBufferSnapshot,
5255 ) {
5256 self.autoclose_regions.retain(|state| {
5257 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5258 return false;
5259 }
5260
5261 let mut i = 0;
5262 while let Some(selection) = selections.get(i) {
5263 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5264 selections = &selections[1..];
5265 continue;
5266 }
5267 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5268 break;
5269 }
5270 if selection.id == state.selection_id {
5271 return true;
5272 } else {
5273 i += 1;
5274 }
5275 }
5276 false
5277 });
5278 }
5279
5280 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5281 let offset = position.to_offset(buffer);
5282 let (word_range, kind) =
5283 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5284 if offset > word_range.start && kind == Some(CharKind::Word) {
5285 Some(
5286 buffer
5287 .text_for_range(word_range.start..offset)
5288 .collect::<String>(),
5289 )
5290 } else {
5291 None
5292 }
5293 }
5294
5295 pub fn visible_excerpts(
5296 &self,
5297 cx: &mut Context<Editor>,
5298 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5299 let Some(project) = self.project() else {
5300 return HashMap::default();
5301 };
5302 let project = project.read(cx);
5303 let multi_buffer = self.buffer().read(cx);
5304 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5305 let multi_buffer_visible_start = self
5306 .scroll_manager
5307 .anchor()
5308 .anchor
5309 .to_point(&multi_buffer_snapshot);
5310 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5311 multi_buffer_visible_start
5312 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5313 Bias::Left,
5314 );
5315 multi_buffer_snapshot
5316 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5317 .into_iter()
5318 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5319 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5320 let buffer_file = project::File::from_dyn(buffer.file())?;
5321 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5322 let worktree_entry = buffer_worktree
5323 .read(cx)
5324 .entry_for_id(buffer_file.project_entry_id()?)?;
5325 if worktree_entry.is_ignored {
5326 None
5327 } else {
5328 Some((
5329 excerpt_id,
5330 (
5331 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5332 buffer.version().clone(),
5333 excerpt_visible_range,
5334 ),
5335 ))
5336 }
5337 })
5338 .collect()
5339 }
5340
5341 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5342 TextLayoutDetails {
5343 text_system: window.text_system().clone(),
5344 editor_style: self.style.clone().unwrap(),
5345 rem_size: window.rem_size(),
5346 scroll_anchor: self.scroll_manager.anchor(),
5347 visible_rows: self.visible_line_count(),
5348 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5349 }
5350 }
5351
5352 fn trigger_on_type_formatting(
5353 &self,
5354 input: String,
5355 window: &mut Window,
5356 cx: &mut Context<Self>,
5357 ) -> Option<Task<Result<()>>> {
5358 if input.len() != 1 {
5359 return None;
5360 }
5361
5362 let project = self.project()?;
5363 let position = self.selections.newest_anchor().head();
5364 let (buffer, buffer_position) = self
5365 .buffer
5366 .read(cx)
5367 .text_anchor_for_position(position, cx)?;
5368
5369 let settings = language_settings::language_settings(
5370 buffer
5371 .read(cx)
5372 .language_at(buffer_position)
5373 .map(|l| l.name()),
5374 buffer.read(cx).file(),
5375 cx,
5376 );
5377 if !settings.use_on_type_format {
5378 return None;
5379 }
5380
5381 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5382 // hence we do LSP request & edit on host side only — add formats to host's history.
5383 let push_to_lsp_host_history = true;
5384 // If this is not the host, append its history with new edits.
5385 let push_to_client_history = project.read(cx).is_via_collab();
5386
5387 let on_type_formatting = project.update(cx, |project, cx| {
5388 project.on_type_format(
5389 buffer.clone(),
5390 buffer_position,
5391 input,
5392 push_to_lsp_host_history,
5393 cx,
5394 )
5395 });
5396 Some(cx.spawn_in(window, async move |editor, cx| {
5397 if let Some(transaction) = on_type_formatting.await? {
5398 if push_to_client_history {
5399 buffer
5400 .update(cx, |buffer, _| {
5401 buffer.push_transaction(transaction, Instant::now());
5402 buffer.finalize_last_transaction();
5403 })
5404 .ok();
5405 }
5406 editor.update(cx, |editor, cx| {
5407 editor.refresh_document_highlights(cx);
5408 })?;
5409 }
5410 Ok(())
5411 }))
5412 }
5413
5414 pub fn show_word_completions(
5415 &mut self,
5416 _: &ShowWordCompletions,
5417 window: &mut Window,
5418 cx: &mut Context<Self>,
5419 ) {
5420 self.open_or_update_completions_menu(
5421 Some(CompletionsMenuSource::Words {
5422 ignore_threshold: true,
5423 }),
5424 None,
5425 false,
5426 window,
5427 cx,
5428 );
5429 }
5430
5431 pub fn show_completions(
5432 &mut self,
5433 _: &ShowCompletions,
5434 window: &mut Window,
5435 cx: &mut Context<Self>,
5436 ) {
5437 self.open_or_update_completions_menu(None, None, false, window, cx);
5438 }
5439
5440 fn open_or_update_completions_menu(
5441 &mut self,
5442 requested_source: Option<CompletionsMenuSource>,
5443 trigger: Option<String>,
5444 trigger_in_words: bool,
5445 window: &mut Window,
5446 cx: &mut Context<Self>,
5447 ) {
5448 if self.pending_rename.is_some() {
5449 return;
5450 }
5451
5452 let completions_source = self
5453 .context_menu
5454 .borrow()
5455 .as_ref()
5456 .and_then(|menu| match menu {
5457 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5458 CodeContextMenu::CodeActions(_) => None,
5459 });
5460
5461 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5462
5463 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5464 // inserted and selected. To handle that case, the start of the selection is used so that
5465 // the menu starts with all choices.
5466 let position = self
5467 .selections
5468 .newest_anchor()
5469 .start
5470 .bias_right(&multibuffer_snapshot);
5471 if position.diff_base_anchor.is_some() {
5472 return;
5473 }
5474 let buffer_position = multibuffer_snapshot.anchor_before(position);
5475 let Some(buffer) = buffer_position
5476 .buffer_id
5477 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5478 else {
5479 return;
5480 };
5481 let buffer_snapshot = buffer.read(cx).snapshot();
5482
5483 let query: Option<Arc<String>> =
5484 Self::completion_query(&multibuffer_snapshot, buffer_position)
5485 .map(|query| query.into());
5486
5487 drop(multibuffer_snapshot);
5488
5489 // Hide the current completions menu when query is empty. Without this, cached
5490 // completions from before the trigger char may be reused (#32774).
5491 if query.is_none() {
5492 let menu_is_open = matches!(
5493 self.context_menu.borrow().as_ref(),
5494 Some(CodeContextMenu::Completions(_))
5495 );
5496 if menu_is_open {
5497 self.hide_context_menu(window, cx);
5498 }
5499 }
5500
5501 let mut ignore_word_threshold = false;
5502 let provider = match requested_source {
5503 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5504 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5505 ignore_word_threshold = ignore_threshold;
5506 None
5507 }
5508 Some(CompletionsMenuSource::SnippetChoices)
5509 | Some(CompletionsMenuSource::SnippetsOnly) => {
5510 log::error!("bug: SnippetChoices requested_source is not handled");
5511 None
5512 }
5513 };
5514
5515 let sort_completions = provider
5516 .as_ref()
5517 .is_some_and(|provider| provider.sort_completions());
5518
5519 let filter_completions = provider
5520 .as_ref()
5521 .is_none_or(|provider| provider.filter_completions());
5522
5523 let was_snippets_only = matches!(
5524 completions_source,
5525 Some(CompletionsMenuSource::SnippetsOnly)
5526 );
5527
5528 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5529 if filter_completions {
5530 menu.filter(
5531 query.clone().unwrap_or_default(),
5532 buffer_position.text_anchor,
5533 &buffer,
5534 provider.clone(),
5535 window,
5536 cx,
5537 );
5538 }
5539 // When `is_incomplete` is false, no need to re-query completions when the current query
5540 // is a suffix of the initial query.
5541 let was_complete = !menu.is_incomplete;
5542 if was_complete && !was_snippets_only {
5543 // If the new query is a suffix of the old query (typing more characters) and
5544 // the previous result was complete, the existing completions can be filtered.
5545 //
5546 // Note that snippet completions are always complete.
5547 let query_matches = match (&menu.initial_query, &query) {
5548 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5549 (None, _) => true,
5550 _ => false,
5551 };
5552 if query_matches {
5553 let position_matches = if menu.initial_position == position {
5554 true
5555 } else {
5556 let snapshot = self.buffer.read(cx).read(cx);
5557 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5558 };
5559 if position_matches {
5560 return;
5561 }
5562 }
5563 }
5564 };
5565
5566 let Anchor {
5567 excerpt_id: buffer_excerpt_id,
5568 text_anchor: buffer_position,
5569 ..
5570 } = buffer_position;
5571
5572 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5573 buffer_snapshot.surrounding_word(buffer_position, None)
5574 {
5575 let word_to_exclude = buffer_snapshot
5576 .text_for_range(word_range.clone())
5577 .collect::<String>();
5578 (
5579 buffer_snapshot.anchor_before(word_range.start)
5580 ..buffer_snapshot.anchor_after(buffer_position),
5581 Some(word_to_exclude),
5582 )
5583 } else {
5584 (buffer_position..buffer_position, None)
5585 };
5586
5587 let language = buffer_snapshot
5588 .language_at(buffer_position)
5589 .map(|language| language.name());
5590
5591 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5592 .completions
5593 .clone();
5594
5595 let show_completion_documentation = buffer_snapshot
5596 .settings_at(buffer_position, cx)
5597 .show_completion_documentation;
5598
5599 // The document can be large, so stay in reasonable bounds when searching for words,
5600 // otherwise completion pop-up might be slow to appear.
5601 const WORD_LOOKUP_ROWS: u32 = 5_000;
5602 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5603 let min_word_search = buffer_snapshot.clip_point(
5604 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5605 Bias::Left,
5606 );
5607 let max_word_search = buffer_snapshot.clip_point(
5608 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5609 Bias::Right,
5610 );
5611 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5612 ..buffer_snapshot.point_to_offset(max_word_search);
5613
5614 let skip_digits = query
5615 .as_ref()
5616 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5617
5618 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5619 trigger.as_ref().is_none_or(|trigger| {
5620 provider.is_completion_trigger(
5621 &buffer,
5622 position.text_anchor,
5623 trigger,
5624 trigger_in_words,
5625 completions_source.is_some(),
5626 cx,
5627 )
5628 })
5629 });
5630
5631 let provider_responses = if let Some(provider) = &provider
5632 && load_provider_completions
5633 {
5634 let trigger_character =
5635 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5636 let completion_context = CompletionContext {
5637 trigger_kind: match &trigger_character {
5638 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5639 None => CompletionTriggerKind::INVOKED,
5640 },
5641 trigger_character,
5642 };
5643
5644 provider.completions(
5645 buffer_excerpt_id,
5646 &buffer,
5647 buffer_position,
5648 completion_context,
5649 window,
5650 cx,
5651 )
5652 } else {
5653 Task::ready(Ok(Vec::new()))
5654 };
5655
5656 let load_word_completions = if !self.word_completions_enabled {
5657 false
5658 } else if requested_source
5659 == Some(CompletionsMenuSource::Words {
5660 ignore_threshold: true,
5661 })
5662 {
5663 true
5664 } else {
5665 load_provider_completions
5666 && completion_settings.words != WordsCompletionMode::Disabled
5667 && (ignore_word_threshold || {
5668 let words_min_length = completion_settings.words_min_length;
5669 // check whether word has at least `words_min_length` characters
5670 let query_chars = query.iter().flat_map(|q| q.chars());
5671 query_chars.take(words_min_length).count() == words_min_length
5672 })
5673 };
5674
5675 let mut words = if load_word_completions {
5676 cx.background_spawn({
5677 let buffer_snapshot = buffer_snapshot.clone();
5678 async move {
5679 buffer_snapshot.words_in_range(WordsQuery {
5680 fuzzy_contents: None,
5681 range: word_search_range,
5682 skip_digits,
5683 })
5684 }
5685 })
5686 } else {
5687 Task::ready(BTreeMap::default())
5688 };
5689
5690 let snippets = if let Some(provider) = &provider
5691 && provider.show_snippets()
5692 && let Some(project) = self.project()
5693 {
5694 let char_classifier = buffer_snapshot
5695 .char_classifier_at(buffer_position)
5696 .scope_context(Some(CharScopeContext::Completion));
5697 project.update(cx, |project, cx| {
5698 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5699 })
5700 } else {
5701 Task::ready(Ok(CompletionResponse {
5702 completions: Vec::new(),
5703 display_options: Default::default(),
5704 is_incomplete: false,
5705 }))
5706 };
5707
5708 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5709
5710 let id = post_inc(&mut self.next_completion_id);
5711 let task = cx.spawn_in(window, async move |editor, cx| {
5712 let Ok(()) = editor.update(cx, |this, _| {
5713 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5714 }) else {
5715 return;
5716 };
5717
5718 // TODO: Ideally completions from different sources would be selectively re-queried, so
5719 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5720 let mut completions = Vec::new();
5721 let mut is_incomplete = false;
5722 let mut display_options: Option<CompletionDisplayOptions> = None;
5723 if let Some(provider_responses) = provider_responses.await.log_err()
5724 && !provider_responses.is_empty()
5725 {
5726 for response in provider_responses {
5727 completions.extend(response.completions);
5728 is_incomplete = is_incomplete || response.is_incomplete;
5729 match display_options.as_mut() {
5730 None => {
5731 display_options = Some(response.display_options);
5732 }
5733 Some(options) => options.merge(&response.display_options),
5734 }
5735 }
5736 if completion_settings.words == WordsCompletionMode::Fallback {
5737 words = Task::ready(BTreeMap::default());
5738 }
5739 }
5740 let display_options = display_options.unwrap_or_default();
5741
5742 let mut words = words.await;
5743 if let Some(word_to_exclude) = &word_to_exclude {
5744 words.remove(word_to_exclude);
5745 }
5746 for lsp_completion in &completions {
5747 words.remove(&lsp_completion.new_text);
5748 }
5749 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5750 replace_range: word_replace_range.clone(),
5751 new_text: word.clone(),
5752 label: CodeLabel::plain(word, None),
5753 match_start: None,
5754 snippet_deduplication_key: None,
5755 icon_path: None,
5756 documentation: None,
5757 source: CompletionSource::BufferWord {
5758 word_range,
5759 resolved: false,
5760 },
5761 insert_text_mode: Some(InsertTextMode::AS_IS),
5762 confirm: None,
5763 }));
5764
5765 completions.extend(
5766 snippets
5767 .await
5768 .into_iter()
5769 .flat_map(|response| response.completions),
5770 );
5771
5772 let menu = if completions.is_empty() {
5773 None
5774 } else {
5775 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5776 let languages = editor
5777 .workspace
5778 .as_ref()
5779 .and_then(|(workspace, _)| workspace.upgrade())
5780 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5781 let menu = CompletionsMenu::new(
5782 id,
5783 requested_source.unwrap_or(if load_provider_completions {
5784 CompletionsMenuSource::Normal
5785 } else {
5786 CompletionsMenuSource::SnippetsOnly
5787 }),
5788 sort_completions,
5789 show_completion_documentation,
5790 position,
5791 query.clone(),
5792 is_incomplete,
5793 buffer.clone(),
5794 completions.into(),
5795 display_options,
5796 snippet_sort_order,
5797 languages,
5798 language,
5799 cx,
5800 );
5801
5802 let query = if filter_completions { query } else { None };
5803 let matches_task = menu.do_async_filtering(
5804 query.unwrap_or_default(),
5805 buffer_position,
5806 &buffer,
5807 cx,
5808 );
5809 (menu, matches_task)
5810 }) else {
5811 return;
5812 };
5813
5814 let matches = matches_task.await;
5815
5816 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5817 // Newer menu already set, so exit.
5818 if let Some(CodeContextMenu::Completions(prev_menu)) =
5819 editor.context_menu.borrow().as_ref()
5820 && prev_menu.id > id
5821 {
5822 return;
5823 };
5824
5825 // Only valid to take prev_menu because either the new menu is immediately set
5826 // below, or the menu is hidden.
5827 if let Some(CodeContextMenu::Completions(prev_menu)) =
5828 editor.context_menu.borrow_mut().take()
5829 {
5830 let position_matches =
5831 if prev_menu.initial_position == menu.initial_position {
5832 true
5833 } else {
5834 let snapshot = editor.buffer.read(cx).read(cx);
5835 prev_menu.initial_position.to_offset(&snapshot)
5836 == menu.initial_position.to_offset(&snapshot)
5837 };
5838 if position_matches {
5839 // Preserve markdown cache before `set_filter_results` because it will
5840 // try to populate the documentation cache.
5841 menu.preserve_markdown_cache(prev_menu);
5842 }
5843 };
5844
5845 menu.set_filter_results(matches, provider, window, cx);
5846 }) else {
5847 return;
5848 };
5849
5850 menu.visible().then_some(menu)
5851 };
5852
5853 editor
5854 .update_in(cx, |editor, window, cx| {
5855 if editor.focus_handle.is_focused(window)
5856 && let Some(menu) = menu
5857 {
5858 *editor.context_menu.borrow_mut() =
5859 Some(CodeContextMenu::Completions(menu));
5860
5861 crate::hover_popover::hide_hover(editor, cx);
5862 if editor.show_edit_predictions_in_menu() {
5863 editor.update_visible_edit_prediction(window, cx);
5864 } else {
5865 editor.discard_edit_prediction(false, cx);
5866 }
5867
5868 cx.notify();
5869 return;
5870 }
5871
5872 if editor.completion_tasks.len() <= 1 {
5873 // If there are no more completion tasks and the last menu was empty, we should hide it.
5874 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5875 // If it was already hidden and we don't show edit predictions in the menu,
5876 // we should also show the edit prediction when available.
5877 if was_hidden && editor.show_edit_predictions_in_menu() {
5878 editor.update_visible_edit_prediction(window, cx);
5879 }
5880 }
5881 })
5882 .ok();
5883 });
5884
5885 self.completion_tasks.push((id, task));
5886 }
5887
5888 #[cfg(feature = "test-support")]
5889 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5890 let menu = self.context_menu.borrow();
5891 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5892 let completions = menu.completions.borrow();
5893 Some(completions.to_vec())
5894 } else {
5895 None
5896 }
5897 }
5898
5899 pub fn with_completions_menu_matching_id<R>(
5900 &self,
5901 id: CompletionId,
5902 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5903 ) -> R {
5904 let mut context_menu = self.context_menu.borrow_mut();
5905 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5906 return f(None);
5907 };
5908 if completions_menu.id != id {
5909 return f(None);
5910 }
5911 f(Some(completions_menu))
5912 }
5913
5914 pub fn confirm_completion(
5915 &mut self,
5916 action: &ConfirmCompletion,
5917 window: &mut Window,
5918 cx: &mut Context<Self>,
5919 ) -> Option<Task<Result<()>>> {
5920 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5921 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5922 }
5923
5924 pub fn confirm_completion_insert(
5925 &mut self,
5926 _: &ConfirmCompletionInsert,
5927 window: &mut Window,
5928 cx: &mut Context<Self>,
5929 ) -> Option<Task<Result<()>>> {
5930 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5931 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5932 }
5933
5934 pub fn confirm_completion_replace(
5935 &mut self,
5936 _: &ConfirmCompletionReplace,
5937 window: &mut Window,
5938 cx: &mut Context<Self>,
5939 ) -> Option<Task<Result<()>>> {
5940 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5941 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5942 }
5943
5944 pub fn compose_completion(
5945 &mut self,
5946 action: &ComposeCompletion,
5947 window: &mut Window,
5948 cx: &mut Context<Self>,
5949 ) -> Option<Task<Result<()>>> {
5950 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5951 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5952 }
5953
5954 fn do_completion(
5955 &mut self,
5956 item_ix: Option<usize>,
5957 intent: CompletionIntent,
5958 window: &mut Window,
5959 cx: &mut Context<Editor>,
5960 ) -> Option<Task<Result<()>>> {
5961 use language::ToOffset as _;
5962
5963 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5964 else {
5965 return None;
5966 };
5967
5968 let candidate_id = {
5969 let entries = completions_menu.entries.borrow();
5970 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5971 if self.show_edit_predictions_in_menu() {
5972 self.discard_edit_prediction(true, cx);
5973 }
5974 mat.candidate_id
5975 };
5976
5977 let completion = completions_menu
5978 .completions
5979 .borrow()
5980 .get(candidate_id)?
5981 .clone();
5982 cx.stop_propagation();
5983
5984 let buffer_handle = completions_menu.buffer.clone();
5985
5986 let CompletionEdit {
5987 new_text,
5988 snippet,
5989 replace_range,
5990 } = process_completion_for_edit(
5991 &completion,
5992 intent,
5993 &buffer_handle,
5994 &completions_menu.initial_position.text_anchor,
5995 cx,
5996 );
5997
5998 let buffer = buffer_handle.read(cx);
5999 let snapshot = self.buffer.read(cx).snapshot(cx);
6000 let newest_anchor = self.selections.newest_anchor();
6001 let replace_range_multibuffer = {
6002 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6003 excerpt.map_range_from_buffer(replace_range.clone())
6004 };
6005 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6006 return None;
6007 }
6008
6009 let old_text = buffer
6010 .text_for_range(replace_range.clone())
6011 .collect::<String>();
6012 let lookbehind = newest_anchor
6013 .start
6014 .text_anchor
6015 .to_offset(buffer)
6016 .saturating_sub(replace_range.start);
6017 let lookahead = replace_range
6018 .end
6019 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6020 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6021 let suffix = &old_text[lookbehind.min(old_text.len())..];
6022
6023 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
6024 let mut ranges = Vec::new();
6025 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6026
6027 for selection in &selections {
6028 let range = if selection.id == newest_anchor.id {
6029 replace_range_multibuffer.clone()
6030 } else {
6031 let mut range = selection.range();
6032
6033 // if prefix is present, don't duplicate it
6034 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6035 range.start = range.start.saturating_sub(lookbehind);
6036
6037 // if suffix is also present, mimic the newest cursor and replace it
6038 if selection.id != newest_anchor.id
6039 && snapshot.contains_str_at(range.end, suffix)
6040 {
6041 range.end += lookahead;
6042 }
6043 }
6044 range
6045 };
6046
6047 ranges.push(range.clone());
6048
6049 if !self.linked_edit_ranges.is_empty() {
6050 let start_anchor = snapshot.anchor_before(range.start);
6051 let end_anchor = snapshot.anchor_after(range.end);
6052 if let Some(ranges) = self
6053 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6054 {
6055 for (buffer, edits) in ranges {
6056 linked_edits
6057 .entry(buffer.clone())
6058 .or_default()
6059 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6060 }
6061 }
6062 }
6063 }
6064
6065 let common_prefix_len = old_text
6066 .chars()
6067 .zip(new_text.chars())
6068 .take_while(|(a, b)| a == b)
6069 .map(|(a, _)| a.len_utf8())
6070 .sum::<usize>();
6071
6072 cx.emit(EditorEvent::InputHandled {
6073 utf16_range_to_replace: None,
6074 text: new_text[common_prefix_len..].into(),
6075 });
6076
6077 self.transact(window, cx, |editor, window, cx| {
6078 if let Some(mut snippet) = snippet {
6079 snippet.text = new_text.to_string();
6080 editor
6081 .insert_snippet(&ranges, snippet, window, cx)
6082 .log_err();
6083 } else {
6084 editor.buffer.update(cx, |multi_buffer, cx| {
6085 let auto_indent = match completion.insert_text_mode {
6086 Some(InsertTextMode::AS_IS) => None,
6087 _ => editor.autoindent_mode.clone(),
6088 };
6089 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6090 multi_buffer.edit(edits, auto_indent, cx);
6091 });
6092 }
6093 for (buffer, edits) in linked_edits {
6094 buffer.update(cx, |buffer, cx| {
6095 let snapshot = buffer.snapshot();
6096 let edits = edits
6097 .into_iter()
6098 .map(|(range, text)| {
6099 use text::ToPoint as TP;
6100 let end_point = TP::to_point(&range.end, &snapshot);
6101 let start_point = TP::to_point(&range.start, &snapshot);
6102 (start_point..end_point, text)
6103 })
6104 .sorted_by_key(|(range, _)| range.start);
6105 buffer.edit(edits, None, cx);
6106 })
6107 }
6108
6109 editor.refresh_edit_prediction(true, false, window, cx);
6110 });
6111 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6112
6113 let show_new_completions_on_confirm = completion
6114 .confirm
6115 .as_ref()
6116 .is_some_and(|confirm| confirm(intent, window, cx));
6117 if show_new_completions_on_confirm {
6118 self.open_or_update_completions_menu(None, None, false, window, cx);
6119 }
6120
6121 let provider = self.completion_provider.as_ref()?;
6122 drop(completion);
6123 let apply_edits = provider.apply_additional_edits_for_completion(
6124 buffer_handle,
6125 completions_menu.completions.clone(),
6126 candidate_id,
6127 true,
6128 cx,
6129 );
6130
6131 let editor_settings = EditorSettings::get_global(cx);
6132 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6133 // After the code completion is finished, users often want to know what signatures are needed.
6134 // so we should automatically call signature_help
6135 self.show_signature_help(&ShowSignatureHelp, window, cx);
6136 }
6137
6138 Some(cx.foreground_executor().spawn(async move {
6139 apply_edits.await?;
6140 Ok(())
6141 }))
6142 }
6143
6144 pub fn toggle_code_actions(
6145 &mut self,
6146 action: &ToggleCodeActions,
6147 window: &mut Window,
6148 cx: &mut Context<Self>,
6149 ) {
6150 let quick_launch = action.quick_launch;
6151 let mut context_menu = self.context_menu.borrow_mut();
6152 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6153 if code_actions.deployed_from == action.deployed_from {
6154 // Toggle if we're selecting the same one
6155 *context_menu = None;
6156 cx.notify();
6157 return;
6158 } else {
6159 // Otherwise, clear it and start a new one
6160 *context_menu = None;
6161 cx.notify();
6162 }
6163 }
6164 drop(context_menu);
6165 let snapshot = self.snapshot(window, cx);
6166 let deployed_from = action.deployed_from.clone();
6167 let action = action.clone();
6168 self.completion_tasks.clear();
6169 self.discard_edit_prediction(false, cx);
6170
6171 let multibuffer_point = match &action.deployed_from {
6172 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6173 DisplayPoint::new(*row, 0).to_point(&snapshot)
6174 }
6175 _ => self
6176 .selections
6177 .newest::<Point>(&snapshot.display_snapshot)
6178 .head(),
6179 };
6180 let Some((buffer, buffer_row)) = snapshot
6181 .buffer_snapshot()
6182 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6183 .and_then(|(buffer_snapshot, range)| {
6184 self.buffer()
6185 .read(cx)
6186 .buffer(buffer_snapshot.remote_id())
6187 .map(|buffer| (buffer, range.start.row))
6188 })
6189 else {
6190 return;
6191 };
6192 let buffer_id = buffer.read(cx).remote_id();
6193 let tasks = self
6194 .tasks
6195 .get(&(buffer_id, buffer_row))
6196 .map(|t| Arc::new(t.to_owned()));
6197
6198 if !self.focus_handle.is_focused(window) {
6199 return;
6200 }
6201 let project = self.project.clone();
6202
6203 let code_actions_task = match deployed_from {
6204 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6205 _ => self.code_actions(buffer_row, window, cx),
6206 };
6207
6208 let runnable_task = match deployed_from {
6209 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6210 _ => {
6211 let mut task_context_task = Task::ready(None);
6212 if let Some(tasks) = &tasks
6213 && let Some(project) = project
6214 {
6215 task_context_task =
6216 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6217 }
6218
6219 cx.spawn_in(window, {
6220 let buffer = buffer.clone();
6221 async move |editor, cx| {
6222 let task_context = task_context_task.await;
6223
6224 let resolved_tasks =
6225 tasks
6226 .zip(task_context.clone())
6227 .map(|(tasks, task_context)| ResolvedTasks {
6228 templates: tasks.resolve(&task_context).collect(),
6229 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6230 multibuffer_point.row,
6231 tasks.column,
6232 )),
6233 });
6234 let debug_scenarios = editor
6235 .update(cx, |editor, cx| {
6236 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6237 })?
6238 .await;
6239 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6240 }
6241 })
6242 }
6243 };
6244
6245 cx.spawn_in(window, async move |editor, cx| {
6246 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6247 let code_actions = code_actions_task.await;
6248 let spawn_straight_away = quick_launch
6249 && resolved_tasks
6250 .as_ref()
6251 .is_some_and(|tasks| tasks.templates.len() == 1)
6252 && code_actions
6253 .as_ref()
6254 .is_none_or(|actions| actions.is_empty())
6255 && debug_scenarios.is_empty();
6256
6257 editor.update_in(cx, |editor, window, cx| {
6258 crate::hover_popover::hide_hover(editor, cx);
6259 let actions = CodeActionContents::new(
6260 resolved_tasks,
6261 code_actions,
6262 debug_scenarios,
6263 task_context.unwrap_or_default(),
6264 );
6265
6266 // Don't show the menu if there are no actions available
6267 if actions.is_empty() {
6268 cx.notify();
6269 return Task::ready(Ok(()));
6270 }
6271
6272 *editor.context_menu.borrow_mut() =
6273 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6274 buffer,
6275 actions,
6276 selected_item: Default::default(),
6277 scroll_handle: UniformListScrollHandle::default(),
6278 deployed_from,
6279 }));
6280 cx.notify();
6281 if spawn_straight_away
6282 && let Some(task) = editor.confirm_code_action(
6283 &ConfirmCodeAction { item_ix: Some(0) },
6284 window,
6285 cx,
6286 )
6287 {
6288 return task;
6289 }
6290
6291 Task::ready(Ok(()))
6292 })
6293 })
6294 .detach_and_log_err(cx);
6295 }
6296
6297 fn debug_scenarios(
6298 &mut self,
6299 resolved_tasks: &Option<ResolvedTasks>,
6300 buffer: &Entity<Buffer>,
6301 cx: &mut App,
6302 ) -> Task<Vec<task::DebugScenario>> {
6303 maybe!({
6304 let project = self.project()?;
6305 let dap_store = project.read(cx).dap_store();
6306 let mut scenarios = vec![];
6307 let resolved_tasks = resolved_tasks.as_ref()?;
6308 let buffer = buffer.read(cx);
6309 let language = buffer.language()?;
6310 let file = buffer.file();
6311 let debug_adapter = language_settings(language.name().into(), file, cx)
6312 .debuggers
6313 .first()
6314 .map(SharedString::from)
6315 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6316
6317 dap_store.update(cx, |dap_store, cx| {
6318 for (_, task) in &resolved_tasks.templates {
6319 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6320 task.original_task().clone(),
6321 debug_adapter.clone().into(),
6322 task.display_label().to_owned().into(),
6323 cx,
6324 );
6325 scenarios.push(maybe_scenario);
6326 }
6327 });
6328 Some(cx.background_spawn(async move {
6329 futures::future::join_all(scenarios)
6330 .await
6331 .into_iter()
6332 .flatten()
6333 .collect::<Vec<_>>()
6334 }))
6335 })
6336 .unwrap_or_else(|| Task::ready(vec![]))
6337 }
6338
6339 fn code_actions(
6340 &mut self,
6341 buffer_row: u32,
6342 window: &mut Window,
6343 cx: &mut Context<Self>,
6344 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6345 let mut task = self.code_actions_task.take();
6346 cx.spawn_in(window, async move |editor, cx| {
6347 while let Some(prev_task) = task {
6348 prev_task.await.log_err();
6349 task = editor
6350 .update(cx, |this, _| this.code_actions_task.take())
6351 .ok()?;
6352 }
6353
6354 editor
6355 .update(cx, |editor, cx| {
6356 editor
6357 .available_code_actions
6358 .clone()
6359 .and_then(|(location, code_actions)| {
6360 let snapshot = location.buffer.read(cx).snapshot();
6361 let point_range = location.range.to_point(&snapshot);
6362 let point_range = point_range.start.row..=point_range.end.row;
6363 if point_range.contains(&buffer_row) {
6364 Some(code_actions)
6365 } else {
6366 None
6367 }
6368 })
6369 })
6370 .ok()
6371 .flatten()
6372 })
6373 }
6374
6375 pub fn confirm_code_action(
6376 &mut self,
6377 action: &ConfirmCodeAction,
6378 window: &mut Window,
6379 cx: &mut Context<Self>,
6380 ) -> Option<Task<Result<()>>> {
6381 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6382
6383 let actions_menu =
6384 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6385 menu
6386 } else {
6387 return None;
6388 };
6389
6390 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6391 let action = actions_menu.actions.get(action_ix)?;
6392 let title = action.label();
6393 let buffer = actions_menu.buffer;
6394 let workspace = self.workspace()?;
6395
6396 match action {
6397 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6398 workspace.update(cx, |workspace, cx| {
6399 workspace.schedule_resolved_task(
6400 task_source_kind,
6401 resolved_task,
6402 false,
6403 window,
6404 cx,
6405 );
6406
6407 Some(Task::ready(Ok(())))
6408 })
6409 }
6410 CodeActionsItem::CodeAction {
6411 excerpt_id,
6412 action,
6413 provider,
6414 } => {
6415 let apply_code_action =
6416 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6417 let workspace = workspace.downgrade();
6418 Some(cx.spawn_in(window, async move |editor, cx| {
6419 let project_transaction = apply_code_action.await?;
6420 Self::open_project_transaction(
6421 &editor,
6422 workspace,
6423 project_transaction,
6424 title,
6425 cx,
6426 )
6427 .await
6428 }))
6429 }
6430 CodeActionsItem::DebugScenario(scenario) => {
6431 let context = actions_menu.actions.context;
6432
6433 workspace.update(cx, |workspace, cx| {
6434 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6435 workspace.start_debug_session(
6436 scenario,
6437 context,
6438 Some(buffer),
6439 None,
6440 window,
6441 cx,
6442 );
6443 });
6444 Some(Task::ready(Ok(())))
6445 }
6446 }
6447 }
6448
6449 pub async fn open_project_transaction(
6450 editor: &WeakEntity<Editor>,
6451 workspace: WeakEntity<Workspace>,
6452 transaction: ProjectTransaction,
6453 title: String,
6454 cx: &mut AsyncWindowContext,
6455 ) -> Result<()> {
6456 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6457 cx.update(|_, cx| {
6458 entries.sort_unstable_by_key(|(buffer, _)| {
6459 buffer.read(cx).file().map(|f| f.path().clone())
6460 });
6461 })?;
6462 if entries.is_empty() {
6463 return Ok(());
6464 }
6465
6466 // If the project transaction's edits are all contained within this editor, then
6467 // avoid opening a new editor to display them.
6468
6469 if let [(buffer, transaction)] = &*entries {
6470 let excerpt = editor.update(cx, |editor, cx| {
6471 editor
6472 .buffer()
6473 .read(cx)
6474 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6475 })?;
6476 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6477 && excerpted_buffer == *buffer
6478 {
6479 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6480 let excerpt_range = excerpt_range.to_offset(buffer);
6481 buffer
6482 .edited_ranges_for_transaction::<usize>(transaction)
6483 .all(|range| {
6484 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6485 })
6486 })?;
6487
6488 if all_edits_within_excerpt {
6489 return Ok(());
6490 }
6491 }
6492 }
6493
6494 let mut ranges_to_highlight = Vec::new();
6495 let excerpt_buffer = cx.new(|cx| {
6496 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6497 for (buffer_handle, transaction) in &entries {
6498 let edited_ranges = buffer_handle
6499 .read(cx)
6500 .edited_ranges_for_transaction::<Point>(transaction)
6501 .collect::<Vec<_>>();
6502 let (ranges, _) = multibuffer.set_excerpts_for_path(
6503 PathKey::for_buffer(buffer_handle, cx),
6504 buffer_handle.clone(),
6505 edited_ranges,
6506 multibuffer_context_lines(cx),
6507 cx,
6508 );
6509
6510 ranges_to_highlight.extend(ranges);
6511 }
6512 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6513 multibuffer
6514 })?;
6515
6516 workspace.update_in(cx, |workspace, window, cx| {
6517 let project = workspace.project().clone();
6518 let editor =
6519 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6520 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6521 editor.update(cx, |editor, cx| {
6522 editor.highlight_background::<Self>(
6523 &ranges_to_highlight,
6524 |theme| theme.colors().editor_highlighted_line_background,
6525 cx,
6526 );
6527 });
6528 })?;
6529
6530 Ok(())
6531 }
6532
6533 pub fn clear_code_action_providers(&mut self) {
6534 self.code_action_providers.clear();
6535 self.available_code_actions.take();
6536 }
6537
6538 pub fn add_code_action_provider(
6539 &mut self,
6540 provider: Rc<dyn CodeActionProvider>,
6541 window: &mut Window,
6542 cx: &mut Context<Self>,
6543 ) {
6544 if self
6545 .code_action_providers
6546 .iter()
6547 .any(|existing_provider| existing_provider.id() == provider.id())
6548 {
6549 return;
6550 }
6551
6552 self.code_action_providers.push(provider);
6553 self.refresh_code_actions(window, cx);
6554 }
6555
6556 pub fn remove_code_action_provider(
6557 &mut self,
6558 id: Arc<str>,
6559 window: &mut Window,
6560 cx: &mut Context<Self>,
6561 ) {
6562 self.code_action_providers
6563 .retain(|provider| provider.id() != id);
6564 self.refresh_code_actions(window, cx);
6565 }
6566
6567 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6568 !self.code_action_providers.is_empty()
6569 && EditorSettings::get_global(cx).toolbar.code_actions
6570 }
6571
6572 pub fn has_available_code_actions(&self) -> bool {
6573 self.available_code_actions
6574 .as_ref()
6575 .is_some_and(|(_, actions)| !actions.is_empty())
6576 }
6577
6578 fn render_inline_code_actions(
6579 &self,
6580 icon_size: ui::IconSize,
6581 display_row: DisplayRow,
6582 is_active: bool,
6583 cx: &mut Context<Self>,
6584 ) -> AnyElement {
6585 let show_tooltip = !self.context_menu_visible();
6586 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6587 .icon_size(icon_size)
6588 .shape(ui::IconButtonShape::Square)
6589 .icon_color(ui::Color::Hidden)
6590 .toggle_state(is_active)
6591 .when(show_tooltip, |this| {
6592 this.tooltip({
6593 let focus_handle = self.focus_handle.clone();
6594 move |_window, cx| {
6595 Tooltip::for_action_in(
6596 "Toggle Code Actions",
6597 &ToggleCodeActions {
6598 deployed_from: None,
6599 quick_launch: false,
6600 },
6601 &focus_handle,
6602 cx,
6603 )
6604 }
6605 })
6606 })
6607 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6608 window.focus(&editor.focus_handle(cx));
6609 editor.toggle_code_actions(
6610 &crate::actions::ToggleCodeActions {
6611 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6612 display_row,
6613 )),
6614 quick_launch: false,
6615 },
6616 window,
6617 cx,
6618 );
6619 }))
6620 .into_any_element()
6621 }
6622
6623 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6624 &self.context_menu
6625 }
6626
6627 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6628 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6629 cx.background_executor()
6630 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6631 .await;
6632
6633 let (start_buffer, start, _, end, newest_selection) = this
6634 .update(cx, |this, cx| {
6635 let newest_selection = this.selections.newest_anchor().clone();
6636 if newest_selection.head().diff_base_anchor.is_some() {
6637 return None;
6638 }
6639 let display_snapshot = this.display_snapshot(cx);
6640 let newest_selection_adjusted =
6641 this.selections.newest_adjusted(&display_snapshot);
6642 let buffer = this.buffer.read(cx);
6643
6644 let (start_buffer, start) =
6645 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6646 let (end_buffer, end) =
6647 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6648
6649 Some((start_buffer, start, end_buffer, end, newest_selection))
6650 })?
6651 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6652 .context(
6653 "Expected selection to lie in a single buffer when refreshing code actions",
6654 )?;
6655 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6656 let providers = this.code_action_providers.clone();
6657 let tasks = this
6658 .code_action_providers
6659 .iter()
6660 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6661 .collect::<Vec<_>>();
6662 (providers, tasks)
6663 })?;
6664
6665 let mut actions = Vec::new();
6666 for (provider, provider_actions) in
6667 providers.into_iter().zip(future::join_all(tasks).await)
6668 {
6669 if let Some(provider_actions) = provider_actions.log_err() {
6670 actions.extend(provider_actions.into_iter().map(|action| {
6671 AvailableCodeAction {
6672 excerpt_id: newest_selection.start.excerpt_id,
6673 action,
6674 provider: provider.clone(),
6675 }
6676 }));
6677 }
6678 }
6679
6680 this.update(cx, |this, cx| {
6681 this.available_code_actions = if actions.is_empty() {
6682 None
6683 } else {
6684 Some((
6685 Location {
6686 buffer: start_buffer,
6687 range: start..end,
6688 },
6689 actions.into(),
6690 ))
6691 };
6692 cx.notify();
6693 })
6694 }));
6695 }
6696
6697 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6698 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6699 self.show_git_blame_inline = false;
6700
6701 self.show_git_blame_inline_delay_task =
6702 Some(cx.spawn_in(window, async move |this, cx| {
6703 cx.background_executor().timer(delay).await;
6704
6705 this.update(cx, |this, cx| {
6706 this.show_git_blame_inline = true;
6707 cx.notify();
6708 })
6709 .log_err();
6710 }));
6711 }
6712 }
6713
6714 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6715 let snapshot = self.snapshot(window, cx);
6716 let cursor = self
6717 .selections
6718 .newest::<Point>(&snapshot.display_snapshot)
6719 .head();
6720 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6721 else {
6722 return;
6723 };
6724
6725 let Some(blame) = self.blame.as_ref() else {
6726 return;
6727 };
6728
6729 let row_info = RowInfo {
6730 buffer_id: Some(buffer.remote_id()),
6731 buffer_row: Some(point.row),
6732 ..Default::default()
6733 };
6734 let Some((buffer, blame_entry)) = blame
6735 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6736 .flatten()
6737 else {
6738 return;
6739 };
6740
6741 let anchor = self.selections.newest_anchor().head();
6742 let position = self.to_pixel_point(anchor, &snapshot, window);
6743 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6744 self.show_blame_popover(
6745 buffer,
6746 &blame_entry,
6747 position + last_bounds.origin,
6748 true,
6749 cx,
6750 );
6751 };
6752 }
6753
6754 fn show_blame_popover(
6755 &mut self,
6756 buffer: BufferId,
6757 blame_entry: &BlameEntry,
6758 position: gpui::Point<Pixels>,
6759 ignore_timeout: bool,
6760 cx: &mut Context<Self>,
6761 ) {
6762 if let Some(state) = &mut self.inline_blame_popover {
6763 state.hide_task.take();
6764 } else {
6765 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6766 let blame_entry = blame_entry.clone();
6767 let show_task = cx.spawn(async move |editor, cx| {
6768 if !ignore_timeout {
6769 cx.background_executor()
6770 .timer(std::time::Duration::from_millis(blame_popover_delay))
6771 .await;
6772 }
6773 editor
6774 .update(cx, |editor, cx| {
6775 editor.inline_blame_popover_show_task.take();
6776 let Some(blame) = editor.blame.as_ref() else {
6777 return;
6778 };
6779 let blame = blame.read(cx);
6780 let details = blame.details_for_entry(buffer, &blame_entry);
6781 let markdown = cx.new(|cx| {
6782 Markdown::new(
6783 details
6784 .as_ref()
6785 .map(|message| message.message.clone())
6786 .unwrap_or_default(),
6787 None,
6788 None,
6789 cx,
6790 )
6791 });
6792 editor.inline_blame_popover = Some(InlineBlamePopover {
6793 position,
6794 hide_task: None,
6795 popover_bounds: None,
6796 popover_state: InlineBlamePopoverState {
6797 scroll_handle: ScrollHandle::new(),
6798 commit_message: details,
6799 markdown,
6800 },
6801 keyboard_grace: ignore_timeout,
6802 });
6803 cx.notify();
6804 })
6805 .ok();
6806 });
6807 self.inline_blame_popover_show_task = Some(show_task);
6808 }
6809 }
6810
6811 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6812 self.inline_blame_popover_show_task.take();
6813 if let Some(state) = &mut self.inline_blame_popover {
6814 let hide_task = cx.spawn(async move |editor, cx| {
6815 if !ignore_timeout {
6816 cx.background_executor()
6817 .timer(std::time::Duration::from_millis(100))
6818 .await;
6819 }
6820 editor
6821 .update(cx, |editor, cx| {
6822 editor.inline_blame_popover.take();
6823 cx.notify();
6824 })
6825 .ok();
6826 });
6827 state.hide_task = Some(hide_task);
6828 true
6829 } else {
6830 false
6831 }
6832 }
6833
6834 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6835 if self.pending_rename.is_some() {
6836 return None;
6837 }
6838
6839 let provider = self.semantics_provider.clone()?;
6840 let buffer = self.buffer.read(cx);
6841 let newest_selection = self.selections.newest_anchor().clone();
6842 let cursor_position = newest_selection.head();
6843 let (cursor_buffer, cursor_buffer_position) =
6844 buffer.text_anchor_for_position(cursor_position, cx)?;
6845 let (tail_buffer, tail_buffer_position) =
6846 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6847 if cursor_buffer != tail_buffer {
6848 return None;
6849 }
6850
6851 let snapshot = cursor_buffer.read(cx).snapshot();
6852 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6853 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6854 if start_word_range != end_word_range {
6855 self.document_highlights_task.take();
6856 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6857 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6858 return None;
6859 }
6860
6861 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6862 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6863 cx.background_executor()
6864 .timer(Duration::from_millis(debounce))
6865 .await;
6866
6867 let highlights = if let Some(highlights) = cx
6868 .update(|cx| {
6869 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6870 })
6871 .ok()
6872 .flatten()
6873 {
6874 highlights.await.log_err()
6875 } else {
6876 None
6877 };
6878
6879 if let Some(highlights) = highlights {
6880 this.update(cx, |this, cx| {
6881 if this.pending_rename.is_some() {
6882 return;
6883 }
6884
6885 let buffer = this.buffer.read(cx);
6886 if buffer
6887 .text_anchor_for_position(cursor_position, cx)
6888 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6889 {
6890 return;
6891 }
6892
6893 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6894 let mut write_ranges = Vec::new();
6895 let mut read_ranges = Vec::new();
6896 for highlight in highlights {
6897 let buffer_id = cursor_buffer.read(cx).remote_id();
6898 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6899 {
6900 let start = highlight
6901 .range
6902 .start
6903 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6904 let end = highlight
6905 .range
6906 .end
6907 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6908 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6909 continue;
6910 }
6911
6912 let range =
6913 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6914 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6915 write_ranges.push(range);
6916 } else {
6917 read_ranges.push(range);
6918 }
6919 }
6920 }
6921
6922 this.highlight_background::<DocumentHighlightRead>(
6923 &read_ranges,
6924 |theme| theme.colors().editor_document_highlight_read_background,
6925 cx,
6926 );
6927 this.highlight_background::<DocumentHighlightWrite>(
6928 &write_ranges,
6929 |theme| theme.colors().editor_document_highlight_write_background,
6930 cx,
6931 );
6932 cx.notify();
6933 })
6934 .log_err();
6935 }
6936 }));
6937 None
6938 }
6939
6940 fn prepare_highlight_query_from_selection(
6941 &mut self,
6942 window: &Window,
6943 cx: &mut Context<Editor>,
6944 ) -> Option<(String, Range<Anchor>)> {
6945 if matches!(self.mode, EditorMode::SingleLine) {
6946 return None;
6947 }
6948 if !EditorSettings::get_global(cx).selection_highlight {
6949 return None;
6950 }
6951 if self.selections.count() != 1 || self.selections.line_mode() {
6952 return None;
6953 }
6954 let snapshot = self.snapshot(window, cx);
6955 let selection = self.selections.newest::<Point>(&snapshot);
6956 // If the selection spans multiple rows OR it is empty
6957 if selection.start.row != selection.end.row
6958 || selection.start.column == selection.end.column
6959 {
6960 return None;
6961 }
6962 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6963 let query = snapshot
6964 .buffer_snapshot()
6965 .text_for_range(selection_anchor_range.clone())
6966 .collect::<String>();
6967 if query.trim().is_empty() {
6968 return None;
6969 }
6970 Some((query, selection_anchor_range))
6971 }
6972
6973 fn update_selection_occurrence_highlights(
6974 &mut self,
6975 query_text: String,
6976 query_range: Range<Anchor>,
6977 multi_buffer_range_to_query: Range<Point>,
6978 use_debounce: bool,
6979 window: &mut Window,
6980 cx: &mut Context<Editor>,
6981 ) -> Task<()> {
6982 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6983 cx.spawn_in(window, async move |editor, cx| {
6984 if use_debounce {
6985 cx.background_executor()
6986 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6987 .await;
6988 }
6989 let match_task = cx.background_spawn(async move {
6990 let buffer_ranges = multi_buffer_snapshot
6991 .range_to_buffer_ranges(multi_buffer_range_to_query)
6992 .into_iter()
6993 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6994 let mut match_ranges = Vec::new();
6995 let Ok(regex) = project::search::SearchQuery::text(
6996 query_text.clone(),
6997 false,
6998 false,
6999 false,
7000 Default::default(),
7001 Default::default(),
7002 false,
7003 None,
7004 ) else {
7005 return Vec::default();
7006 };
7007 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7008 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7009 match_ranges.extend(
7010 regex
7011 .search(buffer_snapshot, Some(search_range.clone()))
7012 .await
7013 .into_iter()
7014 .filter_map(|match_range| {
7015 let match_start = buffer_snapshot
7016 .anchor_after(search_range.start + match_range.start);
7017 let match_end = buffer_snapshot
7018 .anchor_before(search_range.start + match_range.end);
7019 let match_anchor_range = Anchor::range_in_buffer(
7020 excerpt_id,
7021 buffer_snapshot.remote_id(),
7022 match_start..match_end,
7023 );
7024 (match_anchor_range != query_range).then_some(match_anchor_range)
7025 }),
7026 );
7027 }
7028 match_ranges
7029 });
7030 let match_ranges = match_task.await;
7031 editor
7032 .update_in(cx, |editor, _, cx| {
7033 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7034 if !match_ranges.is_empty() {
7035 editor.highlight_background::<SelectedTextHighlight>(
7036 &match_ranges,
7037 |theme| theme.colors().editor_document_highlight_bracket_background,
7038 cx,
7039 )
7040 }
7041 })
7042 .log_err();
7043 })
7044 }
7045
7046 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7047 struct NewlineFold;
7048 let type_id = std::any::TypeId::of::<NewlineFold>();
7049 if !self.mode.is_single_line() {
7050 return;
7051 }
7052 let snapshot = self.snapshot(window, cx);
7053 if snapshot.buffer_snapshot().max_point().row == 0 {
7054 return;
7055 }
7056 let task = cx.background_spawn(async move {
7057 let new_newlines = snapshot
7058 .buffer_chars_at(0)
7059 .filter_map(|(c, i)| {
7060 if c == '\n' {
7061 Some(
7062 snapshot.buffer_snapshot().anchor_after(i)
7063 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7064 )
7065 } else {
7066 None
7067 }
7068 })
7069 .collect::<Vec<_>>();
7070 let existing_newlines = snapshot
7071 .folds_in_range(0..snapshot.buffer_snapshot().len())
7072 .filter_map(|fold| {
7073 if fold.placeholder.type_tag == Some(type_id) {
7074 Some(fold.range.start..fold.range.end)
7075 } else {
7076 None
7077 }
7078 })
7079 .collect::<Vec<_>>();
7080
7081 (new_newlines, existing_newlines)
7082 });
7083 self.folding_newlines = cx.spawn(async move |this, cx| {
7084 let (new_newlines, existing_newlines) = task.await;
7085 if new_newlines == existing_newlines {
7086 return;
7087 }
7088 let placeholder = FoldPlaceholder {
7089 render: Arc::new(move |_, _, cx| {
7090 div()
7091 .bg(cx.theme().status().hint_background)
7092 .border_b_1()
7093 .size_full()
7094 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7095 .border_color(cx.theme().status().hint)
7096 .child("\\n")
7097 .into_any()
7098 }),
7099 constrain_width: false,
7100 merge_adjacent: false,
7101 type_tag: Some(type_id),
7102 };
7103 let creases = new_newlines
7104 .into_iter()
7105 .map(|range| Crease::simple(range, placeholder.clone()))
7106 .collect();
7107 this.update(cx, |this, cx| {
7108 this.display_map.update(cx, |display_map, cx| {
7109 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7110 display_map.fold(creases, cx);
7111 });
7112 })
7113 .ok();
7114 });
7115 }
7116
7117 fn refresh_selected_text_highlights(
7118 &mut self,
7119 on_buffer_edit: bool,
7120 window: &mut Window,
7121 cx: &mut Context<Editor>,
7122 ) {
7123 let Some((query_text, query_range)) =
7124 self.prepare_highlight_query_from_selection(window, cx)
7125 else {
7126 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7127 self.quick_selection_highlight_task.take();
7128 self.debounced_selection_highlight_task.take();
7129 return;
7130 };
7131 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7132 if on_buffer_edit
7133 || self
7134 .quick_selection_highlight_task
7135 .as_ref()
7136 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7137 {
7138 let multi_buffer_visible_start = self
7139 .scroll_manager
7140 .anchor()
7141 .anchor
7142 .to_point(&multi_buffer_snapshot);
7143 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7144 multi_buffer_visible_start
7145 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7146 Bias::Left,
7147 );
7148 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7149 self.quick_selection_highlight_task = Some((
7150 query_range.clone(),
7151 self.update_selection_occurrence_highlights(
7152 query_text.clone(),
7153 query_range.clone(),
7154 multi_buffer_visible_range,
7155 false,
7156 window,
7157 cx,
7158 ),
7159 ));
7160 }
7161 if on_buffer_edit
7162 || self
7163 .debounced_selection_highlight_task
7164 .as_ref()
7165 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7166 {
7167 let multi_buffer_start = multi_buffer_snapshot
7168 .anchor_before(0)
7169 .to_point(&multi_buffer_snapshot);
7170 let multi_buffer_end = multi_buffer_snapshot
7171 .anchor_after(multi_buffer_snapshot.len())
7172 .to_point(&multi_buffer_snapshot);
7173 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7174 self.debounced_selection_highlight_task = Some((
7175 query_range.clone(),
7176 self.update_selection_occurrence_highlights(
7177 query_text,
7178 query_range,
7179 multi_buffer_full_range,
7180 true,
7181 window,
7182 cx,
7183 ),
7184 ));
7185 }
7186 }
7187
7188 pub fn refresh_edit_prediction(
7189 &mut self,
7190 debounce: bool,
7191 user_requested: bool,
7192 window: &mut Window,
7193 cx: &mut Context<Self>,
7194 ) -> Option<()> {
7195 if DisableAiSettings::get_global(cx).disable_ai {
7196 return None;
7197 }
7198
7199 let provider = self.edit_prediction_provider()?;
7200 let cursor = self.selections.newest_anchor().head();
7201 let (buffer, cursor_buffer_position) =
7202 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7203
7204 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7205 self.discard_edit_prediction(false, cx);
7206 return None;
7207 }
7208
7209 self.update_visible_edit_prediction(window, cx);
7210
7211 if !user_requested
7212 && (!self.should_show_edit_predictions()
7213 || !self.is_focused(window)
7214 || buffer.read(cx).is_empty())
7215 {
7216 self.discard_edit_prediction(false, cx);
7217 return None;
7218 }
7219
7220 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7221 Some(())
7222 }
7223
7224 fn show_edit_predictions_in_menu(&self) -> bool {
7225 match self.edit_prediction_settings {
7226 EditPredictionSettings::Disabled => false,
7227 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7228 }
7229 }
7230
7231 pub fn edit_predictions_enabled(&self) -> bool {
7232 match self.edit_prediction_settings {
7233 EditPredictionSettings::Disabled => false,
7234 EditPredictionSettings::Enabled { .. } => true,
7235 }
7236 }
7237
7238 fn edit_prediction_requires_modifier(&self) -> bool {
7239 match self.edit_prediction_settings {
7240 EditPredictionSettings::Disabled => false,
7241 EditPredictionSettings::Enabled {
7242 preview_requires_modifier,
7243 ..
7244 } => preview_requires_modifier,
7245 }
7246 }
7247
7248 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7249 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7250 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7251 self.discard_edit_prediction(false, cx);
7252 } else {
7253 let selection = self.selections.newest_anchor();
7254 let cursor = selection.head();
7255
7256 if let Some((buffer, cursor_buffer_position)) =
7257 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7258 {
7259 self.edit_prediction_settings =
7260 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7261 }
7262 }
7263 }
7264
7265 fn edit_prediction_settings_at_position(
7266 &self,
7267 buffer: &Entity<Buffer>,
7268 buffer_position: language::Anchor,
7269 cx: &App,
7270 ) -> EditPredictionSettings {
7271 if !self.mode.is_full()
7272 || !self.show_edit_predictions_override.unwrap_or(true)
7273 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7274 {
7275 return EditPredictionSettings::Disabled;
7276 }
7277
7278 let buffer = buffer.read(cx);
7279
7280 let file = buffer.file();
7281
7282 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7283 return EditPredictionSettings::Disabled;
7284 };
7285
7286 let by_provider = matches!(
7287 self.menu_edit_predictions_policy,
7288 MenuEditPredictionsPolicy::ByProvider
7289 );
7290
7291 let show_in_menu = by_provider
7292 && self
7293 .edit_prediction_provider
7294 .as_ref()
7295 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7296
7297 let preview_requires_modifier =
7298 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7299
7300 EditPredictionSettings::Enabled {
7301 show_in_menu,
7302 preview_requires_modifier,
7303 }
7304 }
7305
7306 fn should_show_edit_predictions(&self) -> bool {
7307 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7308 }
7309
7310 pub fn edit_prediction_preview_is_active(&self) -> bool {
7311 matches!(
7312 self.edit_prediction_preview,
7313 EditPredictionPreview::Active { .. }
7314 )
7315 }
7316
7317 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7318 let cursor = self.selections.newest_anchor().head();
7319 if let Some((buffer, cursor_position)) =
7320 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7321 {
7322 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7323 } else {
7324 false
7325 }
7326 }
7327
7328 pub fn supports_minimap(&self, cx: &App) -> bool {
7329 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7330 }
7331
7332 fn edit_predictions_enabled_in_buffer(
7333 &self,
7334 buffer: &Entity<Buffer>,
7335 buffer_position: language::Anchor,
7336 cx: &App,
7337 ) -> bool {
7338 maybe!({
7339 if self.read_only(cx) {
7340 return Some(false);
7341 }
7342 let provider = self.edit_prediction_provider()?;
7343 if !provider.is_enabled(buffer, buffer_position, cx) {
7344 return Some(false);
7345 }
7346 let buffer = buffer.read(cx);
7347 let Some(file) = buffer.file() else {
7348 return Some(true);
7349 };
7350 let settings = all_language_settings(Some(file), cx);
7351 Some(settings.edit_predictions_enabled_for_file(file, cx))
7352 })
7353 .unwrap_or(false)
7354 }
7355
7356 fn cycle_edit_prediction(
7357 &mut self,
7358 direction: Direction,
7359 window: &mut Window,
7360 cx: &mut Context<Self>,
7361 ) -> Option<()> {
7362 let provider = self.edit_prediction_provider()?;
7363 let cursor = self.selections.newest_anchor().head();
7364 let (buffer, cursor_buffer_position) =
7365 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7366 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7367 return None;
7368 }
7369
7370 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7371 self.update_visible_edit_prediction(window, cx);
7372
7373 Some(())
7374 }
7375
7376 pub fn show_edit_prediction(
7377 &mut self,
7378 _: &ShowEditPrediction,
7379 window: &mut Window,
7380 cx: &mut Context<Self>,
7381 ) {
7382 if !self.has_active_edit_prediction() {
7383 self.refresh_edit_prediction(false, true, window, cx);
7384 return;
7385 }
7386
7387 self.update_visible_edit_prediction(window, cx);
7388 }
7389
7390 pub fn display_cursor_names(
7391 &mut self,
7392 _: &DisplayCursorNames,
7393 window: &mut Window,
7394 cx: &mut Context<Self>,
7395 ) {
7396 self.show_cursor_names(window, cx);
7397 }
7398
7399 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7400 self.show_cursor_names = true;
7401 cx.notify();
7402 cx.spawn_in(window, async move |this, cx| {
7403 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7404 this.update(cx, |this, cx| {
7405 this.show_cursor_names = false;
7406 cx.notify()
7407 })
7408 .ok()
7409 })
7410 .detach();
7411 }
7412
7413 pub fn next_edit_prediction(
7414 &mut self,
7415 _: &NextEditPrediction,
7416 window: &mut Window,
7417 cx: &mut Context<Self>,
7418 ) {
7419 if self.has_active_edit_prediction() {
7420 self.cycle_edit_prediction(Direction::Next, window, cx);
7421 } else {
7422 let is_copilot_disabled = self
7423 .refresh_edit_prediction(false, true, window, cx)
7424 .is_none();
7425 if is_copilot_disabled {
7426 cx.propagate();
7427 }
7428 }
7429 }
7430
7431 pub fn previous_edit_prediction(
7432 &mut self,
7433 _: &PreviousEditPrediction,
7434 window: &mut Window,
7435 cx: &mut Context<Self>,
7436 ) {
7437 if self.has_active_edit_prediction() {
7438 self.cycle_edit_prediction(Direction::Prev, window, cx);
7439 } else {
7440 let is_copilot_disabled = self
7441 .refresh_edit_prediction(false, true, window, cx)
7442 .is_none();
7443 if is_copilot_disabled {
7444 cx.propagate();
7445 }
7446 }
7447 }
7448
7449 pub fn accept_edit_prediction(
7450 &mut self,
7451 _: &AcceptEditPrediction,
7452 window: &mut Window,
7453 cx: &mut Context<Self>,
7454 ) {
7455 if self.show_edit_predictions_in_menu() {
7456 self.hide_context_menu(window, cx);
7457 }
7458
7459 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7460 return;
7461 };
7462
7463 match &active_edit_prediction.completion {
7464 EditPrediction::MoveWithin { target, .. } => {
7465 let target = *target;
7466
7467 if let Some(position_map) = &self.last_position_map {
7468 if position_map
7469 .visible_row_range
7470 .contains(&target.to_display_point(&position_map.snapshot).row())
7471 || !self.edit_prediction_requires_modifier()
7472 {
7473 self.unfold_ranges(&[target..target], true, false, cx);
7474 // Note that this is also done in vim's handler of the Tab action.
7475 self.change_selections(
7476 SelectionEffects::scroll(Autoscroll::newest()),
7477 window,
7478 cx,
7479 |selections| {
7480 selections.select_anchor_ranges([target..target]);
7481 },
7482 );
7483 self.clear_row_highlights::<EditPredictionPreview>();
7484
7485 self.edit_prediction_preview
7486 .set_previous_scroll_position(None);
7487 } else {
7488 self.edit_prediction_preview
7489 .set_previous_scroll_position(Some(
7490 position_map.snapshot.scroll_anchor,
7491 ));
7492
7493 self.highlight_rows::<EditPredictionPreview>(
7494 target..target,
7495 cx.theme().colors().editor_highlighted_line_background,
7496 RowHighlightOptions {
7497 autoscroll: true,
7498 ..Default::default()
7499 },
7500 cx,
7501 );
7502 self.request_autoscroll(Autoscroll::fit(), cx);
7503 }
7504 }
7505 }
7506 EditPrediction::MoveOutside { snapshot, target } => {
7507 if let Some(workspace) = self.workspace() {
7508 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7509 .detach_and_log_err(cx);
7510 }
7511 }
7512 EditPrediction::Edit { edits, .. } => {
7513 self.report_edit_prediction_event(
7514 active_edit_prediction.completion_id.clone(),
7515 true,
7516 cx,
7517 );
7518
7519 if let Some(provider) = self.edit_prediction_provider() {
7520 provider.accept(cx);
7521 }
7522
7523 // Store the transaction ID and selections before applying the edit
7524 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7525
7526 let snapshot = self.buffer.read(cx).snapshot(cx);
7527 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7528
7529 self.buffer.update(cx, |buffer, cx| {
7530 buffer.edit(edits.iter().cloned(), None, cx)
7531 });
7532
7533 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7534 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7535 });
7536
7537 let selections = self.selections.disjoint_anchors_arc();
7538 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7539 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7540 if has_new_transaction {
7541 self.selection_history
7542 .insert_transaction(transaction_id_now, selections);
7543 }
7544 }
7545
7546 self.update_visible_edit_prediction(window, cx);
7547 if self.active_edit_prediction.is_none() {
7548 self.refresh_edit_prediction(true, true, window, cx);
7549 }
7550
7551 cx.notify();
7552 }
7553 }
7554
7555 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7556 }
7557
7558 pub fn accept_partial_edit_prediction(
7559 &mut self,
7560 _: &AcceptPartialEditPrediction,
7561 window: &mut Window,
7562 cx: &mut Context<Self>,
7563 ) {
7564 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7565 return;
7566 };
7567 if self.selections.count() != 1 {
7568 return;
7569 }
7570
7571 match &active_edit_prediction.completion {
7572 EditPrediction::MoveWithin { target, .. } => {
7573 let target = *target;
7574 self.change_selections(
7575 SelectionEffects::scroll(Autoscroll::newest()),
7576 window,
7577 cx,
7578 |selections| {
7579 selections.select_anchor_ranges([target..target]);
7580 },
7581 );
7582 }
7583 EditPrediction::MoveOutside { snapshot, target } => {
7584 if let Some(workspace) = self.workspace() {
7585 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7586 .detach_and_log_err(cx);
7587 }
7588 }
7589 EditPrediction::Edit { edits, .. } => {
7590 self.report_edit_prediction_event(
7591 active_edit_prediction.completion_id.clone(),
7592 true,
7593 cx,
7594 );
7595
7596 // Find an insertion that starts at the cursor position.
7597 let snapshot = self.buffer.read(cx).snapshot(cx);
7598 let cursor_offset = self
7599 .selections
7600 .newest::<usize>(&self.display_snapshot(cx))
7601 .head();
7602 let insertion = edits.iter().find_map(|(range, text)| {
7603 let range = range.to_offset(&snapshot);
7604 if range.is_empty() && range.start == cursor_offset {
7605 Some(text)
7606 } else {
7607 None
7608 }
7609 });
7610
7611 if let Some(text) = insertion {
7612 let mut partial_completion = text
7613 .chars()
7614 .by_ref()
7615 .take_while(|c| c.is_alphabetic())
7616 .collect::<String>();
7617 if partial_completion.is_empty() {
7618 partial_completion = text
7619 .chars()
7620 .by_ref()
7621 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7622 .collect::<String>();
7623 }
7624
7625 cx.emit(EditorEvent::InputHandled {
7626 utf16_range_to_replace: None,
7627 text: partial_completion.clone().into(),
7628 });
7629
7630 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7631
7632 self.refresh_edit_prediction(true, true, window, cx);
7633 cx.notify();
7634 } else {
7635 self.accept_edit_prediction(&Default::default(), window, cx);
7636 }
7637 }
7638 }
7639 }
7640
7641 fn discard_edit_prediction(
7642 &mut self,
7643 should_report_edit_prediction_event: bool,
7644 cx: &mut Context<Self>,
7645 ) -> bool {
7646 if should_report_edit_prediction_event {
7647 let completion_id = self
7648 .active_edit_prediction
7649 .as_ref()
7650 .and_then(|active_completion| active_completion.completion_id.clone());
7651
7652 self.report_edit_prediction_event(completion_id, false, cx);
7653 }
7654
7655 if let Some(provider) = self.edit_prediction_provider() {
7656 provider.discard(cx);
7657 }
7658
7659 self.take_active_edit_prediction(cx)
7660 }
7661
7662 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7663 let Some(provider) = self.edit_prediction_provider() else {
7664 return;
7665 };
7666
7667 let Some((_, buffer, _)) = self
7668 .buffer
7669 .read(cx)
7670 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7671 else {
7672 return;
7673 };
7674
7675 let extension = buffer
7676 .read(cx)
7677 .file()
7678 .and_then(|file| Some(file.path().extension()?.to_string()));
7679
7680 let event_type = match accepted {
7681 true => "Edit Prediction Accepted",
7682 false => "Edit Prediction Discarded",
7683 };
7684 telemetry::event!(
7685 event_type,
7686 provider = provider.name(),
7687 prediction_id = id,
7688 suggestion_accepted = accepted,
7689 file_extension = extension,
7690 );
7691 }
7692
7693 fn open_editor_at_anchor(
7694 snapshot: &language::BufferSnapshot,
7695 target: language::Anchor,
7696 workspace: &Entity<Workspace>,
7697 window: &mut Window,
7698 cx: &mut App,
7699 ) -> Task<Result<()>> {
7700 workspace.update(cx, |workspace, cx| {
7701 let path = snapshot.file().map(|file| file.full_path(cx));
7702 let Some(path) =
7703 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7704 else {
7705 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7706 };
7707 let target = text::ToPoint::to_point(&target, snapshot);
7708 let item = workspace.open_path(path, None, true, window, cx);
7709 window.spawn(cx, async move |cx| {
7710 let Some(editor) = item.await?.downcast::<Editor>() else {
7711 return Ok(());
7712 };
7713 editor
7714 .update_in(cx, |editor, window, cx| {
7715 editor.go_to_singleton_buffer_point(target, window, cx);
7716 })
7717 .ok();
7718 anyhow::Ok(())
7719 })
7720 })
7721 }
7722
7723 pub fn has_active_edit_prediction(&self) -> bool {
7724 self.active_edit_prediction.is_some()
7725 }
7726
7727 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7728 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7729 return false;
7730 };
7731
7732 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7733 self.clear_highlights::<EditPredictionHighlight>(cx);
7734 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7735 true
7736 }
7737
7738 /// Returns true when we're displaying the edit prediction popover below the cursor
7739 /// like we are not previewing and the LSP autocomplete menu is visible
7740 /// or we are in `when_holding_modifier` mode.
7741 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7742 if self.edit_prediction_preview_is_active()
7743 || !self.show_edit_predictions_in_menu()
7744 || !self.edit_predictions_enabled()
7745 {
7746 return false;
7747 }
7748
7749 if self.has_visible_completions_menu() {
7750 return true;
7751 }
7752
7753 has_completion && self.edit_prediction_requires_modifier()
7754 }
7755
7756 fn handle_modifiers_changed(
7757 &mut self,
7758 modifiers: Modifiers,
7759 position_map: &PositionMap,
7760 window: &mut Window,
7761 cx: &mut Context<Self>,
7762 ) {
7763 // Ensure that the edit prediction preview is updated, even when not
7764 // enabled, if there's an active edit prediction preview.
7765 if self.show_edit_predictions_in_menu()
7766 || matches!(
7767 self.edit_prediction_preview,
7768 EditPredictionPreview::Active { .. }
7769 )
7770 {
7771 self.update_edit_prediction_preview(&modifiers, window, cx);
7772 }
7773
7774 self.update_selection_mode(&modifiers, position_map, window, cx);
7775
7776 let mouse_position = window.mouse_position();
7777 if !position_map.text_hitbox.is_hovered(window) {
7778 return;
7779 }
7780
7781 self.update_hovered_link(
7782 position_map.point_for_position(mouse_position),
7783 &position_map.snapshot,
7784 modifiers,
7785 window,
7786 cx,
7787 )
7788 }
7789
7790 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7791 match EditorSettings::get_global(cx).multi_cursor_modifier {
7792 MultiCursorModifier::Alt => modifiers.secondary(),
7793 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7794 }
7795 }
7796
7797 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7798 match EditorSettings::get_global(cx).multi_cursor_modifier {
7799 MultiCursorModifier::Alt => modifiers.alt,
7800 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7801 }
7802 }
7803
7804 fn columnar_selection_mode(
7805 modifiers: &Modifiers,
7806 cx: &mut Context<Self>,
7807 ) -> Option<ColumnarMode> {
7808 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7809 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7810 Some(ColumnarMode::FromMouse)
7811 } else if Self::is_alt_pressed(modifiers, cx) {
7812 Some(ColumnarMode::FromSelection)
7813 } else {
7814 None
7815 }
7816 } else {
7817 None
7818 }
7819 }
7820
7821 fn update_selection_mode(
7822 &mut self,
7823 modifiers: &Modifiers,
7824 position_map: &PositionMap,
7825 window: &mut Window,
7826 cx: &mut Context<Self>,
7827 ) {
7828 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7829 return;
7830 };
7831 if self.selections.pending_anchor().is_none() {
7832 return;
7833 }
7834
7835 let mouse_position = window.mouse_position();
7836 let point_for_position = position_map.point_for_position(mouse_position);
7837 let position = point_for_position.previous_valid;
7838
7839 self.select(
7840 SelectPhase::BeginColumnar {
7841 position,
7842 reset: false,
7843 mode,
7844 goal_column: point_for_position.exact_unclipped.column(),
7845 },
7846 window,
7847 cx,
7848 );
7849 }
7850
7851 fn update_edit_prediction_preview(
7852 &mut self,
7853 modifiers: &Modifiers,
7854 window: &mut Window,
7855 cx: &mut Context<Self>,
7856 ) {
7857 let mut modifiers_held = false;
7858 if let Some(accept_keystroke) = self
7859 .accept_edit_prediction_keybind(false, window, cx)
7860 .keystroke()
7861 {
7862 modifiers_held = modifiers_held
7863 || (accept_keystroke.modifiers() == modifiers
7864 && accept_keystroke.modifiers().modified());
7865 };
7866 if let Some(accept_partial_keystroke) = self
7867 .accept_edit_prediction_keybind(true, window, cx)
7868 .keystroke()
7869 {
7870 modifiers_held = modifiers_held
7871 || (accept_partial_keystroke.modifiers() == modifiers
7872 && accept_partial_keystroke.modifiers().modified());
7873 }
7874
7875 if modifiers_held {
7876 if matches!(
7877 self.edit_prediction_preview,
7878 EditPredictionPreview::Inactive { .. }
7879 ) {
7880 if let Some(provider) = self.edit_prediction_provider.as_ref() {
7881 provider.provider.did_show(cx)
7882 }
7883
7884 self.edit_prediction_preview = EditPredictionPreview::Active {
7885 previous_scroll_position: None,
7886 since: Instant::now(),
7887 };
7888
7889 self.update_visible_edit_prediction(window, cx);
7890 cx.notify();
7891 }
7892 } else if let EditPredictionPreview::Active {
7893 previous_scroll_position,
7894 since,
7895 } = self.edit_prediction_preview
7896 {
7897 if let (Some(previous_scroll_position), Some(position_map)) =
7898 (previous_scroll_position, self.last_position_map.as_ref())
7899 {
7900 self.set_scroll_position(
7901 previous_scroll_position
7902 .scroll_position(&position_map.snapshot.display_snapshot),
7903 window,
7904 cx,
7905 );
7906 }
7907
7908 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7909 released_too_fast: since.elapsed() < Duration::from_millis(200),
7910 };
7911 self.clear_row_highlights::<EditPredictionPreview>();
7912 self.update_visible_edit_prediction(window, cx);
7913 cx.notify();
7914 }
7915 }
7916
7917 fn update_visible_edit_prediction(
7918 &mut self,
7919 _window: &mut Window,
7920 cx: &mut Context<Self>,
7921 ) -> Option<()> {
7922 if DisableAiSettings::get_global(cx).disable_ai {
7923 return None;
7924 }
7925
7926 if self.ime_transaction.is_some() {
7927 self.discard_edit_prediction(false, cx);
7928 return None;
7929 }
7930
7931 let selection = self.selections.newest_anchor();
7932 let cursor = selection.head();
7933 let multibuffer = self.buffer.read(cx).snapshot(cx);
7934 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7935 let excerpt_id = cursor.excerpt_id;
7936
7937 let show_in_menu = self.show_edit_predictions_in_menu();
7938 let completions_menu_has_precedence = !show_in_menu
7939 && (self.context_menu.borrow().is_some()
7940 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7941
7942 if completions_menu_has_precedence
7943 || !offset_selection.is_empty()
7944 || self
7945 .active_edit_prediction
7946 .as_ref()
7947 .is_some_and(|completion| {
7948 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7949 return false;
7950 };
7951 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7952 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7953 !invalidation_range.contains(&offset_selection.head())
7954 })
7955 {
7956 self.discard_edit_prediction(false, cx);
7957 return None;
7958 }
7959
7960 self.take_active_edit_prediction(cx);
7961 let Some(provider) = self.edit_prediction_provider() else {
7962 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7963 return None;
7964 };
7965
7966 let (buffer, cursor_buffer_position) =
7967 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7968
7969 self.edit_prediction_settings =
7970 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7971
7972 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7973
7974 if self.edit_prediction_indent_conflict {
7975 let cursor_point = cursor.to_point(&multibuffer);
7976
7977 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7978
7979 if let Some((_, indent)) = indents.iter().next()
7980 && indent.len == cursor_point.column
7981 {
7982 self.edit_prediction_indent_conflict = false;
7983 }
7984 }
7985
7986 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7987
7988 let (completion_id, edits, edit_preview) = match edit_prediction {
7989 edit_prediction::EditPrediction::Local {
7990 id,
7991 edits,
7992 edit_preview,
7993 } => (id, edits, edit_preview),
7994 edit_prediction::EditPrediction::Jump {
7995 id,
7996 snapshot,
7997 target,
7998 } => {
7999 self.stale_edit_prediction_in_menu = None;
8000 self.active_edit_prediction = Some(EditPredictionState {
8001 inlay_ids: vec![],
8002 completion: EditPrediction::MoveOutside { snapshot, target },
8003 completion_id: id,
8004 invalidation_range: None,
8005 });
8006 cx.notify();
8007 return Some(());
8008 }
8009 };
8010
8011 let edits = edits
8012 .into_iter()
8013 .flat_map(|(range, new_text)| {
8014 Some((
8015 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8016 new_text,
8017 ))
8018 })
8019 .collect::<Vec<_>>();
8020 if edits.is_empty() {
8021 return None;
8022 }
8023
8024 let first_edit_start = edits.first().unwrap().0.start;
8025 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8026 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8027
8028 let last_edit_end = edits.last().unwrap().0.end;
8029 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8030 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8031
8032 let cursor_row = cursor.to_point(&multibuffer).row;
8033
8034 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8035
8036 let mut inlay_ids = Vec::new();
8037 let invalidation_row_range;
8038 let move_invalidation_row_range = if cursor_row < edit_start_row {
8039 Some(cursor_row..edit_end_row)
8040 } else if cursor_row > edit_end_row {
8041 Some(edit_start_row..cursor_row)
8042 } else {
8043 None
8044 };
8045 let supports_jump = self
8046 .edit_prediction_provider
8047 .as_ref()
8048 .map(|provider| provider.provider.supports_jump_to_edit())
8049 .unwrap_or(true);
8050
8051 let is_move = supports_jump
8052 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8053 let completion = if is_move {
8054 invalidation_row_range =
8055 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8056 let target = first_edit_start;
8057 EditPrediction::MoveWithin { target, snapshot }
8058 } else {
8059 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8060 && !self.edit_predictions_hidden_for_vim_mode;
8061
8062 if show_completions_in_buffer {
8063 if let Some(provider) = &self.edit_prediction_provider {
8064 provider.provider.did_show(cx);
8065 }
8066 if edits
8067 .iter()
8068 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8069 {
8070 let mut inlays = Vec::new();
8071 for (range, new_text) in &edits {
8072 let inlay = Inlay::edit_prediction(
8073 post_inc(&mut self.next_inlay_id),
8074 range.start,
8075 new_text.as_ref(),
8076 );
8077 inlay_ids.push(inlay.id);
8078 inlays.push(inlay);
8079 }
8080
8081 self.splice_inlays(&[], inlays, cx);
8082 } else {
8083 let background_color = cx.theme().status().deleted_background;
8084 self.highlight_text::<EditPredictionHighlight>(
8085 edits.iter().map(|(range, _)| range.clone()).collect(),
8086 HighlightStyle {
8087 background_color: Some(background_color),
8088 ..Default::default()
8089 },
8090 cx,
8091 );
8092 }
8093 }
8094
8095 invalidation_row_range = edit_start_row..edit_end_row;
8096
8097 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8098 if provider.show_tab_accept_marker() {
8099 EditDisplayMode::TabAccept
8100 } else {
8101 EditDisplayMode::Inline
8102 }
8103 } else {
8104 EditDisplayMode::DiffPopover
8105 };
8106
8107 EditPrediction::Edit {
8108 edits,
8109 edit_preview,
8110 display_mode,
8111 snapshot,
8112 }
8113 };
8114
8115 let invalidation_range = multibuffer
8116 .anchor_before(Point::new(invalidation_row_range.start, 0))
8117 ..multibuffer.anchor_after(Point::new(
8118 invalidation_row_range.end,
8119 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8120 ));
8121
8122 self.stale_edit_prediction_in_menu = None;
8123 self.active_edit_prediction = Some(EditPredictionState {
8124 inlay_ids,
8125 completion,
8126 completion_id,
8127 invalidation_range: Some(invalidation_range),
8128 });
8129
8130 cx.notify();
8131
8132 Some(())
8133 }
8134
8135 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8136 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8137 }
8138
8139 fn clear_tasks(&mut self) {
8140 self.tasks.clear()
8141 }
8142
8143 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8144 if self.tasks.insert(key, value).is_some() {
8145 // This case should hopefully be rare, but just in case...
8146 log::error!(
8147 "multiple different run targets found on a single line, only the last target will be rendered"
8148 )
8149 }
8150 }
8151
8152 /// Get all display points of breakpoints that will be rendered within editor
8153 ///
8154 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8155 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8156 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8157 fn active_breakpoints(
8158 &self,
8159 range: Range<DisplayRow>,
8160 window: &mut Window,
8161 cx: &mut Context<Self>,
8162 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8163 let mut breakpoint_display_points = HashMap::default();
8164
8165 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8166 return breakpoint_display_points;
8167 };
8168
8169 let snapshot = self.snapshot(window, cx);
8170
8171 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8172 let Some(project) = self.project() else {
8173 return breakpoint_display_points;
8174 };
8175
8176 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8177 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8178
8179 for (buffer_snapshot, range, excerpt_id) in
8180 multi_buffer_snapshot.range_to_buffer_ranges(range)
8181 {
8182 let Some(buffer) = project
8183 .read(cx)
8184 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8185 else {
8186 continue;
8187 };
8188 let breakpoints = breakpoint_store.read(cx).breakpoints(
8189 &buffer,
8190 Some(
8191 buffer_snapshot.anchor_before(range.start)
8192 ..buffer_snapshot.anchor_after(range.end),
8193 ),
8194 buffer_snapshot,
8195 cx,
8196 );
8197 for (breakpoint, state) in breakpoints {
8198 let multi_buffer_anchor =
8199 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8200 let position = multi_buffer_anchor
8201 .to_point(&multi_buffer_snapshot)
8202 .to_display_point(&snapshot);
8203
8204 breakpoint_display_points.insert(
8205 position.row(),
8206 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8207 );
8208 }
8209 }
8210
8211 breakpoint_display_points
8212 }
8213
8214 fn breakpoint_context_menu(
8215 &self,
8216 anchor: Anchor,
8217 window: &mut Window,
8218 cx: &mut Context<Self>,
8219 ) -> Entity<ui::ContextMenu> {
8220 let weak_editor = cx.weak_entity();
8221 let focus_handle = self.focus_handle(cx);
8222
8223 let row = self
8224 .buffer
8225 .read(cx)
8226 .snapshot(cx)
8227 .summary_for_anchor::<Point>(&anchor)
8228 .row;
8229
8230 let breakpoint = self
8231 .breakpoint_at_row(row, window, cx)
8232 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8233
8234 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8235 "Edit Log Breakpoint"
8236 } else {
8237 "Set Log Breakpoint"
8238 };
8239
8240 let condition_breakpoint_msg = if breakpoint
8241 .as_ref()
8242 .is_some_and(|bp| bp.1.condition.is_some())
8243 {
8244 "Edit Condition Breakpoint"
8245 } else {
8246 "Set Condition Breakpoint"
8247 };
8248
8249 let hit_condition_breakpoint_msg = if breakpoint
8250 .as_ref()
8251 .is_some_and(|bp| bp.1.hit_condition.is_some())
8252 {
8253 "Edit Hit Condition Breakpoint"
8254 } else {
8255 "Set Hit Condition Breakpoint"
8256 };
8257
8258 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8259 "Unset Breakpoint"
8260 } else {
8261 "Set Breakpoint"
8262 };
8263
8264 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8265
8266 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8267 BreakpointState::Enabled => Some("Disable"),
8268 BreakpointState::Disabled => Some("Enable"),
8269 });
8270
8271 let (anchor, breakpoint) =
8272 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8273
8274 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8275 menu.on_blur_subscription(Subscription::new(|| {}))
8276 .context(focus_handle)
8277 .when(run_to_cursor, |this| {
8278 let weak_editor = weak_editor.clone();
8279 this.entry("Run to cursor", None, move |window, cx| {
8280 weak_editor
8281 .update(cx, |editor, cx| {
8282 editor.change_selections(
8283 SelectionEffects::no_scroll(),
8284 window,
8285 cx,
8286 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8287 );
8288 })
8289 .ok();
8290
8291 window.dispatch_action(Box::new(RunToCursor), cx);
8292 })
8293 .separator()
8294 })
8295 .when_some(toggle_state_msg, |this, msg| {
8296 this.entry(msg, None, {
8297 let weak_editor = weak_editor.clone();
8298 let breakpoint = breakpoint.clone();
8299 move |_window, cx| {
8300 weak_editor
8301 .update(cx, |this, cx| {
8302 this.edit_breakpoint_at_anchor(
8303 anchor,
8304 breakpoint.as_ref().clone(),
8305 BreakpointEditAction::InvertState,
8306 cx,
8307 );
8308 })
8309 .log_err();
8310 }
8311 })
8312 })
8313 .entry(set_breakpoint_msg, None, {
8314 let weak_editor = weak_editor.clone();
8315 let breakpoint = breakpoint.clone();
8316 move |_window, cx| {
8317 weak_editor
8318 .update(cx, |this, cx| {
8319 this.edit_breakpoint_at_anchor(
8320 anchor,
8321 breakpoint.as_ref().clone(),
8322 BreakpointEditAction::Toggle,
8323 cx,
8324 );
8325 })
8326 .log_err();
8327 }
8328 })
8329 .entry(log_breakpoint_msg, None, {
8330 let breakpoint = breakpoint.clone();
8331 let weak_editor = weak_editor.clone();
8332 move |window, cx| {
8333 weak_editor
8334 .update(cx, |this, cx| {
8335 this.add_edit_breakpoint_block(
8336 anchor,
8337 breakpoint.as_ref(),
8338 BreakpointPromptEditAction::Log,
8339 window,
8340 cx,
8341 );
8342 })
8343 .log_err();
8344 }
8345 })
8346 .entry(condition_breakpoint_msg, None, {
8347 let breakpoint = breakpoint.clone();
8348 let weak_editor = weak_editor.clone();
8349 move |window, cx| {
8350 weak_editor
8351 .update(cx, |this, cx| {
8352 this.add_edit_breakpoint_block(
8353 anchor,
8354 breakpoint.as_ref(),
8355 BreakpointPromptEditAction::Condition,
8356 window,
8357 cx,
8358 );
8359 })
8360 .log_err();
8361 }
8362 })
8363 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8364 weak_editor
8365 .update(cx, |this, cx| {
8366 this.add_edit_breakpoint_block(
8367 anchor,
8368 breakpoint.as_ref(),
8369 BreakpointPromptEditAction::HitCondition,
8370 window,
8371 cx,
8372 );
8373 })
8374 .log_err();
8375 })
8376 })
8377 }
8378
8379 fn render_breakpoint(
8380 &self,
8381 position: Anchor,
8382 row: DisplayRow,
8383 breakpoint: &Breakpoint,
8384 state: Option<BreakpointSessionState>,
8385 cx: &mut Context<Self>,
8386 ) -> IconButton {
8387 let is_rejected = state.is_some_and(|s| !s.verified);
8388 // Is it a breakpoint that shows up when hovering over gutter?
8389 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8390 (false, false),
8391 |PhantomBreakpointIndicator {
8392 is_active,
8393 display_row,
8394 collides_with_existing_breakpoint,
8395 }| {
8396 (
8397 is_active && display_row == row,
8398 collides_with_existing_breakpoint,
8399 )
8400 },
8401 );
8402
8403 let (color, icon) = {
8404 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8405 (false, false) => ui::IconName::DebugBreakpoint,
8406 (true, false) => ui::IconName::DebugLogBreakpoint,
8407 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8408 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8409 };
8410
8411 let color = if is_phantom {
8412 Color::Hint
8413 } else if is_rejected {
8414 Color::Disabled
8415 } else {
8416 Color::Debugger
8417 };
8418
8419 (color, icon)
8420 };
8421
8422 let breakpoint = Arc::from(breakpoint.clone());
8423
8424 let alt_as_text = gpui::Keystroke {
8425 modifiers: Modifiers::secondary_key(),
8426 ..Default::default()
8427 };
8428 let primary_action_text = if breakpoint.is_disabled() {
8429 "Enable breakpoint"
8430 } else if is_phantom && !collides_with_existing {
8431 "Set breakpoint"
8432 } else {
8433 "Unset breakpoint"
8434 };
8435 let focus_handle = self.focus_handle.clone();
8436
8437 let meta = if is_rejected {
8438 SharedString::from("No executable code is associated with this line.")
8439 } else if collides_with_existing && !breakpoint.is_disabled() {
8440 SharedString::from(format!(
8441 "{alt_as_text}-click to disable,\nright-click for more options."
8442 ))
8443 } else {
8444 SharedString::from("Right-click for more options.")
8445 };
8446 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8447 .icon_size(IconSize::XSmall)
8448 .size(ui::ButtonSize::None)
8449 .when(is_rejected, |this| {
8450 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8451 })
8452 .icon_color(color)
8453 .style(ButtonStyle::Transparent)
8454 .on_click(cx.listener({
8455 move |editor, event: &ClickEvent, window, cx| {
8456 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8457 BreakpointEditAction::InvertState
8458 } else {
8459 BreakpointEditAction::Toggle
8460 };
8461
8462 window.focus(&editor.focus_handle(cx));
8463 editor.edit_breakpoint_at_anchor(
8464 position,
8465 breakpoint.as_ref().clone(),
8466 edit_action,
8467 cx,
8468 );
8469 }
8470 }))
8471 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8472 editor.set_breakpoint_context_menu(
8473 row,
8474 Some(position),
8475 event.position(),
8476 window,
8477 cx,
8478 );
8479 }))
8480 .tooltip(move |_window, cx| {
8481 Tooltip::with_meta_in(
8482 primary_action_text,
8483 Some(&ToggleBreakpoint),
8484 meta.clone(),
8485 &focus_handle,
8486 cx,
8487 )
8488 })
8489 }
8490
8491 fn build_tasks_context(
8492 project: &Entity<Project>,
8493 buffer: &Entity<Buffer>,
8494 buffer_row: u32,
8495 tasks: &Arc<RunnableTasks>,
8496 cx: &mut Context<Self>,
8497 ) -> Task<Option<task::TaskContext>> {
8498 let position = Point::new(buffer_row, tasks.column);
8499 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8500 let location = Location {
8501 buffer: buffer.clone(),
8502 range: range_start..range_start,
8503 };
8504 // Fill in the environmental variables from the tree-sitter captures
8505 let mut captured_task_variables = TaskVariables::default();
8506 for (capture_name, value) in tasks.extra_variables.clone() {
8507 captured_task_variables.insert(
8508 task::VariableName::Custom(capture_name.into()),
8509 value.clone(),
8510 );
8511 }
8512 project.update(cx, |project, cx| {
8513 project.task_store().update(cx, |task_store, cx| {
8514 task_store.task_context_for_location(captured_task_variables, location, cx)
8515 })
8516 })
8517 }
8518
8519 pub fn spawn_nearest_task(
8520 &mut self,
8521 action: &SpawnNearestTask,
8522 window: &mut Window,
8523 cx: &mut Context<Self>,
8524 ) {
8525 let Some((workspace, _)) = self.workspace.clone() else {
8526 return;
8527 };
8528 let Some(project) = self.project.clone() else {
8529 return;
8530 };
8531
8532 // Try to find a closest, enclosing node using tree-sitter that has a task
8533 let Some((buffer, buffer_row, tasks)) = self
8534 .find_enclosing_node_task(cx)
8535 // Or find the task that's closest in row-distance.
8536 .or_else(|| self.find_closest_task(cx))
8537 else {
8538 return;
8539 };
8540
8541 let reveal_strategy = action.reveal;
8542 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8543 cx.spawn_in(window, async move |_, cx| {
8544 let context = task_context.await?;
8545 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8546
8547 let resolved = &mut resolved_task.resolved;
8548 resolved.reveal = reveal_strategy;
8549
8550 workspace
8551 .update_in(cx, |workspace, window, cx| {
8552 workspace.schedule_resolved_task(
8553 task_source_kind,
8554 resolved_task,
8555 false,
8556 window,
8557 cx,
8558 );
8559 })
8560 .ok()
8561 })
8562 .detach();
8563 }
8564
8565 fn find_closest_task(
8566 &mut self,
8567 cx: &mut Context<Self>,
8568 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8569 let cursor_row = self
8570 .selections
8571 .newest_adjusted(&self.display_snapshot(cx))
8572 .head()
8573 .row;
8574
8575 let ((buffer_id, row), tasks) = self
8576 .tasks
8577 .iter()
8578 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8579
8580 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8581 let tasks = Arc::new(tasks.to_owned());
8582 Some((buffer, *row, tasks))
8583 }
8584
8585 fn find_enclosing_node_task(
8586 &mut self,
8587 cx: &mut Context<Self>,
8588 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8589 let snapshot = self.buffer.read(cx).snapshot(cx);
8590 let offset = self
8591 .selections
8592 .newest::<usize>(&self.display_snapshot(cx))
8593 .head();
8594 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8595 let buffer_id = excerpt.buffer().remote_id();
8596
8597 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8598 let mut cursor = layer.node().walk();
8599
8600 while cursor.goto_first_child_for_byte(offset).is_some() {
8601 if cursor.node().end_byte() == offset {
8602 cursor.goto_next_sibling();
8603 }
8604 }
8605
8606 // Ascend to the smallest ancestor that contains the range and has a task.
8607 loop {
8608 let node = cursor.node();
8609 let node_range = node.byte_range();
8610 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8611
8612 // Check if this node contains our offset
8613 if node_range.start <= offset && node_range.end >= offset {
8614 // If it contains offset, check for task
8615 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8616 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8617 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8618 }
8619 }
8620
8621 if !cursor.goto_parent() {
8622 break;
8623 }
8624 }
8625 None
8626 }
8627
8628 fn render_run_indicator(
8629 &self,
8630 _style: &EditorStyle,
8631 is_active: bool,
8632 row: DisplayRow,
8633 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8634 cx: &mut Context<Self>,
8635 ) -> IconButton {
8636 let color = Color::Muted;
8637 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8638
8639 IconButton::new(
8640 ("run_indicator", row.0 as usize),
8641 ui::IconName::PlayOutlined,
8642 )
8643 .shape(ui::IconButtonShape::Square)
8644 .icon_size(IconSize::XSmall)
8645 .icon_color(color)
8646 .toggle_state(is_active)
8647 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8648 let quick_launch = match e {
8649 ClickEvent::Keyboard(_) => true,
8650 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8651 };
8652
8653 window.focus(&editor.focus_handle(cx));
8654 editor.toggle_code_actions(
8655 &ToggleCodeActions {
8656 deployed_from: Some(CodeActionSource::RunMenu(row)),
8657 quick_launch,
8658 },
8659 window,
8660 cx,
8661 );
8662 }))
8663 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8664 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8665 }))
8666 }
8667
8668 pub fn context_menu_visible(&self) -> bool {
8669 !self.edit_prediction_preview_is_active()
8670 && self
8671 .context_menu
8672 .borrow()
8673 .as_ref()
8674 .is_some_and(|menu| menu.visible())
8675 }
8676
8677 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8678 self.context_menu
8679 .borrow()
8680 .as_ref()
8681 .map(|menu| menu.origin())
8682 }
8683
8684 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8685 self.context_menu_options = Some(options);
8686 }
8687
8688 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8689 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8690
8691 fn render_edit_prediction_popover(
8692 &mut self,
8693 text_bounds: &Bounds<Pixels>,
8694 content_origin: gpui::Point<Pixels>,
8695 right_margin: Pixels,
8696 editor_snapshot: &EditorSnapshot,
8697 visible_row_range: Range<DisplayRow>,
8698 scroll_top: ScrollOffset,
8699 scroll_bottom: ScrollOffset,
8700 line_layouts: &[LineWithInvisibles],
8701 line_height: Pixels,
8702 scroll_position: gpui::Point<ScrollOffset>,
8703 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8704 newest_selection_head: Option<DisplayPoint>,
8705 editor_width: Pixels,
8706 style: &EditorStyle,
8707 window: &mut Window,
8708 cx: &mut App,
8709 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8710 if self.mode().is_minimap() {
8711 return None;
8712 }
8713 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8714
8715 if self.edit_prediction_visible_in_cursor_popover(true) {
8716 return None;
8717 }
8718
8719 match &active_edit_prediction.completion {
8720 EditPrediction::MoveWithin { target, .. } => {
8721 let target_display_point = target.to_display_point(editor_snapshot);
8722
8723 if self.edit_prediction_requires_modifier() {
8724 if !self.edit_prediction_preview_is_active() {
8725 return None;
8726 }
8727
8728 self.render_edit_prediction_modifier_jump_popover(
8729 text_bounds,
8730 content_origin,
8731 visible_row_range,
8732 line_layouts,
8733 line_height,
8734 scroll_pixel_position,
8735 newest_selection_head,
8736 target_display_point,
8737 window,
8738 cx,
8739 )
8740 } else {
8741 self.render_edit_prediction_eager_jump_popover(
8742 text_bounds,
8743 content_origin,
8744 editor_snapshot,
8745 visible_row_range,
8746 scroll_top,
8747 scroll_bottom,
8748 line_height,
8749 scroll_pixel_position,
8750 target_display_point,
8751 editor_width,
8752 window,
8753 cx,
8754 )
8755 }
8756 }
8757 EditPrediction::Edit {
8758 display_mode: EditDisplayMode::Inline,
8759 ..
8760 } => None,
8761 EditPrediction::Edit {
8762 display_mode: EditDisplayMode::TabAccept,
8763 edits,
8764 ..
8765 } => {
8766 let range = &edits.first()?.0;
8767 let target_display_point = range.end.to_display_point(editor_snapshot);
8768
8769 self.render_edit_prediction_end_of_line_popover(
8770 "Accept",
8771 editor_snapshot,
8772 visible_row_range,
8773 target_display_point,
8774 line_height,
8775 scroll_pixel_position,
8776 content_origin,
8777 editor_width,
8778 window,
8779 cx,
8780 )
8781 }
8782 EditPrediction::Edit {
8783 edits,
8784 edit_preview,
8785 display_mode: EditDisplayMode::DiffPopover,
8786 snapshot,
8787 } => self.render_edit_prediction_diff_popover(
8788 text_bounds,
8789 content_origin,
8790 right_margin,
8791 editor_snapshot,
8792 visible_row_range,
8793 line_layouts,
8794 line_height,
8795 scroll_position,
8796 scroll_pixel_position,
8797 newest_selection_head,
8798 editor_width,
8799 style,
8800 edits,
8801 edit_preview,
8802 snapshot,
8803 window,
8804 cx,
8805 ),
8806 EditPrediction::MoveOutside { snapshot, .. } => {
8807 let file_name = snapshot
8808 .file()
8809 .map(|file| file.file_name(cx))
8810 .unwrap_or("untitled");
8811 let mut element = self
8812 .render_edit_prediction_line_popover(
8813 format!("Jump to {file_name}"),
8814 Some(IconName::ZedPredict),
8815 window,
8816 cx,
8817 )
8818 .into_any();
8819
8820 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8821 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8822 let origin_y = text_bounds.size.height - size.height - px(30.);
8823 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8824 element.prepaint_at(origin, window, cx);
8825
8826 Some((element, origin))
8827 }
8828 }
8829 }
8830
8831 fn render_edit_prediction_modifier_jump_popover(
8832 &mut self,
8833 text_bounds: &Bounds<Pixels>,
8834 content_origin: gpui::Point<Pixels>,
8835 visible_row_range: Range<DisplayRow>,
8836 line_layouts: &[LineWithInvisibles],
8837 line_height: Pixels,
8838 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8839 newest_selection_head: Option<DisplayPoint>,
8840 target_display_point: DisplayPoint,
8841 window: &mut Window,
8842 cx: &mut App,
8843 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8844 let scrolled_content_origin =
8845 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8846
8847 const SCROLL_PADDING_Y: Pixels = px(12.);
8848
8849 if target_display_point.row() < visible_row_range.start {
8850 return self.render_edit_prediction_scroll_popover(
8851 |_| SCROLL_PADDING_Y,
8852 IconName::ArrowUp,
8853 visible_row_range,
8854 line_layouts,
8855 newest_selection_head,
8856 scrolled_content_origin,
8857 window,
8858 cx,
8859 );
8860 } else if target_display_point.row() >= visible_row_range.end {
8861 return self.render_edit_prediction_scroll_popover(
8862 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8863 IconName::ArrowDown,
8864 visible_row_range,
8865 line_layouts,
8866 newest_selection_head,
8867 scrolled_content_origin,
8868 window,
8869 cx,
8870 );
8871 }
8872
8873 const POLE_WIDTH: Pixels = px(2.);
8874
8875 let line_layout =
8876 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8877 let target_column = target_display_point.column() as usize;
8878
8879 let target_x = line_layout.x_for_index(target_column);
8880 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8881 - scroll_pixel_position.y;
8882
8883 let flag_on_right = target_x < text_bounds.size.width / 2.;
8884
8885 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8886 border_color.l += 0.001;
8887
8888 let mut element = v_flex()
8889 .items_end()
8890 .when(flag_on_right, |el| el.items_start())
8891 .child(if flag_on_right {
8892 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8893 .rounded_bl(px(0.))
8894 .rounded_tl(px(0.))
8895 .border_l_2()
8896 .border_color(border_color)
8897 } else {
8898 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8899 .rounded_br(px(0.))
8900 .rounded_tr(px(0.))
8901 .border_r_2()
8902 .border_color(border_color)
8903 })
8904 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8905 .into_any();
8906
8907 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8908
8909 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8910 - point(
8911 if flag_on_right {
8912 POLE_WIDTH
8913 } else {
8914 size.width - POLE_WIDTH
8915 },
8916 size.height - line_height,
8917 );
8918
8919 origin.x = origin.x.max(content_origin.x);
8920
8921 element.prepaint_at(origin, window, cx);
8922
8923 Some((element, origin))
8924 }
8925
8926 fn render_edit_prediction_scroll_popover(
8927 &mut self,
8928 to_y: impl Fn(Size<Pixels>) -> Pixels,
8929 scroll_icon: IconName,
8930 visible_row_range: Range<DisplayRow>,
8931 line_layouts: &[LineWithInvisibles],
8932 newest_selection_head: Option<DisplayPoint>,
8933 scrolled_content_origin: gpui::Point<Pixels>,
8934 window: &mut Window,
8935 cx: &mut App,
8936 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8937 let mut element = self
8938 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8939 .into_any();
8940
8941 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8942
8943 let cursor = newest_selection_head?;
8944 let cursor_row_layout =
8945 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8946 let cursor_column = cursor.column() as usize;
8947
8948 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8949
8950 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8951
8952 element.prepaint_at(origin, window, cx);
8953 Some((element, origin))
8954 }
8955
8956 fn render_edit_prediction_eager_jump_popover(
8957 &mut self,
8958 text_bounds: &Bounds<Pixels>,
8959 content_origin: gpui::Point<Pixels>,
8960 editor_snapshot: &EditorSnapshot,
8961 visible_row_range: Range<DisplayRow>,
8962 scroll_top: ScrollOffset,
8963 scroll_bottom: ScrollOffset,
8964 line_height: Pixels,
8965 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8966 target_display_point: DisplayPoint,
8967 editor_width: Pixels,
8968 window: &mut Window,
8969 cx: &mut App,
8970 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8971 if target_display_point.row().as_f64() < scroll_top {
8972 let mut element = self
8973 .render_edit_prediction_line_popover(
8974 "Jump to Edit",
8975 Some(IconName::ArrowUp),
8976 window,
8977 cx,
8978 )
8979 .into_any();
8980
8981 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8982 let offset = point(
8983 (text_bounds.size.width - size.width) / 2.,
8984 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8985 );
8986
8987 let origin = text_bounds.origin + offset;
8988 element.prepaint_at(origin, window, cx);
8989 Some((element, origin))
8990 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8991 let mut element = self
8992 .render_edit_prediction_line_popover(
8993 "Jump to Edit",
8994 Some(IconName::ArrowDown),
8995 window,
8996 cx,
8997 )
8998 .into_any();
8999
9000 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9001 let offset = point(
9002 (text_bounds.size.width - size.width) / 2.,
9003 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9004 );
9005
9006 let origin = text_bounds.origin + offset;
9007 element.prepaint_at(origin, window, cx);
9008 Some((element, origin))
9009 } else {
9010 self.render_edit_prediction_end_of_line_popover(
9011 "Jump to Edit",
9012 editor_snapshot,
9013 visible_row_range,
9014 target_display_point,
9015 line_height,
9016 scroll_pixel_position,
9017 content_origin,
9018 editor_width,
9019 window,
9020 cx,
9021 )
9022 }
9023 }
9024
9025 fn render_edit_prediction_end_of_line_popover(
9026 self: &mut Editor,
9027 label: &'static str,
9028 editor_snapshot: &EditorSnapshot,
9029 visible_row_range: Range<DisplayRow>,
9030 target_display_point: DisplayPoint,
9031 line_height: Pixels,
9032 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9033 content_origin: gpui::Point<Pixels>,
9034 editor_width: Pixels,
9035 window: &mut Window,
9036 cx: &mut App,
9037 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9038 let target_line_end = DisplayPoint::new(
9039 target_display_point.row(),
9040 editor_snapshot.line_len(target_display_point.row()),
9041 );
9042
9043 let mut element = self
9044 .render_edit_prediction_line_popover(label, None, window, cx)
9045 .into_any();
9046
9047 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9048
9049 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9050
9051 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9052 let mut origin = start_point
9053 + line_origin
9054 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9055 origin.x = origin.x.max(content_origin.x);
9056
9057 let max_x = content_origin.x + editor_width - size.width;
9058
9059 if origin.x > max_x {
9060 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9061
9062 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9063 origin.y += offset;
9064 IconName::ArrowUp
9065 } else {
9066 origin.y -= offset;
9067 IconName::ArrowDown
9068 };
9069
9070 element = self
9071 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9072 .into_any();
9073
9074 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9075
9076 origin.x = content_origin.x + editor_width - size.width - px(2.);
9077 }
9078
9079 element.prepaint_at(origin, window, cx);
9080 Some((element, origin))
9081 }
9082
9083 fn render_edit_prediction_diff_popover(
9084 self: &Editor,
9085 text_bounds: &Bounds<Pixels>,
9086 content_origin: gpui::Point<Pixels>,
9087 right_margin: Pixels,
9088 editor_snapshot: &EditorSnapshot,
9089 visible_row_range: Range<DisplayRow>,
9090 line_layouts: &[LineWithInvisibles],
9091 line_height: Pixels,
9092 scroll_position: gpui::Point<ScrollOffset>,
9093 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9094 newest_selection_head: Option<DisplayPoint>,
9095 editor_width: Pixels,
9096 style: &EditorStyle,
9097 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9098 edit_preview: &Option<language::EditPreview>,
9099 snapshot: &language::BufferSnapshot,
9100 window: &mut Window,
9101 cx: &mut App,
9102 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9103 let edit_start = edits
9104 .first()
9105 .unwrap()
9106 .0
9107 .start
9108 .to_display_point(editor_snapshot);
9109 let edit_end = edits
9110 .last()
9111 .unwrap()
9112 .0
9113 .end
9114 .to_display_point(editor_snapshot);
9115
9116 let is_visible = visible_row_range.contains(&edit_start.row())
9117 || visible_row_range.contains(&edit_end.row());
9118 if !is_visible {
9119 return None;
9120 }
9121
9122 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9123 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9124 } else {
9125 // Fallback for providers without edit_preview
9126 crate::edit_prediction_fallback_text(edits, cx)
9127 };
9128
9129 let styled_text = highlighted_edits.to_styled_text(&style.text);
9130 let line_count = highlighted_edits.text.lines().count();
9131
9132 const BORDER_WIDTH: Pixels = px(1.);
9133
9134 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9135 let has_keybind = keybind.is_some();
9136
9137 let mut element = h_flex()
9138 .items_start()
9139 .child(
9140 h_flex()
9141 .bg(cx.theme().colors().editor_background)
9142 .border(BORDER_WIDTH)
9143 .shadow_xs()
9144 .border_color(cx.theme().colors().border)
9145 .rounded_l_lg()
9146 .when(line_count > 1, |el| el.rounded_br_lg())
9147 .pr_1()
9148 .child(styled_text),
9149 )
9150 .child(
9151 h_flex()
9152 .h(line_height + BORDER_WIDTH * 2.)
9153 .px_1p5()
9154 .gap_1()
9155 // Workaround: For some reason, there's a gap if we don't do this
9156 .ml(-BORDER_WIDTH)
9157 .shadow(vec![gpui::BoxShadow {
9158 color: gpui::black().opacity(0.05),
9159 offset: point(px(1.), px(1.)),
9160 blur_radius: px(2.),
9161 spread_radius: px(0.),
9162 }])
9163 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9164 .border(BORDER_WIDTH)
9165 .border_color(cx.theme().colors().border)
9166 .rounded_r_lg()
9167 .id("edit_prediction_diff_popover_keybind")
9168 .when(!has_keybind, |el| {
9169 let status_colors = cx.theme().status();
9170
9171 el.bg(status_colors.error_background)
9172 .border_color(status_colors.error.opacity(0.6))
9173 .child(Icon::new(IconName::Info).color(Color::Error))
9174 .cursor_default()
9175 .hoverable_tooltip(move |_window, cx| {
9176 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9177 })
9178 })
9179 .children(keybind),
9180 )
9181 .into_any();
9182
9183 let longest_row =
9184 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9185 let longest_line_width = if visible_row_range.contains(&longest_row) {
9186 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9187 } else {
9188 layout_line(
9189 longest_row,
9190 editor_snapshot,
9191 style,
9192 editor_width,
9193 |_| false,
9194 window,
9195 cx,
9196 )
9197 .width
9198 };
9199
9200 let viewport_bounds =
9201 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9202 right: -right_margin,
9203 ..Default::default()
9204 });
9205
9206 let x_after_longest = Pixels::from(
9207 ScrollPixelOffset::from(
9208 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9209 ) - scroll_pixel_position.x,
9210 );
9211
9212 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9213
9214 // Fully visible if it can be displayed within the window (allow overlapping other
9215 // panes). However, this is only allowed if the popover starts within text_bounds.
9216 let can_position_to_the_right = x_after_longest < text_bounds.right()
9217 && x_after_longest + element_bounds.width < viewport_bounds.right();
9218
9219 let mut origin = if can_position_to_the_right {
9220 point(
9221 x_after_longest,
9222 text_bounds.origin.y
9223 + Pixels::from(
9224 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9225 - scroll_pixel_position.y,
9226 ),
9227 )
9228 } else {
9229 let cursor_row = newest_selection_head.map(|head| head.row());
9230 let above_edit = edit_start
9231 .row()
9232 .0
9233 .checked_sub(line_count as u32)
9234 .map(DisplayRow);
9235 let below_edit = Some(edit_end.row() + 1);
9236 let above_cursor =
9237 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9238 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9239
9240 // Place the edit popover adjacent to the edit if there is a location
9241 // available that is onscreen and does not obscure the cursor. Otherwise,
9242 // place it adjacent to the cursor.
9243 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9244 .into_iter()
9245 .flatten()
9246 .find(|&start_row| {
9247 let end_row = start_row + line_count as u32;
9248 visible_row_range.contains(&start_row)
9249 && visible_row_range.contains(&end_row)
9250 && cursor_row
9251 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9252 })?;
9253
9254 content_origin
9255 + point(
9256 Pixels::from(-scroll_pixel_position.x),
9257 Pixels::from(
9258 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9259 ),
9260 )
9261 };
9262
9263 origin.x -= BORDER_WIDTH;
9264
9265 window.defer_draw(element, origin, 1);
9266
9267 // Do not return an element, since it will already be drawn due to defer_draw.
9268 None
9269 }
9270
9271 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9272 px(30.)
9273 }
9274
9275 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9276 if self.read_only(cx) {
9277 cx.theme().players().read_only()
9278 } else {
9279 self.style.as_ref().unwrap().local_player
9280 }
9281 }
9282
9283 fn render_edit_prediction_accept_keybind(
9284 &self,
9285 window: &mut Window,
9286 cx: &mut App,
9287 ) -> Option<AnyElement> {
9288 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9289 let accept_keystroke = accept_binding.keystroke()?;
9290
9291 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9292
9293 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9294 Color::Accent
9295 } else {
9296 Color::Muted
9297 };
9298
9299 h_flex()
9300 .px_0p5()
9301 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9302 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9303 .text_size(TextSize::XSmall.rems(cx))
9304 .child(h_flex().children(ui::render_modifiers(
9305 accept_keystroke.modifiers(),
9306 PlatformStyle::platform(),
9307 Some(modifiers_color),
9308 Some(IconSize::XSmall.rems().into()),
9309 true,
9310 )))
9311 .when(is_platform_style_mac, |parent| {
9312 parent.child(accept_keystroke.key().to_string())
9313 })
9314 .when(!is_platform_style_mac, |parent| {
9315 parent.child(
9316 Key::new(
9317 util::capitalize(accept_keystroke.key()),
9318 Some(Color::Default),
9319 )
9320 .size(Some(IconSize::XSmall.rems().into())),
9321 )
9322 })
9323 .into_any()
9324 .into()
9325 }
9326
9327 fn render_edit_prediction_line_popover(
9328 &self,
9329 label: impl Into<SharedString>,
9330 icon: Option<IconName>,
9331 window: &mut Window,
9332 cx: &mut App,
9333 ) -> Stateful<Div> {
9334 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9335
9336 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9337 let has_keybind = keybind.is_some();
9338
9339 h_flex()
9340 .id("ep-line-popover")
9341 .py_0p5()
9342 .pl_1()
9343 .pr(padding_right)
9344 .gap_1()
9345 .rounded_md()
9346 .border_1()
9347 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9348 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9349 .shadow_xs()
9350 .when(!has_keybind, |el| {
9351 let status_colors = cx.theme().status();
9352
9353 el.bg(status_colors.error_background)
9354 .border_color(status_colors.error.opacity(0.6))
9355 .pl_2()
9356 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9357 .cursor_default()
9358 .hoverable_tooltip(move |_window, cx| {
9359 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9360 })
9361 })
9362 .children(keybind)
9363 .child(
9364 Label::new(label)
9365 .size(LabelSize::Small)
9366 .when(!has_keybind, |el| {
9367 el.color(cx.theme().status().error.into()).strikethrough()
9368 }),
9369 )
9370 .when(!has_keybind, |el| {
9371 el.child(
9372 h_flex().ml_1().child(
9373 Icon::new(IconName::Info)
9374 .size(IconSize::Small)
9375 .color(cx.theme().status().error.into()),
9376 ),
9377 )
9378 })
9379 .when_some(icon, |element, icon| {
9380 element.child(
9381 div()
9382 .mt(px(1.5))
9383 .child(Icon::new(icon).size(IconSize::Small)),
9384 )
9385 })
9386 }
9387
9388 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9389 let accent_color = cx.theme().colors().text_accent;
9390 let editor_bg_color = cx.theme().colors().editor_background;
9391 editor_bg_color.blend(accent_color.opacity(0.1))
9392 }
9393
9394 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9395 let accent_color = cx.theme().colors().text_accent;
9396 let editor_bg_color = cx.theme().colors().editor_background;
9397 editor_bg_color.blend(accent_color.opacity(0.6))
9398 }
9399 fn get_prediction_provider_icon_name(
9400 provider: &Option<RegisteredEditPredictionProvider>,
9401 ) -> IconName {
9402 match provider {
9403 Some(provider) => match provider.provider.name() {
9404 "copilot" => IconName::Copilot,
9405 "supermaven" => IconName::Supermaven,
9406 _ => IconName::ZedPredict,
9407 },
9408 None => IconName::ZedPredict,
9409 }
9410 }
9411
9412 fn render_edit_prediction_cursor_popover(
9413 &self,
9414 min_width: Pixels,
9415 max_width: Pixels,
9416 cursor_point: Point,
9417 style: &EditorStyle,
9418 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9419 _window: &Window,
9420 cx: &mut Context<Editor>,
9421 ) -> Option<AnyElement> {
9422 let provider = self.edit_prediction_provider.as_ref()?;
9423 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9424
9425 let is_refreshing = provider.provider.is_refreshing(cx);
9426
9427 fn pending_completion_container(icon: IconName) -> Div {
9428 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9429 }
9430
9431 let completion = match &self.active_edit_prediction {
9432 Some(prediction) => {
9433 if !self.has_visible_completions_menu() {
9434 const RADIUS: Pixels = px(6.);
9435 const BORDER_WIDTH: Pixels = px(1.);
9436
9437 return Some(
9438 h_flex()
9439 .elevation_2(cx)
9440 .border(BORDER_WIDTH)
9441 .border_color(cx.theme().colors().border)
9442 .when(accept_keystroke.is_none(), |el| {
9443 el.border_color(cx.theme().status().error)
9444 })
9445 .rounded(RADIUS)
9446 .rounded_tl(px(0.))
9447 .overflow_hidden()
9448 .child(div().px_1p5().child(match &prediction.completion {
9449 EditPrediction::MoveWithin { target, snapshot } => {
9450 use text::ToPoint as _;
9451 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9452 {
9453 Icon::new(IconName::ZedPredictDown)
9454 } else {
9455 Icon::new(IconName::ZedPredictUp)
9456 }
9457 }
9458 EditPrediction::MoveOutside { .. } => {
9459 // TODO [zeta2] custom icon for external jump?
9460 Icon::new(provider_icon)
9461 }
9462 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9463 }))
9464 .child(
9465 h_flex()
9466 .gap_1()
9467 .py_1()
9468 .px_2()
9469 .rounded_r(RADIUS - BORDER_WIDTH)
9470 .border_l_1()
9471 .border_color(cx.theme().colors().border)
9472 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9473 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9474 el.child(
9475 Label::new("Hold")
9476 .size(LabelSize::Small)
9477 .when(accept_keystroke.is_none(), |el| {
9478 el.strikethrough()
9479 })
9480 .line_height_style(LineHeightStyle::UiLabel),
9481 )
9482 })
9483 .id("edit_prediction_cursor_popover_keybind")
9484 .when(accept_keystroke.is_none(), |el| {
9485 let status_colors = cx.theme().status();
9486
9487 el.bg(status_colors.error_background)
9488 .border_color(status_colors.error.opacity(0.6))
9489 .child(Icon::new(IconName::Info).color(Color::Error))
9490 .cursor_default()
9491 .hoverable_tooltip(move |_window, cx| {
9492 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9493 .into()
9494 })
9495 })
9496 .when_some(
9497 accept_keystroke.as_ref(),
9498 |el, accept_keystroke| {
9499 el.child(h_flex().children(ui::render_modifiers(
9500 accept_keystroke.modifiers(),
9501 PlatformStyle::platform(),
9502 Some(Color::Default),
9503 Some(IconSize::XSmall.rems().into()),
9504 false,
9505 )))
9506 },
9507 ),
9508 )
9509 .into_any(),
9510 );
9511 }
9512
9513 self.render_edit_prediction_cursor_popover_preview(
9514 prediction,
9515 cursor_point,
9516 style,
9517 cx,
9518 )?
9519 }
9520
9521 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9522 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9523 stale_completion,
9524 cursor_point,
9525 style,
9526 cx,
9527 )?,
9528
9529 None => pending_completion_container(provider_icon)
9530 .child(Label::new("...").size(LabelSize::Small)),
9531 },
9532
9533 None => pending_completion_container(provider_icon)
9534 .child(Label::new("...").size(LabelSize::Small)),
9535 };
9536
9537 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9538 completion
9539 .with_animation(
9540 "loading-completion",
9541 Animation::new(Duration::from_secs(2))
9542 .repeat()
9543 .with_easing(pulsating_between(0.4, 0.8)),
9544 |label, delta| label.opacity(delta),
9545 )
9546 .into_any_element()
9547 } else {
9548 completion.into_any_element()
9549 };
9550
9551 let has_completion = self.active_edit_prediction.is_some();
9552
9553 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9554 Some(
9555 h_flex()
9556 .min_w(min_width)
9557 .max_w(max_width)
9558 .flex_1()
9559 .elevation_2(cx)
9560 .border_color(cx.theme().colors().border)
9561 .child(
9562 div()
9563 .flex_1()
9564 .py_1()
9565 .px_2()
9566 .overflow_hidden()
9567 .child(completion),
9568 )
9569 .when_some(accept_keystroke, |el, accept_keystroke| {
9570 if !accept_keystroke.modifiers().modified() {
9571 return el;
9572 }
9573
9574 el.child(
9575 h_flex()
9576 .h_full()
9577 .border_l_1()
9578 .rounded_r_lg()
9579 .border_color(cx.theme().colors().border)
9580 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9581 .gap_1()
9582 .py_1()
9583 .px_2()
9584 .child(
9585 h_flex()
9586 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9587 .when(is_platform_style_mac, |parent| parent.gap_1())
9588 .child(h_flex().children(ui::render_modifiers(
9589 accept_keystroke.modifiers(),
9590 PlatformStyle::platform(),
9591 Some(if !has_completion {
9592 Color::Muted
9593 } else {
9594 Color::Default
9595 }),
9596 None,
9597 false,
9598 ))),
9599 )
9600 .child(Label::new("Preview").into_any_element())
9601 .opacity(if has_completion { 1.0 } else { 0.4 }),
9602 )
9603 })
9604 .into_any(),
9605 )
9606 }
9607
9608 fn render_edit_prediction_cursor_popover_preview(
9609 &self,
9610 completion: &EditPredictionState,
9611 cursor_point: Point,
9612 style: &EditorStyle,
9613 cx: &mut Context<Editor>,
9614 ) -> Option<Div> {
9615 use text::ToPoint as _;
9616
9617 fn render_relative_row_jump(
9618 prefix: impl Into<String>,
9619 current_row: u32,
9620 target_row: u32,
9621 ) -> Div {
9622 let (row_diff, arrow) = if target_row < current_row {
9623 (current_row - target_row, IconName::ArrowUp)
9624 } else {
9625 (target_row - current_row, IconName::ArrowDown)
9626 };
9627
9628 h_flex()
9629 .child(
9630 Label::new(format!("{}{}", prefix.into(), row_diff))
9631 .color(Color::Muted)
9632 .size(LabelSize::Small),
9633 )
9634 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9635 }
9636
9637 let supports_jump = self
9638 .edit_prediction_provider
9639 .as_ref()
9640 .map(|provider| provider.provider.supports_jump_to_edit())
9641 .unwrap_or(true);
9642
9643 match &completion.completion {
9644 EditPrediction::MoveWithin {
9645 target, snapshot, ..
9646 } => {
9647 if !supports_jump {
9648 return None;
9649 }
9650
9651 Some(
9652 h_flex()
9653 .px_2()
9654 .gap_2()
9655 .flex_1()
9656 .child(
9657 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9658 Icon::new(IconName::ZedPredictDown)
9659 } else {
9660 Icon::new(IconName::ZedPredictUp)
9661 },
9662 )
9663 .child(Label::new("Jump to Edit")),
9664 )
9665 }
9666 EditPrediction::MoveOutside { snapshot, .. } => {
9667 let file_name = snapshot
9668 .file()
9669 .map(|file| file.file_name(cx))
9670 .unwrap_or("untitled");
9671 Some(
9672 h_flex()
9673 .px_2()
9674 .gap_2()
9675 .flex_1()
9676 .child(Icon::new(IconName::ZedPredict))
9677 .child(Label::new(format!("Jump to {file_name}"))),
9678 )
9679 }
9680 EditPrediction::Edit {
9681 edits,
9682 edit_preview,
9683 snapshot,
9684 display_mode: _,
9685 } => {
9686 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9687
9688 let (highlighted_edits, has_more_lines) =
9689 if let Some(edit_preview) = edit_preview.as_ref() {
9690 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9691 .first_line_preview()
9692 } else {
9693 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9694 };
9695
9696 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9697 .with_default_highlights(&style.text, highlighted_edits.highlights);
9698
9699 let preview = h_flex()
9700 .gap_1()
9701 .min_w_16()
9702 .child(styled_text)
9703 .when(has_more_lines, |parent| parent.child("…"));
9704
9705 let left = if supports_jump && first_edit_row != cursor_point.row {
9706 render_relative_row_jump("", cursor_point.row, first_edit_row)
9707 .into_any_element()
9708 } else {
9709 let icon_name =
9710 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9711 Icon::new(icon_name).into_any_element()
9712 };
9713
9714 Some(
9715 h_flex()
9716 .h_full()
9717 .flex_1()
9718 .gap_2()
9719 .pr_1()
9720 .overflow_x_hidden()
9721 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9722 .child(left)
9723 .child(preview),
9724 )
9725 }
9726 }
9727 }
9728
9729 pub fn render_context_menu(
9730 &self,
9731 style: &EditorStyle,
9732 max_height_in_lines: u32,
9733 window: &mut Window,
9734 cx: &mut Context<Editor>,
9735 ) -> Option<AnyElement> {
9736 let menu = self.context_menu.borrow();
9737 let menu = menu.as_ref()?;
9738 if !menu.visible() {
9739 return None;
9740 };
9741 Some(menu.render(style, max_height_in_lines, window, cx))
9742 }
9743
9744 fn render_context_menu_aside(
9745 &mut self,
9746 max_size: Size<Pixels>,
9747 window: &mut Window,
9748 cx: &mut Context<Editor>,
9749 ) -> Option<AnyElement> {
9750 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9751 if menu.visible() {
9752 menu.render_aside(max_size, window, cx)
9753 } else {
9754 None
9755 }
9756 })
9757 }
9758
9759 fn hide_context_menu(
9760 &mut self,
9761 window: &mut Window,
9762 cx: &mut Context<Self>,
9763 ) -> Option<CodeContextMenu> {
9764 cx.notify();
9765 self.completion_tasks.clear();
9766 let context_menu = self.context_menu.borrow_mut().take();
9767 self.stale_edit_prediction_in_menu.take();
9768 self.update_visible_edit_prediction(window, cx);
9769 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9770 && let Some(completion_provider) = &self.completion_provider
9771 {
9772 completion_provider.selection_changed(None, window, cx);
9773 }
9774 context_menu
9775 }
9776
9777 fn show_snippet_choices(
9778 &mut self,
9779 choices: &Vec<String>,
9780 selection: Range<Anchor>,
9781 cx: &mut Context<Self>,
9782 ) {
9783 let Some((_, buffer, _)) = self
9784 .buffer()
9785 .read(cx)
9786 .excerpt_containing(selection.start, cx)
9787 else {
9788 return;
9789 };
9790 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9791 else {
9792 return;
9793 };
9794 if buffer != end_buffer {
9795 log::error!("expected anchor range to have matching buffer IDs");
9796 return;
9797 }
9798
9799 let id = post_inc(&mut self.next_completion_id);
9800 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9801 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9802 CompletionsMenu::new_snippet_choices(
9803 id,
9804 true,
9805 choices,
9806 selection,
9807 buffer,
9808 snippet_sort_order,
9809 ),
9810 ));
9811 }
9812
9813 pub fn insert_snippet(
9814 &mut self,
9815 insertion_ranges: &[Range<usize>],
9816 snippet: Snippet,
9817 window: &mut Window,
9818 cx: &mut Context<Self>,
9819 ) -> Result<()> {
9820 struct Tabstop<T> {
9821 is_end_tabstop: bool,
9822 ranges: Vec<Range<T>>,
9823 choices: Option<Vec<String>>,
9824 }
9825
9826 let tabstops = self.buffer.update(cx, |buffer, cx| {
9827 let snippet_text: Arc<str> = snippet.text.clone().into();
9828 let edits = insertion_ranges
9829 .iter()
9830 .cloned()
9831 .map(|range| (range, snippet_text.clone()));
9832 let autoindent_mode = AutoindentMode::Block {
9833 original_indent_columns: Vec::new(),
9834 };
9835 buffer.edit(edits, Some(autoindent_mode), cx);
9836
9837 let snapshot = &*buffer.read(cx);
9838 let snippet = &snippet;
9839 snippet
9840 .tabstops
9841 .iter()
9842 .map(|tabstop| {
9843 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9844 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9845 });
9846 let mut tabstop_ranges = tabstop
9847 .ranges
9848 .iter()
9849 .flat_map(|tabstop_range| {
9850 let mut delta = 0_isize;
9851 insertion_ranges.iter().map(move |insertion_range| {
9852 let insertion_start = insertion_range.start as isize + delta;
9853 delta +=
9854 snippet.text.len() as isize - insertion_range.len() as isize;
9855
9856 let start = ((insertion_start + tabstop_range.start) as usize)
9857 .min(snapshot.len());
9858 let end = ((insertion_start + tabstop_range.end) as usize)
9859 .min(snapshot.len());
9860 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9861 })
9862 })
9863 .collect::<Vec<_>>();
9864 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9865
9866 Tabstop {
9867 is_end_tabstop,
9868 ranges: tabstop_ranges,
9869 choices: tabstop.choices.clone(),
9870 }
9871 })
9872 .collect::<Vec<_>>()
9873 });
9874 if let Some(tabstop) = tabstops.first() {
9875 self.change_selections(Default::default(), window, cx, |s| {
9876 // Reverse order so that the first range is the newest created selection.
9877 // Completions will use it and autoscroll will prioritize it.
9878 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9879 });
9880
9881 if let Some(choices) = &tabstop.choices
9882 && let Some(selection) = tabstop.ranges.first()
9883 {
9884 self.show_snippet_choices(choices, selection.clone(), cx)
9885 }
9886
9887 // If we're already at the last tabstop and it's at the end of the snippet,
9888 // we're done, we don't need to keep the state around.
9889 if !tabstop.is_end_tabstop {
9890 let choices = tabstops
9891 .iter()
9892 .map(|tabstop| tabstop.choices.clone())
9893 .collect();
9894
9895 let ranges = tabstops
9896 .into_iter()
9897 .map(|tabstop| tabstop.ranges)
9898 .collect::<Vec<_>>();
9899
9900 self.snippet_stack.push(SnippetState {
9901 active_index: 0,
9902 ranges,
9903 choices,
9904 });
9905 }
9906
9907 // Check whether the just-entered snippet ends with an auto-closable bracket.
9908 if self.autoclose_regions.is_empty() {
9909 let snapshot = self.buffer.read(cx).snapshot(cx);
9910 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9911 let selection_head = selection.head();
9912 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9913 continue;
9914 };
9915
9916 let mut bracket_pair = None;
9917 let max_lookup_length = scope
9918 .brackets()
9919 .map(|(pair, _)| {
9920 pair.start
9921 .as_str()
9922 .chars()
9923 .count()
9924 .max(pair.end.as_str().chars().count())
9925 })
9926 .max();
9927 if let Some(max_lookup_length) = max_lookup_length {
9928 let next_text = snapshot
9929 .chars_at(selection_head)
9930 .take(max_lookup_length)
9931 .collect::<String>();
9932 let prev_text = snapshot
9933 .reversed_chars_at(selection_head)
9934 .take(max_lookup_length)
9935 .collect::<String>();
9936
9937 for (pair, enabled) in scope.brackets() {
9938 if enabled
9939 && pair.close
9940 && prev_text.starts_with(pair.start.as_str())
9941 && next_text.starts_with(pair.end.as_str())
9942 {
9943 bracket_pair = Some(pair.clone());
9944 break;
9945 }
9946 }
9947 }
9948
9949 if let Some(pair) = bracket_pair {
9950 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9951 let autoclose_enabled =
9952 self.use_autoclose && snapshot_settings.use_autoclose;
9953 if autoclose_enabled {
9954 let start = snapshot.anchor_after(selection_head);
9955 let end = snapshot.anchor_after(selection_head);
9956 self.autoclose_regions.push(AutocloseRegion {
9957 selection_id: selection.id,
9958 range: start..end,
9959 pair,
9960 });
9961 }
9962 }
9963 }
9964 }
9965 }
9966 Ok(())
9967 }
9968
9969 pub fn move_to_next_snippet_tabstop(
9970 &mut self,
9971 window: &mut Window,
9972 cx: &mut Context<Self>,
9973 ) -> bool {
9974 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9975 }
9976
9977 pub fn move_to_prev_snippet_tabstop(
9978 &mut self,
9979 window: &mut Window,
9980 cx: &mut Context<Self>,
9981 ) -> bool {
9982 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9983 }
9984
9985 pub fn move_to_snippet_tabstop(
9986 &mut self,
9987 bias: Bias,
9988 window: &mut Window,
9989 cx: &mut Context<Self>,
9990 ) -> bool {
9991 if let Some(mut snippet) = self.snippet_stack.pop() {
9992 match bias {
9993 Bias::Left => {
9994 if snippet.active_index > 0 {
9995 snippet.active_index -= 1;
9996 } else {
9997 self.snippet_stack.push(snippet);
9998 return false;
9999 }
10000 }
10001 Bias::Right => {
10002 if snippet.active_index + 1 < snippet.ranges.len() {
10003 snippet.active_index += 1;
10004 } else {
10005 self.snippet_stack.push(snippet);
10006 return false;
10007 }
10008 }
10009 }
10010 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10011 self.change_selections(Default::default(), window, cx, |s| {
10012 // Reverse order so that the first range is the newest created selection.
10013 // Completions will use it and autoscroll will prioritize it.
10014 s.select_ranges(current_ranges.iter().rev().cloned())
10015 });
10016
10017 if let Some(choices) = &snippet.choices[snippet.active_index]
10018 && let Some(selection) = current_ranges.first()
10019 {
10020 self.show_snippet_choices(choices, selection.clone(), cx);
10021 }
10022
10023 // If snippet state is not at the last tabstop, push it back on the stack
10024 if snippet.active_index + 1 < snippet.ranges.len() {
10025 self.snippet_stack.push(snippet);
10026 }
10027 return true;
10028 }
10029 }
10030
10031 false
10032 }
10033
10034 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10035 self.transact(window, cx, |this, window, cx| {
10036 this.select_all(&SelectAll, window, cx);
10037 this.insert("", window, cx);
10038 });
10039 }
10040
10041 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10042 if self.read_only(cx) {
10043 return;
10044 }
10045 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10046 self.transact(window, cx, |this, window, cx| {
10047 this.select_autoclose_pair(window, cx);
10048
10049 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10050
10051 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10052 if !this.linked_edit_ranges.is_empty() {
10053 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10054 let snapshot = this.buffer.read(cx).snapshot(cx);
10055
10056 for selection in selections.iter() {
10057 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10058 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10059 if selection_start.buffer_id != selection_end.buffer_id {
10060 continue;
10061 }
10062 if let Some(ranges) =
10063 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10064 {
10065 for (buffer, entries) in ranges {
10066 linked_ranges.entry(buffer).or_default().extend(entries);
10067 }
10068 }
10069 }
10070 }
10071
10072 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10073 for selection in &mut selections {
10074 if selection.is_empty() {
10075 let old_head = selection.head();
10076 let mut new_head =
10077 movement::left(&display_map, old_head.to_display_point(&display_map))
10078 .to_point(&display_map);
10079 if let Some((buffer, line_buffer_range)) = display_map
10080 .buffer_snapshot()
10081 .buffer_line_for_row(MultiBufferRow(old_head.row))
10082 {
10083 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10084 let indent_len = match indent_size.kind {
10085 IndentKind::Space => {
10086 buffer.settings_at(line_buffer_range.start, cx).tab_size
10087 }
10088 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10089 };
10090 if old_head.column <= indent_size.len && old_head.column > 0 {
10091 let indent_len = indent_len.get();
10092 new_head = cmp::min(
10093 new_head,
10094 MultiBufferPoint::new(
10095 old_head.row,
10096 ((old_head.column - 1) / indent_len) * indent_len,
10097 ),
10098 );
10099 }
10100 }
10101
10102 selection.set_head(new_head, SelectionGoal::None);
10103 }
10104 }
10105
10106 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10107 this.insert("", window, cx);
10108 let empty_str: Arc<str> = Arc::from("");
10109 for (buffer, edits) in linked_ranges {
10110 let snapshot = buffer.read(cx).snapshot();
10111 use text::ToPoint as TP;
10112
10113 let edits = edits
10114 .into_iter()
10115 .map(|range| {
10116 let end_point = TP::to_point(&range.end, &snapshot);
10117 let mut start_point = TP::to_point(&range.start, &snapshot);
10118
10119 if end_point == start_point {
10120 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10121 .saturating_sub(1);
10122 start_point =
10123 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10124 };
10125
10126 (start_point..end_point, empty_str.clone())
10127 })
10128 .sorted_by_key(|(range, _)| range.start)
10129 .collect::<Vec<_>>();
10130 buffer.update(cx, |this, cx| {
10131 this.edit(edits, None, cx);
10132 })
10133 }
10134 this.refresh_edit_prediction(true, false, window, cx);
10135 refresh_linked_ranges(this, window, cx);
10136 });
10137 }
10138
10139 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10140 if self.read_only(cx) {
10141 return;
10142 }
10143 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10144 self.transact(window, cx, |this, window, cx| {
10145 this.change_selections(Default::default(), window, cx, |s| {
10146 s.move_with(|map, selection| {
10147 if selection.is_empty() {
10148 let cursor = movement::right(map, selection.head());
10149 selection.end = cursor;
10150 selection.reversed = true;
10151 selection.goal = SelectionGoal::None;
10152 }
10153 })
10154 });
10155 this.insert("", window, cx);
10156 this.refresh_edit_prediction(true, false, window, cx);
10157 });
10158 }
10159
10160 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10161 if self.mode.is_single_line() {
10162 cx.propagate();
10163 return;
10164 }
10165
10166 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10167 if self.move_to_prev_snippet_tabstop(window, cx) {
10168 return;
10169 }
10170 self.outdent(&Outdent, window, cx);
10171 }
10172
10173 pub fn next_snippet_tabstop(
10174 &mut self,
10175 _: &NextSnippetTabstop,
10176 window: &mut Window,
10177 cx: &mut Context<Self>,
10178 ) {
10179 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10180 cx.propagate();
10181 return;
10182 }
10183
10184 if self.move_to_next_snippet_tabstop(window, cx) {
10185 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10186 return;
10187 }
10188 cx.propagate();
10189 }
10190
10191 pub fn previous_snippet_tabstop(
10192 &mut self,
10193 _: &PreviousSnippetTabstop,
10194 window: &mut Window,
10195 cx: &mut Context<Self>,
10196 ) {
10197 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10198 cx.propagate();
10199 return;
10200 }
10201
10202 if self.move_to_prev_snippet_tabstop(window, cx) {
10203 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10204 return;
10205 }
10206 cx.propagate();
10207 }
10208
10209 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10210 if self.mode.is_single_line() {
10211 cx.propagate();
10212 return;
10213 }
10214
10215 if self.move_to_next_snippet_tabstop(window, cx) {
10216 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10217 return;
10218 }
10219 if self.read_only(cx) {
10220 return;
10221 }
10222 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10223 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10224 let buffer = self.buffer.read(cx);
10225 let snapshot = buffer.snapshot(cx);
10226 let rows_iter = selections.iter().map(|s| s.head().row);
10227 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10228
10229 let has_some_cursor_in_whitespace = selections
10230 .iter()
10231 .filter(|selection| selection.is_empty())
10232 .any(|selection| {
10233 let cursor = selection.head();
10234 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10235 cursor.column < current_indent.len
10236 });
10237
10238 let mut edits = Vec::new();
10239 let mut prev_edited_row = 0;
10240 let mut row_delta = 0;
10241 for selection in &mut selections {
10242 if selection.start.row != prev_edited_row {
10243 row_delta = 0;
10244 }
10245 prev_edited_row = selection.end.row;
10246
10247 // If the selection is non-empty, then increase the indentation of the selected lines.
10248 if !selection.is_empty() {
10249 row_delta =
10250 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10251 continue;
10252 }
10253
10254 let cursor = selection.head();
10255 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10256 if let Some(suggested_indent) =
10257 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10258 {
10259 // Don't do anything if already at suggested indent
10260 // and there is any other cursor which is not
10261 if has_some_cursor_in_whitespace
10262 && cursor.column == current_indent.len
10263 && current_indent.len == suggested_indent.len
10264 {
10265 continue;
10266 }
10267
10268 // Adjust line and move cursor to suggested indent
10269 // if cursor is not at suggested indent
10270 if cursor.column < suggested_indent.len
10271 && cursor.column <= current_indent.len
10272 && current_indent.len <= suggested_indent.len
10273 {
10274 selection.start = Point::new(cursor.row, suggested_indent.len);
10275 selection.end = selection.start;
10276 if row_delta == 0 {
10277 edits.extend(Buffer::edit_for_indent_size_adjustment(
10278 cursor.row,
10279 current_indent,
10280 suggested_indent,
10281 ));
10282 row_delta = suggested_indent.len - current_indent.len;
10283 }
10284 continue;
10285 }
10286
10287 // If current indent is more than suggested indent
10288 // only move cursor to current indent and skip indent
10289 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10290 selection.start = Point::new(cursor.row, current_indent.len);
10291 selection.end = selection.start;
10292 continue;
10293 }
10294 }
10295
10296 // Otherwise, insert a hard or soft tab.
10297 let settings = buffer.language_settings_at(cursor, cx);
10298 let tab_size = if settings.hard_tabs {
10299 IndentSize::tab()
10300 } else {
10301 let tab_size = settings.tab_size.get();
10302 let indent_remainder = snapshot
10303 .text_for_range(Point::new(cursor.row, 0)..cursor)
10304 .flat_map(str::chars)
10305 .fold(row_delta % tab_size, |counter: u32, c| {
10306 if c == '\t' {
10307 0
10308 } else {
10309 (counter + 1) % tab_size
10310 }
10311 });
10312
10313 let chars_to_next_tab_stop = tab_size - indent_remainder;
10314 IndentSize::spaces(chars_to_next_tab_stop)
10315 };
10316 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10317 selection.end = selection.start;
10318 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10319 row_delta += tab_size.len;
10320 }
10321
10322 self.transact(window, cx, |this, window, cx| {
10323 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10324 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10325 this.refresh_edit_prediction(true, false, window, cx);
10326 });
10327 }
10328
10329 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10330 if self.read_only(cx) {
10331 return;
10332 }
10333 if self.mode.is_single_line() {
10334 cx.propagate();
10335 return;
10336 }
10337
10338 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10339 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10340 let mut prev_edited_row = 0;
10341 let mut row_delta = 0;
10342 let mut edits = Vec::new();
10343 let buffer = self.buffer.read(cx);
10344 let snapshot = buffer.snapshot(cx);
10345 for selection in &mut selections {
10346 if selection.start.row != prev_edited_row {
10347 row_delta = 0;
10348 }
10349 prev_edited_row = selection.end.row;
10350
10351 row_delta =
10352 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10353 }
10354
10355 self.transact(window, cx, |this, window, cx| {
10356 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10357 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10358 });
10359 }
10360
10361 fn indent_selection(
10362 buffer: &MultiBuffer,
10363 snapshot: &MultiBufferSnapshot,
10364 selection: &mut Selection<Point>,
10365 edits: &mut Vec<(Range<Point>, String)>,
10366 delta_for_start_row: u32,
10367 cx: &App,
10368 ) -> u32 {
10369 let settings = buffer.language_settings_at(selection.start, cx);
10370 let tab_size = settings.tab_size.get();
10371 let indent_kind = if settings.hard_tabs {
10372 IndentKind::Tab
10373 } else {
10374 IndentKind::Space
10375 };
10376 let mut start_row = selection.start.row;
10377 let mut end_row = selection.end.row + 1;
10378
10379 // If a selection ends at the beginning of a line, don't indent
10380 // that last line.
10381 if selection.end.column == 0 && selection.end.row > selection.start.row {
10382 end_row -= 1;
10383 }
10384
10385 // Avoid re-indenting a row that has already been indented by a
10386 // previous selection, but still update this selection's column
10387 // to reflect that indentation.
10388 if delta_for_start_row > 0 {
10389 start_row += 1;
10390 selection.start.column += delta_for_start_row;
10391 if selection.end.row == selection.start.row {
10392 selection.end.column += delta_for_start_row;
10393 }
10394 }
10395
10396 let mut delta_for_end_row = 0;
10397 let has_multiple_rows = start_row + 1 != end_row;
10398 for row in start_row..end_row {
10399 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10400 let indent_delta = match (current_indent.kind, indent_kind) {
10401 (IndentKind::Space, IndentKind::Space) => {
10402 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10403 IndentSize::spaces(columns_to_next_tab_stop)
10404 }
10405 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10406 (_, IndentKind::Tab) => IndentSize::tab(),
10407 };
10408
10409 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10410 0
10411 } else {
10412 selection.start.column
10413 };
10414 let row_start = Point::new(row, start);
10415 edits.push((
10416 row_start..row_start,
10417 indent_delta.chars().collect::<String>(),
10418 ));
10419
10420 // Update this selection's endpoints to reflect the indentation.
10421 if row == selection.start.row {
10422 selection.start.column += indent_delta.len;
10423 }
10424 if row == selection.end.row {
10425 selection.end.column += indent_delta.len;
10426 delta_for_end_row = indent_delta.len;
10427 }
10428 }
10429
10430 if selection.start.row == selection.end.row {
10431 delta_for_start_row + delta_for_end_row
10432 } else {
10433 delta_for_end_row
10434 }
10435 }
10436
10437 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10438 if self.read_only(cx) {
10439 return;
10440 }
10441 if self.mode.is_single_line() {
10442 cx.propagate();
10443 return;
10444 }
10445
10446 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10447 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10448 let selections = self.selections.all::<Point>(&display_map);
10449 let mut deletion_ranges = Vec::new();
10450 let mut last_outdent = None;
10451 {
10452 let buffer = self.buffer.read(cx);
10453 let snapshot = buffer.snapshot(cx);
10454 for selection in &selections {
10455 let settings = buffer.language_settings_at(selection.start, cx);
10456 let tab_size = settings.tab_size.get();
10457 let mut rows = selection.spanned_rows(false, &display_map);
10458
10459 // Avoid re-outdenting a row that has already been outdented by a
10460 // previous selection.
10461 if let Some(last_row) = last_outdent
10462 && last_row == rows.start
10463 {
10464 rows.start = rows.start.next_row();
10465 }
10466 let has_multiple_rows = rows.len() > 1;
10467 for row in rows.iter_rows() {
10468 let indent_size = snapshot.indent_size_for_line(row);
10469 if indent_size.len > 0 {
10470 let deletion_len = match indent_size.kind {
10471 IndentKind::Space => {
10472 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10473 if columns_to_prev_tab_stop == 0 {
10474 tab_size
10475 } else {
10476 columns_to_prev_tab_stop
10477 }
10478 }
10479 IndentKind::Tab => 1,
10480 };
10481 let start = if has_multiple_rows
10482 || deletion_len > selection.start.column
10483 || indent_size.len < selection.start.column
10484 {
10485 0
10486 } else {
10487 selection.start.column - deletion_len
10488 };
10489 deletion_ranges.push(
10490 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10491 );
10492 last_outdent = Some(row);
10493 }
10494 }
10495 }
10496 }
10497
10498 self.transact(window, cx, |this, window, cx| {
10499 this.buffer.update(cx, |buffer, cx| {
10500 let empty_str: Arc<str> = Arc::default();
10501 buffer.edit(
10502 deletion_ranges
10503 .into_iter()
10504 .map(|range| (range, empty_str.clone())),
10505 None,
10506 cx,
10507 );
10508 });
10509 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10510 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10511 });
10512 }
10513
10514 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10515 if self.read_only(cx) {
10516 return;
10517 }
10518 if self.mode.is_single_line() {
10519 cx.propagate();
10520 return;
10521 }
10522
10523 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10524 let selections = self
10525 .selections
10526 .all::<usize>(&self.display_snapshot(cx))
10527 .into_iter()
10528 .map(|s| s.range());
10529
10530 self.transact(window, cx, |this, window, cx| {
10531 this.buffer.update(cx, |buffer, cx| {
10532 buffer.autoindent_ranges(selections, cx);
10533 });
10534 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10535 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10536 });
10537 }
10538
10539 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10540 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10541 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10542 let selections = self.selections.all::<Point>(&display_map);
10543
10544 let mut new_cursors = Vec::new();
10545 let mut edit_ranges = Vec::new();
10546 let mut selections = selections.iter().peekable();
10547 while let Some(selection) = selections.next() {
10548 let mut rows = selection.spanned_rows(false, &display_map);
10549
10550 // Accumulate contiguous regions of rows that we want to delete.
10551 while let Some(next_selection) = selections.peek() {
10552 let next_rows = next_selection.spanned_rows(false, &display_map);
10553 if next_rows.start <= rows.end {
10554 rows.end = next_rows.end;
10555 selections.next().unwrap();
10556 } else {
10557 break;
10558 }
10559 }
10560
10561 let buffer = display_map.buffer_snapshot();
10562 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10563 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10564 // If there's a line after the range, delete the \n from the end of the row range
10565 (
10566 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10567 rows.end,
10568 )
10569 } else {
10570 // If there isn't a line after the range, delete the \n from the line before the
10571 // start of the row range
10572 edit_start = edit_start.saturating_sub(1);
10573 (buffer.len(), rows.start.previous_row())
10574 };
10575
10576 let text_layout_details = self.text_layout_details(window);
10577 let x = display_map.x_for_display_point(
10578 selection.head().to_display_point(&display_map),
10579 &text_layout_details,
10580 );
10581 let row = Point::new(target_row.0, 0)
10582 .to_display_point(&display_map)
10583 .row();
10584 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10585
10586 new_cursors.push((
10587 selection.id,
10588 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10589 SelectionGoal::None,
10590 ));
10591 edit_ranges.push(edit_start..edit_end);
10592 }
10593
10594 self.transact(window, cx, |this, window, cx| {
10595 let buffer = this.buffer.update(cx, |buffer, cx| {
10596 let empty_str: Arc<str> = Arc::default();
10597 buffer.edit(
10598 edit_ranges
10599 .into_iter()
10600 .map(|range| (range, empty_str.clone())),
10601 None,
10602 cx,
10603 );
10604 buffer.snapshot(cx)
10605 });
10606 let new_selections = new_cursors
10607 .into_iter()
10608 .map(|(id, cursor, goal)| {
10609 let cursor = cursor.to_point(&buffer);
10610 Selection {
10611 id,
10612 start: cursor,
10613 end: cursor,
10614 reversed: false,
10615 goal,
10616 }
10617 })
10618 .collect();
10619
10620 this.change_selections(Default::default(), window, cx, |s| {
10621 s.select(new_selections);
10622 });
10623 });
10624 }
10625
10626 pub fn join_lines_impl(
10627 &mut self,
10628 insert_whitespace: bool,
10629 window: &mut Window,
10630 cx: &mut Context<Self>,
10631 ) {
10632 if self.read_only(cx) {
10633 return;
10634 }
10635 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10636 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10637 let start = MultiBufferRow(selection.start.row);
10638 // Treat single line selections as if they include the next line. Otherwise this action
10639 // would do nothing for single line selections individual cursors.
10640 let end = if selection.start.row == selection.end.row {
10641 MultiBufferRow(selection.start.row + 1)
10642 } else {
10643 MultiBufferRow(selection.end.row)
10644 };
10645
10646 if let Some(last_row_range) = row_ranges.last_mut()
10647 && start <= last_row_range.end
10648 {
10649 last_row_range.end = end;
10650 continue;
10651 }
10652 row_ranges.push(start..end);
10653 }
10654
10655 let snapshot = self.buffer.read(cx).snapshot(cx);
10656 let mut cursor_positions = Vec::new();
10657 for row_range in &row_ranges {
10658 let anchor = snapshot.anchor_before(Point::new(
10659 row_range.end.previous_row().0,
10660 snapshot.line_len(row_range.end.previous_row()),
10661 ));
10662 cursor_positions.push(anchor..anchor);
10663 }
10664
10665 self.transact(window, cx, |this, window, cx| {
10666 for row_range in row_ranges.into_iter().rev() {
10667 for row in row_range.iter_rows().rev() {
10668 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10669 let next_line_row = row.next_row();
10670 let indent = snapshot.indent_size_for_line(next_line_row);
10671 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10672
10673 let replace =
10674 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10675 " "
10676 } else {
10677 ""
10678 };
10679
10680 this.buffer.update(cx, |buffer, cx| {
10681 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10682 });
10683 }
10684 }
10685
10686 this.change_selections(Default::default(), window, cx, |s| {
10687 s.select_anchor_ranges(cursor_positions)
10688 });
10689 });
10690 }
10691
10692 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10693 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10694 self.join_lines_impl(true, window, cx);
10695 }
10696
10697 pub fn sort_lines_case_sensitive(
10698 &mut self,
10699 _: &SortLinesCaseSensitive,
10700 window: &mut Window,
10701 cx: &mut Context<Self>,
10702 ) {
10703 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10704 }
10705
10706 pub fn sort_lines_by_length(
10707 &mut self,
10708 _: &SortLinesByLength,
10709 window: &mut Window,
10710 cx: &mut Context<Self>,
10711 ) {
10712 self.manipulate_immutable_lines(window, cx, |lines| {
10713 lines.sort_by_key(|&line| line.chars().count())
10714 })
10715 }
10716
10717 pub fn sort_lines_case_insensitive(
10718 &mut self,
10719 _: &SortLinesCaseInsensitive,
10720 window: &mut Window,
10721 cx: &mut Context<Self>,
10722 ) {
10723 self.manipulate_immutable_lines(window, cx, |lines| {
10724 lines.sort_by_key(|line| line.to_lowercase())
10725 })
10726 }
10727
10728 pub fn unique_lines_case_insensitive(
10729 &mut self,
10730 _: &UniqueLinesCaseInsensitive,
10731 window: &mut Window,
10732 cx: &mut Context<Self>,
10733 ) {
10734 self.manipulate_immutable_lines(window, cx, |lines| {
10735 let mut seen = HashSet::default();
10736 lines.retain(|line| seen.insert(line.to_lowercase()));
10737 })
10738 }
10739
10740 pub fn unique_lines_case_sensitive(
10741 &mut self,
10742 _: &UniqueLinesCaseSensitive,
10743 window: &mut Window,
10744 cx: &mut Context<Self>,
10745 ) {
10746 self.manipulate_immutable_lines(window, cx, |lines| {
10747 let mut seen = HashSet::default();
10748 lines.retain(|line| seen.insert(*line));
10749 })
10750 }
10751
10752 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10753 let snapshot = self.buffer.read(cx).snapshot(cx);
10754 for selection in self.selections.disjoint_anchors_arc().iter() {
10755 if snapshot
10756 .language_at(selection.start)
10757 .and_then(|lang| lang.config().wrap_characters.as_ref())
10758 .is_some()
10759 {
10760 return true;
10761 }
10762 }
10763 false
10764 }
10765
10766 fn wrap_selections_in_tag(
10767 &mut self,
10768 _: &WrapSelectionsInTag,
10769 window: &mut Window,
10770 cx: &mut Context<Self>,
10771 ) {
10772 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10773
10774 let snapshot = self.buffer.read(cx).snapshot(cx);
10775
10776 let mut edits = Vec::new();
10777 let mut boundaries = Vec::new();
10778
10779 for selection in self
10780 .selections
10781 .all_adjusted(&self.display_snapshot(cx))
10782 .iter()
10783 {
10784 let Some(wrap_config) = snapshot
10785 .language_at(selection.start)
10786 .and_then(|lang| lang.config().wrap_characters.clone())
10787 else {
10788 continue;
10789 };
10790
10791 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10792 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10793
10794 let start_before = snapshot.anchor_before(selection.start);
10795 let end_after = snapshot.anchor_after(selection.end);
10796
10797 edits.push((start_before..start_before, open_tag));
10798 edits.push((end_after..end_after, close_tag));
10799
10800 boundaries.push((
10801 start_before,
10802 end_after,
10803 wrap_config.start_prefix.len(),
10804 wrap_config.end_suffix.len(),
10805 ));
10806 }
10807
10808 if edits.is_empty() {
10809 return;
10810 }
10811
10812 self.transact(window, cx, |this, window, cx| {
10813 let buffer = this.buffer.update(cx, |buffer, cx| {
10814 buffer.edit(edits, None, cx);
10815 buffer.snapshot(cx)
10816 });
10817
10818 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10819 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10820 boundaries.into_iter()
10821 {
10822 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10823 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10824 new_selections.push(open_offset..open_offset);
10825 new_selections.push(close_offset..close_offset);
10826 }
10827
10828 this.change_selections(Default::default(), window, cx, |s| {
10829 s.select_ranges(new_selections);
10830 });
10831
10832 this.request_autoscroll(Autoscroll::fit(), cx);
10833 });
10834 }
10835
10836 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10837 let Some(project) = self.project.clone() else {
10838 return;
10839 };
10840 self.reload(project, window, cx)
10841 .detach_and_notify_err(window, cx);
10842 }
10843
10844 pub fn restore_file(
10845 &mut self,
10846 _: &::git::RestoreFile,
10847 window: &mut Window,
10848 cx: &mut Context<Self>,
10849 ) {
10850 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10851 let mut buffer_ids = HashSet::default();
10852 let snapshot = self.buffer().read(cx).snapshot(cx);
10853 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10854 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10855 }
10856
10857 let buffer = self.buffer().read(cx);
10858 let ranges = buffer_ids
10859 .into_iter()
10860 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10861 .collect::<Vec<_>>();
10862
10863 self.restore_hunks_in_ranges(ranges, window, cx);
10864 }
10865
10866 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10867 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10868 let selections = self
10869 .selections
10870 .all(&self.display_snapshot(cx))
10871 .into_iter()
10872 .map(|s| s.range())
10873 .collect();
10874 self.restore_hunks_in_ranges(selections, window, cx);
10875 }
10876
10877 pub fn restore_hunks_in_ranges(
10878 &mut self,
10879 ranges: Vec<Range<Point>>,
10880 window: &mut Window,
10881 cx: &mut Context<Editor>,
10882 ) {
10883 let mut revert_changes = HashMap::default();
10884 let chunk_by = self
10885 .snapshot(window, cx)
10886 .hunks_for_ranges(ranges)
10887 .into_iter()
10888 .chunk_by(|hunk| hunk.buffer_id);
10889 for (buffer_id, hunks) in &chunk_by {
10890 let hunks = hunks.collect::<Vec<_>>();
10891 for hunk in &hunks {
10892 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10893 }
10894 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10895 }
10896 drop(chunk_by);
10897 if !revert_changes.is_empty() {
10898 self.transact(window, cx, |editor, window, cx| {
10899 editor.restore(revert_changes, window, cx);
10900 });
10901 }
10902 }
10903
10904 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10905 if let Some(status) = self
10906 .addons
10907 .iter()
10908 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10909 {
10910 return Some(status);
10911 }
10912 self.project
10913 .as_ref()?
10914 .read(cx)
10915 .status_for_buffer_id(buffer_id, cx)
10916 }
10917
10918 pub fn open_active_item_in_terminal(
10919 &mut self,
10920 _: &OpenInTerminal,
10921 window: &mut Window,
10922 cx: &mut Context<Self>,
10923 ) {
10924 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10925 let project_path = buffer.read(cx).project_path(cx)?;
10926 let project = self.project()?.read(cx);
10927 let entry = project.entry_for_path(&project_path, cx)?;
10928 let parent = match &entry.canonical_path {
10929 Some(canonical_path) => canonical_path.to_path_buf(),
10930 None => project.absolute_path(&project_path, cx)?,
10931 }
10932 .parent()?
10933 .to_path_buf();
10934 Some(parent)
10935 }) {
10936 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10937 }
10938 }
10939
10940 fn set_breakpoint_context_menu(
10941 &mut self,
10942 display_row: DisplayRow,
10943 position: Option<Anchor>,
10944 clicked_point: gpui::Point<Pixels>,
10945 window: &mut Window,
10946 cx: &mut Context<Self>,
10947 ) {
10948 let source = self
10949 .buffer
10950 .read(cx)
10951 .snapshot(cx)
10952 .anchor_before(Point::new(display_row.0, 0u32));
10953
10954 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10955
10956 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10957 self,
10958 source,
10959 clicked_point,
10960 context_menu,
10961 window,
10962 cx,
10963 );
10964 }
10965
10966 fn add_edit_breakpoint_block(
10967 &mut self,
10968 anchor: Anchor,
10969 breakpoint: &Breakpoint,
10970 edit_action: BreakpointPromptEditAction,
10971 window: &mut Window,
10972 cx: &mut Context<Self>,
10973 ) {
10974 let weak_editor = cx.weak_entity();
10975 let bp_prompt = cx.new(|cx| {
10976 BreakpointPromptEditor::new(
10977 weak_editor,
10978 anchor,
10979 breakpoint.clone(),
10980 edit_action,
10981 window,
10982 cx,
10983 )
10984 });
10985
10986 let height = bp_prompt.update(cx, |this, cx| {
10987 this.prompt
10988 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10989 });
10990 let cloned_prompt = bp_prompt.clone();
10991 let blocks = vec![BlockProperties {
10992 style: BlockStyle::Sticky,
10993 placement: BlockPlacement::Above(anchor),
10994 height: Some(height),
10995 render: Arc::new(move |cx| {
10996 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10997 cloned_prompt.clone().into_any_element()
10998 }),
10999 priority: 0,
11000 }];
11001
11002 let focus_handle = bp_prompt.focus_handle(cx);
11003 window.focus(&focus_handle);
11004
11005 let block_ids = self.insert_blocks(blocks, None, cx);
11006 bp_prompt.update(cx, |prompt, _| {
11007 prompt.add_block_ids(block_ids);
11008 });
11009 }
11010
11011 pub(crate) fn breakpoint_at_row(
11012 &self,
11013 row: u32,
11014 window: &mut Window,
11015 cx: &mut Context<Self>,
11016 ) -> Option<(Anchor, Breakpoint)> {
11017 let snapshot = self.snapshot(window, cx);
11018 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11019
11020 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11021 }
11022
11023 pub(crate) fn breakpoint_at_anchor(
11024 &self,
11025 breakpoint_position: Anchor,
11026 snapshot: &EditorSnapshot,
11027 cx: &mut Context<Self>,
11028 ) -> Option<(Anchor, Breakpoint)> {
11029 let buffer = self
11030 .buffer
11031 .read(cx)
11032 .buffer_for_anchor(breakpoint_position, cx)?;
11033
11034 let enclosing_excerpt = breakpoint_position.excerpt_id;
11035 let buffer_snapshot = buffer.read(cx).snapshot();
11036
11037 let row = buffer_snapshot
11038 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11039 .row;
11040
11041 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11042 let anchor_end = snapshot
11043 .buffer_snapshot()
11044 .anchor_after(Point::new(row, line_len));
11045
11046 self.breakpoint_store
11047 .as_ref()?
11048 .read_with(cx, |breakpoint_store, cx| {
11049 breakpoint_store
11050 .breakpoints(
11051 &buffer,
11052 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11053 &buffer_snapshot,
11054 cx,
11055 )
11056 .next()
11057 .and_then(|(bp, _)| {
11058 let breakpoint_row = buffer_snapshot
11059 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11060 .row;
11061
11062 if breakpoint_row == row {
11063 snapshot
11064 .buffer_snapshot()
11065 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11066 .map(|position| (position, bp.bp.clone()))
11067 } else {
11068 None
11069 }
11070 })
11071 })
11072 }
11073
11074 pub fn edit_log_breakpoint(
11075 &mut self,
11076 _: &EditLogBreakpoint,
11077 window: &mut Window,
11078 cx: &mut Context<Self>,
11079 ) {
11080 if self.breakpoint_store.is_none() {
11081 return;
11082 }
11083
11084 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11085 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11086 message: None,
11087 state: BreakpointState::Enabled,
11088 condition: None,
11089 hit_condition: None,
11090 });
11091
11092 self.add_edit_breakpoint_block(
11093 anchor,
11094 &breakpoint,
11095 BreakpointPromptEditAction::Log,
11096 window,
11097 cx,
11098 );
11099 }
11100 }
11101
11102 fn breakpoints_at_cursors(
11103 &self,
11104 window: &mut Window,
11105 cx: &mut Context<Self>,
11106 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11107 let snapshot = self.snapshot(window, cx);
11108 let cursors = self
11109 .selections
11110 .disjoint_anchors_arc()
11111 .iter()
11112 .map(|selection| {
11113 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11114
11115 let breakpoint_position = self
11116 .breakpoint_at_row(cursor_position.row, window, cx)
11117 .map(|bp| bp.0)
11118 .unwrap_or_else(|| {
11119 snapshot
11120 .display_snapshot
11121 .buffer_snapshot()
11122 .anchor_after(Point::new(cursor_position.row, 0))
11123 });
11124
11125 let breakpoint = self
11126 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11127 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11128
11129 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11130 })
11131 // 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.
11132 .collect::<HashMap<Anchor, _>>();
11133
11134 cursors.into_iter().collect()
11135 }
11136
11137 pub fn enable_breakpoint(
11138 &mut self,
11139 _: &crate::actions::EnableBreakpoint,
11140 window: &mut Window,
11141 cx: &mut Context<Self>,
11142 ) {
11143 if self.breakpoint_store.is_none() {
11144 return;
11145 }
11146
11147 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11148 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11149 continue;
11150 };
11151 self.edit_breakpoint_at_anchor(
11152 anchor,
11153 breakpoint,
11154 BreakpointEditAction::InvertState,
11155 cx,
11156 );
11157 }
11158 }
11159
11160 pub fn disable_breakpoint(
11161 &mut self,
11162 _: &crate::actions::DisableBreakpoint,
11163 window: &mut Window,
11164 cx: &mut Context<Self>,
11165 ) {
11166 if self.breakpoint_store.is_none() {
11167 return;
11168 }
11169
11170 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11171 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11172 continue;
11173 };
11174 self.edit_breakpoint_at_anchor(
11175 anchor,
11176 breakpoint,
11177 BreakpointEditAction::InvertState,
11178 cx,
11179 );
11180 }
11181 }
11182
11183 pub fn toggle_breakpoint(
11184 &mut self,
11185 _: &crate::actions::ToggleBreakpoint,
11186 window: &mut Window,
11187 cx: &mut Context<Self>,
11188 ) {
11189 if self.breakpoint_store.is_none() {
11190 return;
11191 }
11192
11193 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11194 if let Some(breakpoint) = breakpoint {
11195 self.edit_breakpoint_at_anchor(
11196 anchor,
11197 breakpoint,
11198 BreakpointEditAction::Toggle,
11199 cx,
11200 );
11201 } else {
11202 self.edit_breakpoint_at_anchor(
11203 anchor,
11204 Breakpoint::new_standard(),
11205 BreakpointEditAction::Toggle,
11206 cx,
11207 );
11208 }
11209 }
11210 }
11211
11212 pub fn edit_breakpoint_at_anchor(
11213 &mut self,
11214 breakpoint_position: Anchor,
11215 breakpoint: Breakpoint,
11216 edit_action: BreakpointEditAction,
11217 cx: &mut Context<Self>,
11218 ) {
11219 let Some(breakpoint_store) = &self.breakpoint_store else {
11220 return;
11221 };
11222
11223 let Some(buffer) = self
11224 .buffer
11225 .read(cx)
11226 .buffer_for_anchor(breakpoint_position, cx)
11227 else {
11228 return;
11229 };
11230
11231 breakpoint_store.update(cx, |breakpoint_store, cx| {
11232 breakpoint_store.toggle_breakpoint(
11233 buffer,
11234 BreakpointWithPosition {
11235 position: breakpoint_position.text_anchor,
11236 bp: breakpoint,
11237 },
11238 edit_action,
11239 cx,
11240 );
11241 });
11242
11243 cx.notify();
11244 }
11245
11246 #[cfg(any(test, feature = "test-support"))]
11247 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11248 self.breakpoint_store.clone()
11249 }
11250
11251 pub fn prepare_restore_change(
11252 &self,
11253 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11254 hunk: &MultiBufferDiffHunk,
11255 cx: &mut App,
11256 ) -> Option<()> {
11257 if hunk.is_created_file() {
11258 return None;
11259 }
11260 let buffer = self.buffer.read(cx);
11261 let diff = buffer.diff_for(hunk.buffer_id)?;
11262 let buffer = buffer.buffer(hunk.buffer_id)?;
11263 let buffer = buffer.read(cx);
11264 let original_text = diff
11265 .read(cx)
11266 .base_text()
11267 .as_rope()
11268 .slice(hunk.diff_base_byte_range.clone());
11269 let buffer_snapshot = buffer.snapshot();
11270 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11271 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11272 probe
11273 .0
11274 .start
11275 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11276 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11277 }) {
11278 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11279 Some(())
11280 } else {
11281 None
11282 }
11283 }
11284
11285 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11286 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11287 }
11288
11289 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11290 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11291 }
11292
11293 fn manipulate_lines<M>(
11294 &mut self,
11295 window: &mut Window,
11296 cx: &mut Context<Self>,
11297 mut manipulate: M,
11298 ) where
11299 M: FnMut(&str) -> LineManipulationResult,
11300 {
11301 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11302
11303 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11304 let buffer = self.buffer.read(cx).snapshot(cx);
11305
11306 let mut edits = Vec::new();
11307
11308 let selections = self.selections.all::<Point>(&display_map);
11309 let mut selections = selections.iter().peekable();
11310 let mut contiguous_row_selections = Vec::new();
11311 let mut new_selections = Vec::new();
11312 let mut added_lines = 0;
11313 let mut removed_lines = 0;
11314
11315 while let Some(selection) = selections.next() {
11316 let (start_row, end_row) = consume_contiguous_rows(
11317 &mut contiguous_row_selections,
11318 selection,
11319 &display_map,
11320 &mut selections,
11321 );
11322
11323 let start_point = Point::new(start_row.0, 0);
11324 let end_point = Point::new(
11325 end_row.previous_row().0,
11326 buffer.line_len(end_row.previous_row()),
11327 );
11328 let text = buffer
11329 .text_for_range(start_point..end_point)
11330 .collect::<String>();
11331
11332 let LineManipulationResult {
11333 new_text,
11334 line_count_before,
11335 line_count_after,
11336 } = manipulate(&text);
11337
11338 edits.push((start_point..end_point, new_text));
11339
11340 // Selections must change based on added and removed line count
11341 let start_row =
11342 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11343 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11344 new_selections.push(Selection {
11345 id: selection.id,
11346 start: start_row,
11347 end: end_row,
11348 goal: SelectionGoal::None,
11349 reversed: selection.reversed,
11350 });
11351
11352 if line_count_after > line_count_before {
11353 added_lines += line_count_after - line_count_before;
11354 } else if line_count_before > line_count_after {
11355 removed_lines += line_count_before - line_count_after;
11356 }
11357 }
11358
11359 self.transact(window, cx, |this, window, cx| {
11360 let buffer = this.buffer.update(cx, |buffer, cx| {
11361 buffer.edit(edits, None, cx);
11362 buffer.snapshot(cx)
11363 });
11364
11365 // Recalculate offsets on newly edited buffer
11366 let new_selections = new_selections
11367 .iter()
11368 .map(|s| {
11369 let start_point = Point::new(s.start.0, 0);
11370 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11371 Selection {
11372 id: s.id,
11373 start: buffer.point_to_offset(start_point),
11374 end: buffer.point_to_offset(end_point),
11375 goal: s.goal,
11376 reversed: s.reversed,
11377 }
11378 })
11379 .collect();
11380
11381 this.change_selections(Default::default(), window, cx, |s| {
11382 s.select(new_selections);
11383 });
11384
11385 this.request_autoscroll(Autoscroll::fit(), cx);
11386 });
11387 }
11388
11389 fn manipulate_immutable_lines<Fn>(
11390 &mut self,
11391 window: &mut Window,
11392 cx: &mut Context<Self>,
11393 mut callback: Fn,
11394 ) where
11395 Fn: FnMut(&mut Vec<&str>),
11396 {
11397 self.manipulate_lines(window, cx, |text| {
11398 let mut lines: Vec<&str> = text.split('\n').collect();
11399 let line_count_before = lines.len();
11400
11401 callback(&mut lines);
11402
11403 LineManipulationResult {
11404 new_text: lines.join("\n"),
11405 line_count_before,
11406 line_count_after: lines.len(),
11407 }
11408 });
11409 }
11410
11411 fn manipulate_mutable_lines<Fn>(
11412 &mut self,
11413 window: &mut Window,
11414 cx: &mut Context<Self>,
11415 mut callback: Fn,
11416 ) where
11417 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11418 {
11419 self.manipulate_lines(window, cx, |text| {
11420 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11421 let line_count_before = lines.len();
11422
11423 callback(&mut lines);
11424
11425 LineManipulationResult {
11426 new_text: lines.join("\n"),
11427 line_count_before,
11428 line_count_after: lines.len(),
11429 }
11430 });
11431 }
11432
11433 pub fn convert_indentation_to_spaces(
11434 &mut self,
11435 _: &ConvertIndentationToSpaces,
11436 window: &mut Window,
11437 cx: &mut Context<Self>,
11438 ) {
11439 let settings = self.buffer.read(cx).language_settings(cx);
11440 let tab_size = settings.tab_size.get() as usize;
11441
11442 self.manipulate_mutable_lines(window, cx, |lines| {
11443 // Allocates a reasonably sized scratch buffer once for the whole loop
11444 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11445 // Avoids recomputing spaces that could be inserted many times
11446 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11447 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11448 .collect();
11449
11450 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11451 let mut chars = line.as_ref().chars();
11452 let mut col = 0;
11453 let mut changed = false;
11454
11455 for ch in chars.by_ref() {
11456 match ch {
11457 ' ' => {
11458 reindented_line.push(' ');
11459 col += 1;
11460 }
11461 '\t' => {
11462 // \t are converted to spaces depending on the current column
11463 let spaces_len = tab_size - (col % tab_size);
11464 reindented_line.extend(&space_cache[spaces_len - 1]);
11465 col += spaces_len;
11466 changed = true;
11467 }
11468 _ => {
11469 // If we dont append before break, the character is consumed
11470 reindented_line.push(ch);
11471 break;
11472 }
11473 }
11474 }
11475
11476 if !changed {
11477 reindented_line.clear();
11478 continue;
11479 }
11480 // Append the rest of the line and replace old reference with new one
11481 reindented_line.extend(chars);
11482 *line = Cow::Owned(reindented_line.clone());
11483 reindented_line.clear();
11484 }
11485 });
11486 }
11487
11488 pub fn convert_indentation_to_tabs(
11489 &mut self,
11490 _: &ConvertIndentationToTabs,
11491 window: &mut Window,
11492 cx: &mut Context<Self>,
11493 ) {
11494 let settings = self.buffer.read(cx).language_settings(cx);
11495 let tab_size = settings.tab_size.get() as usize;
11496
11497 self.manipulate_mutable_lines(window, cx, |lines| {
11498 // Allocates a reasonably sized buffer once for the whole loop
11499 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11500 // Avoids recomputing spaces that could be inserted many times
11501 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11502 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11503 .collect();
11504
11505 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11506 let mut chars = line.chars();
11507 let mut spaces_count = 0;
11508 let mut first_non_indent_char = None;
11509 let mut changed = false;
11510
11511 for ch in chars.by_ref() {
11512 match ch {
11513 ' ' => {
11514 // Keep track of spaces. Append \t when we reach tab_size
11515 spaces_count += 1;
11516 changed = true;
11517 if spaces_count == tab_size {
11518 reindented_line.push('\t');
11519 spaces_count = 0;
11520 }
11521 }
11522 '\t' => {
11523 reindented_line.push('\t');
11524 spaces_count = 0;
11525 }
11526 _ => {
11527 // Dont append it yet, we might have remaining spaces
11528 first_non_indent_char = Some(ch);
11529 break;
11530 }
11531 }
11532 }
11533
11534 if !changed {
11535 reindented_line.clear();
11536 continue;
11537 }
11538 // Remaining spaces that didn't make a full tab stop
11539 if spaces_count > 0 {
11540 reindented_line.extend(&space_cache[spaces_count - 1]);
11541 }
11542 // If we consume an extra character that was not indentation, add it back
11543 if let Some(extra_char) = first_non_indent_char {
11544 reindented_line.push(extra_char);
11545 }
11546 // Append the rest of the line and replace old reference with new one
11547 reindented_line.extend(chars);
11548 *line = Cow::Owned(reindented_line.clone());
11549 reindented_line.clear();
11550 }
11551 });
11552 }
11553
11554 pub fn convert_to_upper_case(
11555 &mut self,
11556 _: &ConvertToUpperCase,
11557 window: &mut Window,
11558 cx: &mut Context<Self>,
11559 ) {
11560 self.manipulate_text(window, cx, |text| text.to_uppercase())
11561 }
11562
11563 pub fn convert_to_lower_case(
11564 &mut self,
11565 _: &ConvertToLowerCase,
11566 window: &mut Window,
11567 cx: &mut Context<Self>,
11568 ) {
11569 self.manipulate_text(window, cx, |text| text.to_lowercase())
11570 }
11571
11572 pub fn convert_to_title_case(
11573 &mut self,
11574 _: &ConvertToTitleCase,
11575 window: &mut Window,
11576 cx: &mut Context<Self>,
11577 ) {
11578 self.manipulate_text(window, cx, |text| {
11579 text.split('\n')
11580 .map(|line| line.to_case(Case::Title))
11581 .join("\n")
11582 })
11583 }
11584
11585 pub fn convert_to_snake_case(
11586 &mut self,
11587 _: &ConvertToSnakeCase,
11588 window: &mut Window,
11589 cx: &mut Context<Self>,
11590 ) {
11591 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11592 }
11593
11594 pub fn convert_to_kebab_case(
11595 &mut self,
11596 _: &ConvertToKebabCase,
11597 window: &mut Window,
11598 cx: &mut Context<Self>,
11599 ) {
11600 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11601 }
11602
11603 pub fn convert_to_upper_camel_case(
11604 &mut self,
11605 _: &ConvertToUpperCamelCase,
11606 window: &mut Window,
11607 cx: &mut Context<Self>,
11608 ) {
11609 self.manipulate_text(window, cx, |text| {
11610 text.split('\n')
11611 .map(|line| line.to_case(Case::UpperCamel))
11612 .join("\n")
11613 })
11614 }
11615
11616 pub fn convert_to_lower_camel_case(
11617 &mut self,
11618 _: &ConvertToLowerCamelCase,
11619 window: &mut Window,
11620 cx: &mut Context<Self>,
11621 ) {
11622 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11623 }
11624
11625 pub fn convert_to_opposite_case(
11626 &mut self,
11627 _: &ConvertToOppositeCase,
11628 window: &mut Window,
11629 cx: &mut Context<Self>,
11630 ) {
11631 self.manipulate_text(window, cx, |text| {
11632 text.chars()
11633 .fold(String::with_capacity(text.len()), |mut t, c| {
11634 if c.is_uppercase() {
11635 t.extend(c.to_lowercase());
11636 } else {
11637 t.extend(c.to_uppercase());
11638 }
11639 t
11640 })
11641 })
11642 }
11643
11644 pub fn convert_to_sentence_case(
11645 &mut self,
11646 _: &ConvertToSentenceCase,
11647 window: &mut Window,
11648 cx: &mut Context<Self>,
11649 ) {
11650 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11651 }
11652
11653 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11654 self.manipulate_text(window, cx, |text| {
11655 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11656 if has_upper_case_characters {
11657 text.to_lowercase()
11658 } else {
11659 text.to_uppercase()
11660 }
11661 })
11662 }
11663
11664 pub fn convert_to_rot13(
11665 &mut self,
11666 _: &ConvertToRot13,
11667 window: &mut Window,
11668 cx: &mut Context<Self>,
11669 ) {
11670 self.manipulate_text(window, cx, |text| {
11671 text.chars()
11672 .map(|c| match c {
11673 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11674 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11675 _ => c,
11676 })
11677 .collect()
11678 })
11679 }
11680
11681 pub fn convert_to_rot47(
11682 &mut self,
11683 _: &ConvertToRot47,
11684 window: &mut Window,
11685 cx: &mut Context<Self>,
11686 ) {
11687 self.manipulate_text(window, cx, |text| {
11688 text.chars()
11689 .map(|c| {
11690 let code_point = c as u32;
11691 if code_point >= 33 && code_point <= 126 {
11692 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11693 }
11694 c
11695 })
11696 .collect()
11697 })
11698 }
11699
11700 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11701 where
11702 Fn: FnMut(&str) -> String,
11703 {
11704 let buffer = self.buffer.read(cx).snapshot(cx);
11705
11706 let mut new_selections = Vec::new();
11707 let mut edits = Vec::new();
11708 let mut selection_adjustment = 0i32;
11709
11710 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11711 let selection_is_empty = selection.is_empty();
11712
11713 let (start, end) = if selection_is_empty {
11714 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11715 (word_range.start, word_range.end)
11716 } else {
11717 (
11718 buffer.point_to_offset(selection.start),
11719 buffer.point_to_offset(selection.end),
11720 )
11721 };
11722
11723 let text = buffer.text_for_range(start..end).collect::<String>();
11724 let old_length = text.len() as i32;
11725 let text = callback(&text);
11726
11727 new_selections.push(Selection {
11728 start: (start as i32 - selection_adjustment) as usize,
11729 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11730 goal: SelectionGoal::None,
11731 id: selection.id,
11732 reversed: selection.reversed,
11733 });
11734
11735 selection_adjustment += old_length - text.len() as i32;
11736
11737 edits.push((start..end, text));
11738 }
11739
11740 self.transact(window, cx, |this, window, cx| {
11741 this.buffer.update(cx, |buffer, cx| {
11742 buffer.edit(edits, None, cx);
11743 });
11744
11745 this.change_selections(Default::default(), window, cx, |s| {
11746 s.select(new_selections);
11747 });
11748
11749 this.request_autoscroll(Autoscroll::fit(), cx);
11750 });
11751 }
11752
11753 pub fn move_selection_on_drop(
11754 &mut self,
11755 selection: &Selection<Anchor>,
11756 target: DisplayPoint,
11757 is_cut: bool,
11758 window: &mut Window,
11759 cx: &mut Context<Self>,
11760 ) {
11761 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11762 let buffer = display_map.buffer_snapshot();
11763 let mut edits = Vec::new();
11764 let insert_point = display_map
11765 .clip_point(target, Bias::Left)
11766 .to_point(&display_map);
11767 let text = buffer
11768 .text_for_range(selection.start..selection.end)
11769 .collect::<String>();
11770 if is_cut {
11771 edits.push(((selection.start..selection.end), String::new()));
11772 }
11773 let insert_anchor = buffer.anchor_before(insert_point);
11774 edits.push(((insert_anchor..insert_anchor), text));
11775 let last_edit_start = insert_anchor.bias_left(buffer);
11776 let last_edit_end = insert_anchor.bias_right(buffer);
11777 self.transact(window, cx, |this, window, cx| {
11778 this.buffer.update(cx, |buffer, cx| {
11779 buffer.edit(edits, None, cx);
11780 });
11781 this.change_selections(Default::default(), window, cx, |s| {
11782 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11783 });
11784 });
11785 }
11786
11787 pub fn clear_selection_drag_state(&mut self) {
11788 self.selection_drag_state = SelectionDragState::None;
11789 }
11790
11791 pub fn duplicate(
11792 &mut self,
11793 upwards: bool,
11794 whole_lines: bool,
11795 window: &mut Window,
11796 cx: &mut Context<Self>,
11797 ) {
11798 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11799
11800 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11801 let buffer = display_map.buffer_snapshot();
11802 let selections = self.selections.all::<Point>(&display_map);
11803
11804 let mut edits = Vec::new();
11805 let mut selections_iter = selections.iter().peekable();
11806 while let Some(selection) = selections_iter.next() {
11807 let mut rows = selection.spanned_rows(false, &display_map);
11808 // duplicate line-wise
11809 if whole_lines || selection.start == selection.end {
11810 // Avoid duplicating the same lines twice.
11811 while let Some(next_selection) = selections_iter.peek() {
11812 let next_rows = next_selection.spanned_rows(false, &display_map);
11813 if next_rows.start < rows.end {
11814 rows.end = next_rows.end;
11815 selections_iter.next().unwrap();
11816 } else {
11817 break;
11818 }
11819 }
11820
11821 // Copy the text from the selected row region and splice it either at the start
11822 // or end of the region.
11823 let start = Point::new(rows.start.0, 0);
11824 let end = Point::new(
11825 rows.end.previous_row().0,
11826 buffer.line_len(rows.end.previous_row()),
11827 );
11828
11829 let mut text = buffer.text_for_range(start..end).collect::<String>();
11830
11831 let insert_location = if upwards {
11832 // When duplicating upward, we need to insert before the current line.
11833 // If we're on the last line and it doesn't end with a newline,
11834 // we need to add a newline before the duplicated content.
11835 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11836 && buffer.max_point().column > 0
11837 && !text.ends_with('\n');
11838
11839 if needs_leading_newline {
11840 text.insert(0, '\n');
11841 end
11842 } else {
11843 text.push('\n');
11844 Point::new(rows.start.0, 0)
11845 }
11846 } else {
11847 text.push('\n');
11848 start
11849 };
11850 edits.push((insert_location..insert_location, text));
11851 } else {
11852 // duplicate character-wise
11853 let start = selection.start;
11854 let end = selection.end;
11855 let text = buffer.text_for_range(start..end).collect::<String>();
11856 edits.push((selection.end..selection.end, text));
11857 }
11858 }
11859
11860 self.transact(window, cx, |this, window, cx| {
11861 this.buffer.update(cx, |buffer, cx| {
11862 buffer.edit(edits, None, cx);
11863 });
11864
11865 // When duplicating upward with whole lines, move the cursor to the duplicated line
11866 if upwards && whole_lines {
11867 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11868
11869 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11870 let mut new_ranges = Vec::new();
11871 let selections = s.all::<Point>(&display_map);
11872 let mut selections_iter = selections.iter().peekable();
11873
11874 while let Some(first_selection) = selections_iter.next() {
11875 // Group contiguous selections together to find the total row span
11876 let mut group_selections = vec![first_selection];
11877 let mut rows = first_selection.spanned_rows(false, &display_map);
11878
11879 while let Some(next_selection) = selections_iter.peek() {
11880 let next_rows = next_selection.spanned_rows(false, &display_map);
11881 if next_rows.start < rows.end {
11882 rows.end = next_rows.end;
11883 group_selections.push(selections_iter.next().unwrap());
11884 } else {
11885 break;
11886 }
11887 }
11888
11889 let row_count = rows.end.0 - rows.start.0;
11890
11891 // Move all selections in this group up by the total number of duplicated rows
11892 for selection in group_selections {
11893 let new_start = Point::new(
11894 selection.start.row.saturating_sub(row_count),
11895 selection.start.column,
11896 );
11897
11898 let new_end = Point::new(
11899 selection.end.row.saturating_sub(row_count),
11900 selection.end.column,
11901 );
11902
11903 new_ranges.push(new_start..new_end);
11904 }
11905 }
11906
11907 s.select_ranges(new_ranges);
11908 });
11909 }
11910
11911 this.request_autoscroll(Autoscroll::fit(), cx);
11912 });
11913 }
11914
11915 pub fn duplicate_line_up(
11916 &mut self,
11917 _: &DuplicateLineUp,
11918 window: &mut Window,
11919 cx: &mut Context<Self>,
11920 ) {
11921 self.duplicate(true, true, window, cx);
11922 }
11923
11924 pub fn duplicate_line_down(
11925 &mut self,
11926 _: &DuplicateLineDown,
11927 window: &mut Window,
11928 cx: &mut Context<Self>,
11929 ) {
11930 self.duplicate(false, true, window, cx);
11931 }
11932
11933 pub fn duplicate_selection(
11934 &mut self,
11935 _: &DuplicateSelection,
11936 window: &mut Window,
11937 cx: &mut Context<Self>,
11938 ) {
11939 self.duplicate(false, false, window, cx);
11940 }
11941
11942 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11943 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11944 if self.mode.is_single_line() {
11945 cx.propagate();
11946 return;
11947 }
11948
11949 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11950 let buffer = self.buffer.read(cx).snapshot(cx);
11951
11952 let mut edits = Vec::new();
11953 let mut unfold_ranges = Vec::new();
11954 let mut refold_creases = Vec::new();
11955
11956 let selections = self.selections.all::<Point>(&display_map);
11957 let mut selections = selections.iter().peekable();
11958 let mut contiguous_row_selections = Vec::new();
11959 let mut new_selections = Vec::new();
11960
11961 while let Some(selection) = selections.next() {
11962 // Find all the selections that span a contiguous row range
11963 let (start_row, end_row) = consume_contiguous_rows(
11964 &mut contiguous_row_selections,
11965 selection,
11966 &display_map,
11967 &mut selections,
11968 );
11969
11970 // Move the text spanned by the row range to be before the line preceding the row range
11971 if start_row.0 > 0 {
11972 let range_to_move = Point::new(
11973 start_row.previous_row().0,
11974 buffer.line_len(start_row.previous_row()),
11975 )
11976 ..Point::new(
11977 end_row.previous_row().0,
11978 buffer.line_len(end_row.previous_row()),
11979 );
11980 let insertion_point = display_map
11981 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11982 .0;
11983
11984 // Don't move lines across excerpts
11985 if buffer
11986 .excerpt_containing(insertion_point..range_to_move.end)
11987 .is_some()
11988 {
11989 let text = buffer
11990 .text_for_range(range_to_move.clone())
11991 .flat_map(|s| s.chars())
11992 .skip(1)
11993 .chain(['\n'])
11994 .collect::<String>();
11995
11996 edits.push((
11997 buffer.anchor_after(range_to_move.start)
11998 ..buffer.anchor_before(range_to_move.end),
11999 String::new(),
12000 ));
12001 let insertion_anchor = buffer.anchor_after(insertion_point);
12002 edits.push((insertion_anchor..insertion_anchor, text));
12003
12004 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12005
12006 // Move selections up
12007 new_selections.extend(contiguous_row_selections.drain(..).map(
12008 |mut selection| {
12009 selection.start.row -= row_delta;
12010 selection.end.row -= row_delta;
12011 selection
12012 },
12013 ));
12014
12015 // Move folds up
12016 unfold_ranges.push(range_to_move.clone());
12017 for fold in display_map.folds_in_range(
12018 buffer.anchor_before(range_to_move.start)
12019 ..buffer.anchor_after(range_to_move.end),
12020 ) {
12021 let mut start = fold.range.start.to_point(&buffer);
12022 let mut end = fold.range.end.to_point(&buffer);
12023 start.row -= row_delta;
12024 end.row -= row_delta;
12025 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12026 }
12027 }
12028 }
12029
12030 // If we didn't move line(s), preserve the existing selections
12031 new_selections.append(&mut contiguous_row_selections);
12032 }
12033
12034 self.transact(window, cx, |this, window, cx| {
12035 this.unfold_ranges(&unfold_ranges, true, true, cx);
12036 this.buffer.update(cx, |buffer, cx| {
12037 for (range, text) in edits {
12038 buffer.edit([(range, text)], None, cx);
12039 }
12040 });
12041 this.fold_creases(refold_creases, true, window, cx);
12042 this.change_selections(Default::default(), window, cx, |s| {
12043 s.select(new_selections);
12044 })
12045 });
12046 }
12047
12048 pub fn move_line_down(
12049 &mut self,
12050 _: &MoveLineDown,
12051 window: &mut Window,
12052 cx: &mut Context<Self>,
12053 ) {
12054 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12055 if self.mode.is_single_line() {
12056 cx.propagate();
12057 return;
12058 }
12059
12060 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12061 let buffer = self.buffer.read(cx).snapshot(cx);
12062
12063 let mut edits = Vec::new();
12064 let mut unfold_ranges = Vec::new();
12065 let mut refold_creases = Vec::new();
12066
12067 let selections = self.selections.all::<Point>(&display_map);
12068 let mut selections = selections.iter().peekable();
12069 let mut contiguous_row_selections = Vec::new();
12070 let mut new_selections = Vec::new();
12071
12072 while let Some(selection) = selections.next() {
12073 // Find all the selections that span a contiguous row range
12074 let (start_row, end_row) = consume_contiguous_rows(
12075 &mut contiguous_row_selections,
12076 selection,
12077 &display_map,
12078 &mut selections,
12079 );
12080
12081 // Move the text spanned by the row range to be after the last line of the row range
12082 if end_row.0 <= buffer.max_point().row {
12083 let range_to_move =
12084 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12085 let insertion_point = display_map
12086 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12087 .0;
12088
12089 // Don't move lines across excerpt boundaries
12090 if buffer
12091 .excerpt_containing(range_to_move.start..insertion_point)
12092 .is_some()
12093 {
12094 let mut text = String::from("\n");
12095 text.extend(buffer.text_for_range(range_to_move.clone()));
12096 text.pop(); // Drop trailing newline
12097 edits.push((
12098 buffer.anchor_after(range_to_move.start)
12099 ..buffer.anchor_before(range_to_move.end),
12100 String::new(),
12101 ));
12102 let insertion_anchor = buffer.anchor_after(insertion_point);
12103 edits.push((insertion_anchor..insertion_anchor, text));
12104
12105 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12106
12107 // Move selections down
12108 new_selections.extend(contiguous_row_selections.drain(..).map(
12109 |mut selection| {
12110 selection.start.row += row_delta;
12111 selection.end.row += row_delta;
12112 selection
12113 },
12114 ));
12115
12116 // Move folds down
12117 unfold_ranges.push(range_to_move.clone());
12118 for fold in display_map.folds_in_range(
12119 buffer.anchor_before(range_to_move.start)
12120 ..buffer.anchor_after(range_to_move.end),
12121 ) {
12122 let mut start = fold.range.start.to_point(&buffer);
12123 let mut end = fold.range.end.to_point(&buffer);
12124 start.row += row_delta;
12125 end.row += row_delta;
12126 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12127 }
12128 }
12129 }
12130
12131 // If we didn't move line(s), preserve the existing selections
12132 new_selections.append(&mut contiguous_row_selections);
12133 }
12134
12135 self.transact(window, cx, |this, window, cx| {
12136 this.unfold_ranges(&unfold_ranges, true, true, cx);
12137 this.buffer.update(cx, |buffer, cx| {
12138 for (range, text) in edits {
12139 buffer.edit([(range, text)], None, cx);
12140 }
12141 });
12142 this.fold_creases(refold_creases, true, window, cx);
12143 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12144 });
12145 }
12146
12147 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12148 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12149 let text_layout_details = &self.text_layout_details(window);
12150 self.transact(window, cx, |this, window, cx| {
12151 let edits = this.change_selections(Default::default(), window, cx, |s| {
12152 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12153 s.move_with(|display_map, selection| {
12154 if !selection.is_empty() {
12155 return;
12156 }
12157
12158 let mut head = selection.head();
12159 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12160 if head.column() == display_map.line_len(head.row()) {
12161 transpose_offset = display_map
12162 .buffer_snapshot()
12163 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12164 }
12165
12166 if transpose_offset == 0 {
12167 return;
12168 }
12169
12170 *head.column_mut() += 1;
12171 head = display_map.clip_point(head, Bias::Right);
12172 let goal = SelectionGoal::HorizontalPosition(
12173 display_map
12174 .x_for_display_point(head, text_layout_details)
12175 .into(),
12176 );
12177 selection.collapse_to(head, goal);
12178
12179 let transpose_start = display_map
12180 .buffer_snapshot()
12181 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12182 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12183 let transpose_end = display_map
12184 .buffer_snapshot()
12185 .clip_offset(transpose_offset + 1, Bias::Right);
12186 if let Some(ch) = display_map
12187 .buffer_snapshot()
12188 .chars_at(transpose_start)
12189 .next()
12190 {
12191 edits.push((transpose_start..transpose_offset, String::new()));
12192 edits.push((transpose_end..transpose_end, ch.to_string()));
12193 }
12194 }
12195 });
12196 edits
12197 });
12198 this.buffer
12199 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12200 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12201 this.change_selections(Default::default(), window, cx, |s| {
12202 s.select(selections);
12203 });
12204 });
12205 }
12206
12207 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12208 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12209 if self.mode.is_single_line() {
12210 cx.propagate();
12211 return;
12212 }
12213
12214 self.rewrap_impl(RewrapOptions::default(), cx)
12215 }
12216
12217 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12218 let buffer = self.buffer.read(cx).snapshot(cx);
12219 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12220
12221 #[derive(Clone, Debug, PartialEq)]
12222 enum CommentFormat {
12223 /// single line comment, with prefix for line
12224 Line(String),
12225 /// single line within a block comment, with prefix for line
12226 BlockLine(String),
12227 /// a single line of a block comment that includes the initial delimiter
12228 BlockCommentWithStart(BlockCommentConfig),
12229 /// a single line of a block comment that includes the ending delimiter
12230 BlockCommentWithEnd(BlockCommentConfig),
12231 }
12232
12233 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12234 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12235 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12236 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12237 .peekable();
12238
12239 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12240 row
12241 } else {
12242 return Vec::new();
12243 };
12244
12245 let language_settings = buffer.language_settings_at(selection.head(), cx);
12246 let language_scope = buffer.language_scope_at(selection.head());
12247
12248 let indent_and_prefix_for_row =
12249 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12250 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12251 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12252 &language_scope
12253 {
12254 let indent_end = Point::new(row, indent.len);
12255 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12256 let line_text_after_indent = buffer
12257 .text_for_range(indent_end..line_end)
12258 .collect::<String>();
12259
12260 let is_within_comment_override = buffer
12261 .language_scope_at(indent_end)
12262 .is_some_and(|scope| scope.override_name() == Some("comment"));
12263 let comment_delimiters = if is_within_comment_override {
12264 // we are within a comment syntax node, but we don't
12265 // yet know what kind of comment: block, doc or line
12266 match (
12267 language_scope.documentation_comment(),
12268 language_scope.block_comment(),
12269 ) {
12270 (Some(config), _) | (_, Some(config))
12271 if buffer.contains_str_at(indent_end, &config.start) =>
12272 {
12273 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12274 }
12275 (Some(config), _) | (_, Some(config))
12276 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12277 {
12278 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12279 }
12280 (Some(config), _) | (_, Some(config))
12281 if buffer.contains_str_at(indent_end, &config.prefix) =>
12282 {
12283 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12284 }
12285 (_, _) => language_scope
12286 .line_comment_prefixes()
12287 .iter()
12288 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12289 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12290 }
12291 } else {
12292 // we not in an overridden comment node, but we may
12293 // be within a non-overridden line comment node
12294 language_scope
12295 .line_comment_prefixes()
12296 .iter()
12297 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12298 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12299 };
12300
12301 let rewrap_prefix = language_scope
12302 .rewrap_prefixes()
12303 .iter()
12304 .find_map(|prefix_regex| {
12305 prefix_regex.find(&line_text_after_indent).map(|mat| {
12306 if mat.start() == 0 {
12307 Some(mat.as_str().to_string())
12308 } else {
12309 None
12310 }
12311 })
12312 })
12313 .flatten();
12314 (comment_delimiters, rewrap_prefix)
12315 } else {
12316 (None, None)
12317 };
12318 (indent, comment_prefix, rewrap_prefix)
12319 };
12320
12321 let mut ranges = Vec::new();
12322 let from_empty_selection = selection.is_empty();
12323
12324 let mut current_range_start = first_row;
12325 let mut prev_row = first_row;
12326 let (
12327 mut current_range_indent,
12328 mut current_range_comment_delimiters,
12329 mut current_range_rewrap_prefix,
12330 ) = indent_and_prefix_for_row(first_row);
12331
12332 for row in non_blank_rows_iter.skip(1) {
12333 let has_paragraph_break = row > prev_row + 1;
12334
12335 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12336 indent_and_prefix_for_row(row);
12337
12338 let has_indent_change = row_indent != current_range_indent;
12339 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12340
12341 let has_boundary_change = has_comment_change
12342 || row_rewrap_prefix.is_some()
12343 || (has_indent_change && current_range_comment_delimiters.is_some());
12344
12345 if has_paragraph_break || has_boundary_change {
12346 ranges.push((
12347 language_settings.clone(),
12348 Point::new(current_range_start, 0)
12349 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12350 current_range_indent,
12351 current_range_comment_delimiters.clone(),
12352 current_range_rewrap_prefix.clone(),
12353 from_empty_selection,
12354 ));
12355 current_range_start = row;
12356 current_range_indent = row_indent;
12357 current_range_comment_delimiters = row_comment_delimiters;
12358 current_range_rewrap_prefix = row_rewrap_prefix;
12359 }
12360 prev_row = row;
12361 }
12362
12363 ranges.push((
12364 language_settings.clone(),
12365 Point::new(current_range_start, 0)
12366 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12367 current_range_indent,
12368 current_range_comment_delimiters,
12369 current_range_rewrap_prefix,
12370 from_empty_selection,
12371 ));
12372
12373 ranges
12374 });
12375
12376 let mut edits = Vec::new();
12377 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12378
12379 for (
12380 language_settings,
12381 wrap_range,
12382 mut indent_size,
12383 comment_prefix,
12384 rewrap_prefix,
12385 from_empty_selection,
12386 ) in wrap_ranges
12387 {
12388 let mut start_row = wrap_range.start.row;
12389 let mut end_row = wrap_range.end.row;
12390
12391 // Skip selections that overlap with a range that has already been rewrapped.
12392 let selection_range = start_row..end_row;
12393 if rewrapped_row_ranges
12394 .iter()
12395 .any(|range| range.overlaps(&selection_range))
12396 {
12397 continue;
12398 }
12399
12400 let tab_size = language_settings.tab_size;
12401
12402 let (line_prefix, inside_comment) = match &comment_prefix {
12403 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12404 (Some(prefix.as_str()), true)
12405 }
12406 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12407 (Some(prefix.as_ref()), true)
12408 }
12409 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12410 start: _,
12411 end: _,
12412 prefix,
12413 tab_size,
12414 })) => {
12415 indent_size.len += tab_size;
12416 (Some(prefix.as_ref()), true)
12417 }
12418 None => (None, false),
12419 };
12420 let indent_prefix = indent_size.chars().collect::<String>();
12421 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12422
12423 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12424 RewrapBehavior::InComments => inside_comment,
12425 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12426 RewrapBehavior::Anywhere => true,
12427 };
12428
12429 let should_rewrap = options.override_language_settings
12430 || allow_rewrap_based_on_language
12431 || self.hard_wrap.is_some();
12432 if !should_rewrap {
12433 continue;
12434 }
12435
12436 if from_empty_selection {
12437 'expand_upwards: while start_row > 0 {
12438 let prev_row = start_row - 1;
12439 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12440 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12441 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12442 {
12443 start_row = prev_row;
12444 } else {
12445 break 'expand_upwards;
12446 }
12447 }
12448
12449 'expand_downwards: while end_row < buffer.max_point().row {
12450 let next_row = end_row + 1;
12451 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12452 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12453 && !buffer.is_line_blank(MultiBufferRow(next_row))
12454 {
12455 end_row = next_row;
12456 } else {
12457 break 'expand_downwards;
12458 }
12459 }
12460 }
12461
12462 let start = Point::new(start_row, 0);
12463 let start_offset = ToOffset::to_offset(&start, &buffer);
12464 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12465 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12466 let mut first_line_delimiter = None;
12467 let mut last_line_delimiter = None;
12468 let Some(lines_without_prefixes) = selection_text
12469 .lines()
12470 .enumerate()
12471 .map(|(ix, line)| {
12472 let line_trimmed = line.trim_start();
12473 if rewrap_prefix.is_some() && ix > 0 {
12474 Ok(line_trimmed)
12475 } else if let Some(
12476 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12477 start,
12478 prefix,
12479 end,
12480 tab_size,
12481 })
12482 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12483 start,
12484 prefix,
12485 end,
12486 tab_size,
12487 }),
12488 ) = &comment_prefix
12489 {
12490 let line_trimmed = line_trimmed
12491 .strip_prefix(start.as_ref())
12492 .map(|s| {
12493 let mut indent_size = indent_size;
12494 indent_size.len -= tab_size;
12495 let indent_prefix: String = indent_size.chars().collect();
12496 first_line_delimiter = Some((indent_prefix, start));
12497 s.trim_start()
12498 })
12499 .unwrap_or(line_trimmed);
12500 let line_trimmed = line_trimmed
12501 .strip_suffix(end.as_ref())
12502 .map(|s| {
12503 last_line_delimiter = Some(end);
12504 s.trim_end()
12505 })
12506 .unwrap_or(line_trimmed);
12507 let line_trimmed = line_trimmed
12508 .strip_prefix(prefix.as_ref())
12509 .unwrap_or(line_trimmed);
12510 Ok(line_trimmed)
12511 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12512 line_trimmed.strip_prefix(prefix).with_context(|| {
12513 format!("line did not start with prefix {prefix:?}: {line:?}")
12514 })
12515 } else {
12516 line_trimmed
12517 .strip_prefix(&line_prefix.trim_start())
12518 .with_context(|| {
12519 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12520 })
12521 }
12522 })
12523 .collect::<Result<Vec<_>, _>>()
12524 .log_err()
12525 else {
12526 continue;
12527 };
12528
12529 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12530 buffer
12531 .language_settings_at(Point::new(start_row, 0), cx)
12532 .preferred_line_length as usize
12533 });
12534
12535 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12536 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12537 } else {
12538 line_prefix.clone()
12539 };
12540
12541 let wrapped_text = {
12542 let mut wrapped_text = wrap_with_prefix(
12543 line_prefix,
12544 subsequent_lines_prefix,
12545 lines_without_prefixes.join("\n"),
12546 wrap_column,
12547 tab_size,
12548 options.preserve_existing_whitespace,
12549 );
12550
12551 if let Some((indent, delimiter)) = first_line_delimiter {
12552 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12553 }
12554 if let Some(last_line) = last_line_delimiter {
12555 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12556 }
12557
12558 wrapped_text
12559 };
12560
12561 // TODO: should always use char-based diff while still supporting cursor behavior that
12562 // matches vim.
12563 let mut diff_options = DiffOptions::default();
12564 if options.override_language_settings {
12565 diff_options.max_word_diff_len = 0;
12566 diff_options.max_word_diff_line_count = 0;
12567 } else {
12568 diff_options.max_word_diff_len = usize::MAX;
12569 diff_options.max_word_diff_line_count = usize::MAX;
12570 }
12571
12572 for (old_range, new_text) in
12573 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12574 {
12575 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12576 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12577 edits.push((edit_start..edit_end, new_text));
12578 }
12579
12580 rewrapped_row_ranges.push(start_row..=end_row);
12581 }
12582
12583 self.buffer
12584 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12585 }
12586
12587 pub fn cut_common(
12588 &mut self,
12589 cut_no_selection_line: bool,
12590 window: &mut Window,
12591 cx: &mut Context<Self>,
12592 ) -> ClipboardItem {
12593 let mut text = String::new();
12594 let buffer = self.buffer.read(cx).snapshot(cx);
12595 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12596 let mut clipboard_selections = Vec::with_capacity(selections.len());
12597 {
12598 let max_point = buffer.max_point();
12599 let mut is_first = true;
12600 let mut prev_selection_was_entire_line = false;
12601 for selection in &mut selections {
12602 let is_entire_line =
12603 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12604 if is_entire_line {
12605 selection.start = Point::new(selection.start.row, 0);
12606 if !selection.is_empty() && selection.end.column == 0 {
12607 selection.end = cmp::min(max_point, selection.end);
12608 } else {
12609 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12610 }
12611 selection.goal = SelectionGoal::None;
12612 }
12613 if is_first {
12614 is_first = false;
12615 } else if !prev_selection_was_entire_line {
12616 text += "\n";
12617 }
12618 prev_selection_was_entire_line = is_entire_line;
12619 let mut len = 0;
12620 for chunk in buffer.text_for_range(selection.start..selection.end) {
12621 text.push_str(chunk);
12622 len += chunk.len();
12623 }
12624 clipboard_selections.push(ClipboardSelection {
12625 len,
12626 is_entire_line,
12627 first_line_indent: buffer
12628 .indent_size_for_line(MultiBufferRow(selection.start.row))
12629 .len,
12630 });
12631 }
12632 }
12633
12634 self.transact(window, cx, |this, window, cx| {
12635 this.change_selections(Default::default(), window, cx, |s| {
12636 s.select(selections);
12637 });
12638 this.insert("", window, cx);
12639 });
12640 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12641 }
12642
12643 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12644 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12645 let item = self.cut_common(true, window, cx);
12646 cx.write_to_clipboard(item);
12647 }
12648
12649 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12650 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12651 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12652 s.move_with(|snapshot, sel| {
12653 if sel.is_empty() {
12654 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12655 }
12656 if sel.is_empty() {
12657 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12658 }
12659 });
12660 });
12661 let item = self.cut_common(false, window, cx);
12662 cx.set_global(KillRing(item))
12663 }
12664
12665 pub fn kill_ring_yank(
12666 &mut self,
12667 _: &KillRingYank,
12668 window: &mut Window,
12669 cx: &mut Context<Self>,
12670 ) {
12671 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12672 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12673 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12674 (kill_ring.text().to_string(), kill_ring.metadata_json())
12675 } else {
12676 return;
12677 }
12678 } else {
12679 return;
12680 };
12681 self.do_paste(&text, metadata, false, window, cx);
12682 }
12683
12684 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12685 self.do_copy(true, cx);
12686 }
12687
12688 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12689 self.do_copy(false, cx);
12690 }
12691
12692 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12693 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12694 let buffer = self.buffer.read(cx).read(cx);
12695 let mut text = String::new();
12696
12697 let mut clipboard_selections = Vec::with_capacity(selections.len());
12698 {
12699 let max_point = buffer.max_point();
12700 let mut is_first = true;
12701 let mut prev_selection_was_entire_line = false;
12702 for selection in &selections {
12703 let mut start = selection.start;
12704 let mut end = selection.end;
12705 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12706 let mut add_trailing_newline = false;
12707 if is_entire_line {
12708 start = Point::new(start.row, 0);
12709 let next_line_start = Point::new(end.row + 1, 0);
12710 if next_line_start <= max_point {
12711 end = next_line_start;
12712 } else {
12713 // We're on the last line without a trailing newline.
12714 // Copy to the end of the line and add a newline afterwards.
12715 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12716 add_trailing_newline = true;
12717 }
12718 }
12719
12720 let mut trimmed_selections = Vec::new();
12721 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12722 let row = MultiBufferRow(start.row);
12723 let first_indent = buffer.indent_size_for_line(row);
12724 if first_indent.len == 0 || start.column > first_indent.len {
12725 trimmed_selections.push(start..end);
12726 } else {
12727 trimmed_selections.push(
12728 Point::new(row.0, first_indent.len)
12729 ..Point::new(row.0, buffer.line_len(row)),
12730 );
12731 for row in start.row + 1..=end.row {
12732 let mut line_len = buffer.line_len(MultiBufferRow(row));
12733 if row == end.row {
12734 line_len = end.column;
12735 }
12736 if line_len == 0 {
12737 trimmed_selections
12738 .push(Point::new(row, 0)..Point::new(row, line_len));
12739 continue;
12740 }
12741 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12742 if row_indent_size.len >= first_indent.len {
12743 trimmed_selections.push(
12744 Point::new(row, first_indent.len)..Point::new(row, line_len),
12745 );
12746 } else {
12747 trimmed_selections.clear();
12748 trimmed_selections.push(start..end);
12749 break;
12750 }
12751 }
12752 }
12753 } else {
12754 trimmed_selections.push(start..end);
12755 }
12756
12757 for trimmed_range in trimmed_selections {
12758 if is_first {
12759 is_first = false;
12760 } else if !prev_selection_was_entire_line {
12761 text += "\n";
12762 }
12763 prev_selection_was_entire_line = is_entire_line;
12764 let mut len = 0;
12765 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12766 text.push_str(chunk);
12767 len += chunk.len();
12768 }
12769 if add_trailing_newline {
12770 text.push('\n');
12771 len += 1;
12772 }
12773 clipboard_selections.push(ClipboardSelection {
12774 len,
12775 is_entire_line,
12776 first_line_indent: buffer
12777 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12778 .len,
12779 });
12780 }
12781 }
12782 }
12783
12784 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12785 text,
12786 clipboard_selections,
12787 ));
12788 }
12789
12790 pub fn do_paste(
12791 &mut self,
12792 text: &String,
12793 clipboard_selections: Option<Vec<ClipboardSelection>>,
12794 handle_entire_lines: bool,
12795 window: &mut Window,
12796 cx: &mut Context<Self>,
12797 ) {
12798 if self.read_only(cx) {
12799 return;
12800 }
12801
12802 let clipboard_text = Cow::Borrowed(text.as_str());
12803
12804 self.transact(window, cx, |this, window, cx| {
12805 let had_active_edit_prediction = this.has_active_edit_prediction();
12806 let display_map = this.display_snapshot(cx);
12807 let old_selections = this.selections.all::<usize>(&display_map);
12808 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12809
12810 if let Some(mut clipboard_selections) = clipboard_selections {
12811 let all_selections_were_entire_line =
12812 clipboard_selections.iter().all(|s| s.is_entire_line);
12813 let first_selection_indent_column =
12814 clipboard_selections.first().map(|s| s.first_line_indent);
12815 if clipboard_selections.len() != old_selections.len() {
12816 clipboard_selections.drain(..);
12817 }
12818 let mut auto_indent_on_paste = true;
12819
12820 this.buffer.update(cx, |buffer, cx| {
12821 let snapshot = buffer.read(cx);
12822 auto_indent_on_paste = snapshot
12823 .language_settings_at(cursor_offset, cx)
12824 .auto_indent_on_paste;
12825
12826 let mut start_offset = 0;
12827 let mut edits = Vec::new();
12828 let mut original_indent_columns = Vec::new();
12829 for (ix, selection) in old_selections.iter().enumerate() {
12830 let to_insert;
12831 let entire_line;
12832 let original_indent_column;
12833 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12834 let end_offset = start_offset + clipboard_selection.len;
12835 to_insert = &clipboard_text[start_offset..end_offset];
12836 entire_line = clipboard_selection.is_entire_line;
12837 start_offset = if entire_line {
12838 end_offset
12839 } else {
12840 end_offset + 1
12841 };
12842 original_indent_column = Some(clipboard_selection.first_line_indent);
12843 } else {
12844 to_insert = &*clipboard_text;
12845 entire_line = all_selections_were_entire_line;
12846 original_indent_column = first_selection_indent_column
12847 }
12848
12849 let (range, to_insert) =
12850 if selection.is_empty() && handle_entire_lines && entire_line {
12851 // If the corresponding selection was empty when this slice of the
12852 // clipboard text was written, then the entire line containing the
12853 // selection was copied. If this selection is also currently empty,
12854 // then paste the line before the current line of the buffer.
12855 let column = selection.start.to_point(&snapshot).column as usize;
12856 let line_start = selection.start - column;
12857 (line_start..line_start, Cow::Borrowed(to_insert))
12858 } else {
12859 let language = snapshot.language_at(selection.head());
12860 let range = selection.range();
12861 if let Some(language) = language
12862 && language.name() == "Markdown".into()
12863 {
12864 edit_for_markdown_paste(
12865 &snapshot,
12866 range,
12867 to_insert,
12868 url::Url::parse(to_insert).ok(),
12869 )
12870 } else {
12871 (range, Cow::Borrowed(to_insert))
12872 }
12873 };
12874
12875 edits.push((range, to_insert));
12876 original_indent_columns.push(original_indent_column);
12877 }
12878 drop(snapshot);
12879
12880 buffer.edit(
12881 edits,
12882 if auto_indent_on_paste {
12883 Some(AutoindentMode::Block {
12884 original_indent_columns,
12885 })
12886 } else {
12887 None
12888 },
12889 cx,
12890 );
12891 });
12892
12893 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12894 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12895 } else {
12896 let url = url::Url::parse(&clipboard_text).ok();
12897
12898 let auto_indent_mode = if !clipboard_text.is_empty() {
12899 Some(AutoindentMode::Block {
12900 original_indent_columns: Vec::new(),
12901 })
12902 } else {
12903 None
12904 };
12905
12906 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12907 let snapshot = buffer.snapshot(cx);
12908
12909 let anchors = old_selections
12910 .iter()
12911 .map(|s| {
12912 let anchor = snapshot.anchor_after(s.head());
12913 s.map(|_| anchor)
12914 })
12915 .collect::<Vec<_>>();
12916
12917 let mut edits = Vec::new();
12918
12919 for selection in old_selections.iter() {
12920 let language = snapshot.language_at(selection.head());
12921 let range = selection.range();
12922
12923 let (edit_range, edit_text) = if let Some(language) = language
12924 && language.name() == "Markdown".into()
12925 {
12926 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12927 } else {
12928 (range, clipboard_text.clone())
12929 };
12930
12931 edits.push((edit_range, edit_text));
12932 }
12933
12934 drop(snapshot);
12935 buffer.edit(edits, auto_indent_mode, cx);
12936
12937 anchors
12938 });
12939
12940 this.change_selections(Default::default(), window, cx, |s| {
12941 s.select_anchors(selection_anchors);
12942 });
12943 }
12944
12945 // 🤔 | .. | show_in_menu |
12946 // | .. | true true
12947 // | had_edit_prediction | false true
12948
12949 let trigger_in_words =
12950 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12951
12952 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12953 });
12954 }
12955
12956 pub fn diff_clipboard_with_selection(
12957 &mut self,
12958 _: &DiffClipboardWithSelection,
12959 window: &mut Window,
12960 cx: &mut Context<Self>,
12961 ) {
12962 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12963
12964 if selections.is_empty() {
12965 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12966 return;
12967 };
12968
12969 let clipboard_text = match cx.read_from_clipboard() {
12970 Some(item) => match item.entries().first() {
12971 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12972 _ => None,
12973 },
12974 None => None,
12975 };
12976
12977 let Some(clipboard_text) = clipboard_text else {
12978 log::warn!("Clipboard doesn't contain text.");
12979 return;
12980 };
12981
12982 window.dispatch_action(
12983 Box::new(DiffClipboardWithSelectionData {
12984 clipboard_text,
12985 editor: cx.entity(),
12986 }),
12987 cx,
12988 );
12989 }
12990
12991 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12992 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12993 if let Some(item) = cx.read_from_clipboard() {
12994 let entries = item.entries();
12995
12996 match entries.first() {
12997 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12998 // of all the pasted entries.
12999 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13000 .do_paste(
13001 clipboard_string.text(),
13002 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13003 true,
13004 window,
13005 cx,
13006 ),
13007 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13008 }
13009 }
13010 }
13011
13012 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13013 if self.read_only(cx) {
13014 return;
13015 }
13016
13017 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13018
13019 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13020 if let Some((selections, _)) =
13021 self.selection_history.transaction(transaction_id).cloned()
13022 {
13023 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13024 s.select_anchors(selections.to_vec());
13025 });
13026 } else {
13027 log::error!(
13028 "No entry in selection_history found for undo. \
13029 This may correspond to a bug where undo does not update the selection. \
13030 If this is occurring, please add details to \
13031 https://github.com/zed-industries/zed/issues/22692"
13032 );
13033 }
13034 self.request_autoscroll(Autoscroll::fit(), cx);
13035 self.unmark_text(window, cx);
13036 self.refresh_edit_prediction(true, false, window, cx);
13037 cx.emit(EditorEvent::Edited { transaction_id });
13038 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13039 }
13040 }
13041
13042 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13043 if self.read_only(cx) {
13044 return;
13045 }
13046
13047 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13048
13049 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13050 if let Some((_, Some(selections))) =
13051 self.selection_history.transaction(transaction_id).cloned()
13052 {
13053 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13054 s.select_anchors(selections.to_vec());
13055 });
13056 } else {
13057 log::error!(
13058 "No entry in selection_history found for redo. \
13059 This may correspond to a bug where undo does not update the selection. \
13060 If this is occurring, please add details to \
13061 https://github.com/zed-industries/zed/issues/22692"
13062 );
13063 }
13064 self.request_autoscroll(Autoscroll::fit(), cx);
13065 self.unmark_text(window, cx);
13066 self.refresh_edit_prediction(true, false, window, cx);
13067 cx.emit(EditorEvent::Edited { transaction_id });
13068 }
13069 }
13070
13071 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13072 self.buffer
13073 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13074 }
13075
13076 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13077 self.buffer
13078 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13079 }
13080
13081 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13082 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13083 self.change_selections(Default::default(), window, cx, |s| {
13084 s.move_with(|map, selection| {
13085 let cursor = if selection.is_empty() {
13086 movement::left(map, selection.start)
13087 } else {
13088 selection.start
13089 };
13090 selection.collapse_to(cursor, SelectionGoal::None);
13091 });
13092 })
13093 }
13094
13095 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13096 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13097 self.change_selections(Default::default(), window, cx, |s| {
13098 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13099 })
13100 }
13101
13102 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13103 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13104 self.change_selections(Default::default(), window, cx, |s| {
13105 s.move_with(|map, selection| {
13106 let cursor = if selection.is_empty() {
13107 movement::right(map, selection.end)
13108 } else {
13109 selection.end
13110 };
13111 selection.collapse_to(cursor, SelectionGoal::None)
13112 });
13113 })
13114 }
13115
13116 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13117 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13118 self.change_selections(Default::default(), window, cx, |s| {
13119 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13120 });
13121 }
13122
13123 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13124 if self.take_rename(true, window, cx).is_some() {
13125 return;
13126 }
13127
13128 if self.mode.is_single_line() {
13129 cx.propagate();
13130 return;
13131 }
13132
13133 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13134
13135 let text_layout_details = &self.text_layout_details(window);
13136 let selection_count = self.selections.count();
13137 let first_selection = self.selections.first_anchor();
13138
13139 self.change_selections(Default::default(), window, cx, |s| {
13140 s.move_with(|map, selection| {
13141 if !selection.is_empty() {
13142 selection.goal = SelectionGoal::None;
13143 }
13144 let (cursor, goal) = movement::up(
13145 map,
13146 selection.start,
13147 selection.goal,
13148 false,
13149 text_layout_details,
13150 );
13151 selection.collapse_to(cursor, goal);
13152 });
13153 });
13154
13155 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13156 {
13157 cx.propagate();
13158 }
13159 }
13160
13161 pub fn move_up_by_lines(
13162 &mut self,
13163 action: &MoveUpByLines,
13164 window: &mut Window,
13165 cx: &mut Context<Self>,
13166 ) {
13167 if self.take_rename(true, window, cx).is_some() {
13168 return;
13169 }
13170
13171 if self.mode.is_single_line() {
13172 cx.propagate();
13173 return;
13174 }
13175
13176 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13177
13178 let text_layout_details = &self.text_layout_details(window);
13179
13180 self.change_selections(Default::default(), window, cx, |s| {
13181 s.move_with(|map, selection| {
13182 if !selection.is_empty() {
13183 selection.goal = SelectionGoal::None;
13184 }
13185 let (cursor, goal) = movement::up_by_rows(
13186 map,
13187 selection.start,
13188 action.lines,
13189 selection.goal,
13190 false,
13191 text_layout_details,
13192 );
13193 selection.collapse_to(cursor, goal);
13194 });
13195 })
13196 }
13197
13198 pub fn move_down_by_lines(
13199 &mut self,
13200 action: &MoveDownByLines,
13201 window: &mut Window,
13202 cx: &mut Context<Self>,
13203 ) {
13204 if self.take_rename(true, window, cx).is_some() {
13205 return;
13206 }
13207
13208 if self.mode.is_single_line() {
13209 cx.propagate();
13210 return;
13211 }
13212
13213 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13214
13215 let text_layout_details = &self.text_layout_details(window);
13216
13217 self.change_selections(Default::default(), window, cx, |s| {
13218 s.move_with(|map, selection| {
13219 if !selection.is_empty() {
13220 selection.goal = SelectionGoal::None;
13221 }
13222 let (cursor, goal) = movement::down_by_rows(
13223 map,
13224 selection.start,
13225 action.lines,
13226 selection.goal,
13227 false,
13228 text_layout_details,
13229 );
13230 selection.collapse_to(cursor, goal);
13231 });
13232 })
13233 }
13234
13235 pub fn select_down_by_lines(
13236 &mut self,
13237 action: &SelectDownByLines,
13238 window: &mut Window,
13239 cx: &mut Context<Self>,
13240 ) {
13241 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13242 let text_layout_details = &self.text_layout_details(window);
13243 self.change_selections(Default::default(), window, cx, |s| {
13244 s.move_heads_with(|map, head, goal| {
13245 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13246 })
13247 })
13248 }
13249
13250 pub fn select_up_by_lines(
13251 &mut self,
13252 action: &SelectUpByLines,
13253 window: &mut Window,
13254 cx: &mut Context<Self>,
13255 ) {
13256 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13257 let text_layout_details = &self.text_layout_details(window);
13258 self.change_selections(Default::default(), window, cx, |s| {
13259 s.move_heads_with(|map, head, goal| {
13260 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13261 })
13262 })
13263 }
13264
13265 pub fn select_page_up(
13266 &mut self,
13267 _: &SelectPageUp,
13268 window: &mut Window,
13269 cx: &mut Context<Self>,
13270 ) {
13271 let Some(row_count) = self.visible_row_count() else {
13272 return;
13273 };
13274
13275 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13276
13277 let text_layout_details = &self.text_layout_details(window);
13278
13279 self.change_selections(Default::default(), window, cx, |s| {
13280 s.move_heads_with(|map, head, goal| {
13281 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13282 })
13283 })
13284 }
13285
13286 pub fn move_page_up(
13287 &mut self,
13288 action: &MovePageUp,
13289 window: &mut Window,
13290 cx: &mut Context<Self>,
13291 ) {
13292 if self.take_rename(true, window, cx).is_some() {
13293 return;
13294 }
13295
13296 if self
13297 .context_menu
13298 .borrow_mut()
13299 .as_mut()
13300 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13301 .unwrap_or(false)
13302 {
13303 return;
13304 }
13305
13306 if matches!(self.mode, EditorMode::SingleLine) {
13307 cx.propagate();
13308 return;
13309 }
13310
13311 let Some(row_count) = self.visible_row_count() else {
13312 return;
13313 };
13314
13315 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13316
13317 let effects = if action.center_cursor {
13318 SelectionEffects::scroll(Autoscroll::center())
13319 } else {
13320 SelectionEffects::default()
13321 };
13322
13323 let text_layout_details = &self.text_layout_details(window);
13324
13325 self.change_selections(effects, window, cx, |s| {
13326 s.move_with(|map, selection| {
13327 if !selection.is_empty() {
13328 selection.goal = SelectionGoal::None;
13329 }
13330 let (cursor, goal) = movement::up_by_rows(
13331 map,
13332 selection.end,
13333 row_count,
13334 selection.goal,
13335 false,
13336 text_layout_details,
13337 );
13338 selection.collapse_to(cursor, goal);
13339 });
13340 });
13341 }
13342
13343 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13344 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13345 let text_layout_details = &self.text_layout_details(window);
13346 self.change_selections(Default::default(), window, cx, |s| {
13347 s.move_heads_with(|map, head, goal| {
13348 movement::up(map, head, goal, false, text_layout_details)
13349 })
13350 })
13351 }
13352
13353 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13354 self.take_rename(true, window, cx);
13355
13356 if self.mode.is_single_line() {
13357 cx.propagate();
13358 return;
13359 }
13360
13361 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13362
13363 let text_layout_details = &self.text_layout_details(window);
13364 let selection_count = self.selections.count();
13365 let first_selection = self.selections.first_anchor();
13366
13367 self.change_selections(Default::default(), window, cx, |s| {
13368 s.move_with(|map, selection| {
13369 if !selection.is_empty() {
13370 selection.goal = SelectionGoal::None;
13371 }
13372 let (cursor, goal) = movement::down(
13373 map,
13374 selection.end,
13375 selection.goal,
13376 false,
13377 text_layout_details,
13378 );
13379 selection.collapse_to(cursor, goal);
13380 });
13381 });
13382
13383 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13384 {
13385 cx.propagate();
13386 }
13387 }
13388
13389 pub fn select_page_down(
13390 &mut self,
13391 _: &SelectPageDown,
13392 window: &mut Window,
13393 cx: &mut Context<Self>,
13394 ) {
13395 let Some(row_count) = self.visible_row_count() else {
13396 return;
13397 };
13398
13399 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13400
13401 let text_layout_details = &self.text_layout_details(window);
13402
13403 self.change_selections(Default::default(), window, cx, |s| {
13404 s.move_heads_with(|map, head, goal| {
13405 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13406 })
13407 })
13408 }
13409
13410 pub fn move_page_down(
13411 &mut self,
13412 action: &MovePageDown,
13413 window: &mut Window,
13414 cx: &mut Context<Self>,
13415 ) {
13416 if self.take_rename(true, window, cx).is_some() {
13417 return;
13418 }
13419
13420 if self
13421 .context_menu
13422 .borrow_mut()
13423 .as_mut()
13424 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13425 .unwrap_or(false)
13426 {
13427 return;
13428 }
13429
13430 if matches!(self.mode, EditorMode::SingleLine) {
13431 cx.propagate();
13432 return;
13433 }
13434
13435 let Some(row_count) = self.visible_row_count() else {
13436 return;
13437 };
13438
13439 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13440
13441 let effects = if action.center_cursor {
13442 SelectionEffects::scroll(Autoscroll::center())
13443 } else {
13444 SelectionEffects::default()
13445 };
13446
13447 let text_layout_details = &self.text_layout_details(window);
13448 self.change_selections(effects, window, cx, |s| {
13449 s.move_with(|map, selection| {
13450 if !selection.is_empty() {
13451 selection.goal = SelectionGoal::None;
13452 }
13453 let (cursor, goal) = movement::down_by_rows(
13454 map,
13455 selection.end,
13456 row_count,
13457 selection.goal,
13458 false,
13459 text_layout_details,
13460 );
13461 selection.collapse_to(cursor, goal);
13462 });
13463 });
13464 }
13465
13466 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13467 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13468 let text_layout_details = &self.text_layout_details(window);
13469 self.change_selections(Default::default(), window, cx, |s| {
13470 s.move_heads_with(|map, head, goal| {
13471 movement::down(map, head, goal, false, text_layout_details)
13472 })
13473 });
13474 }
13475
13476 pub fn context_menu_first(
13477 &mut self,
13478 _: &ContextMenuFirst,
13479 window: &mut Window,
13480 cx: &mut Context<Self>,
13481 ) {
13482 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13483 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13484 }
13485 }
13486
13487 pub fn context_menu_prev(
13488 &mut self,
13489 _: &ContextMenuPrevious,
13490 window: &mut Window,
13491 cx: &mut Context<Self>,
13492 ) {
13493 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13494 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13495 }
13496 }
13497
13498 pub fn context_menu_next(
13499 &mut self,
13500 _: &ContextMenuNext,
13501 window: &mut Window,
13502 cx: &mut Context<Self>,
13503 ) {
13504 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13505 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13506 }
13507 }
13508
13509 pub fn context_menu_last(
13510 &mut self,
13511 _: &ContextMenuLast,
13512 window: &mut Window,
13513 cx: &mut Context<Self>,
13514 ) {
13515 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13516 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13517 }
13518 }
13519
13520 pub fn signature_help_prev(
13521 &mut self,
13522 _: &SignatureHelpPrevious,
13523 _: &mut Window,
13524 cx: &mut Context<Self>,
13525 ) {
13526 if let Some(popover) = self.signature_help_state.popover_mut() {
13527 if popover.current_signature == 0 {
13528 popover.current_signature = popover.signatures.len() - 1;
13529 } else {
13530 popover.current_signature -= 1;
13531 }
13532 cx.notify();
13533 }
13534 }
13535
13536 pub fn signature_help_next(
13537 &mut self,
13538 _: &SignatureHelpNext,
13539 _: &mut Window,
13540 cx: &mut Context<Self>,
13541 ) {
13542 if let Some(popover) = self.signature_help_state.popover_mut() {
13543 if popover.current_signature + 1 == popover.signatures.len() {
13544 popover.current_signature = 0;
13545 } else {
13546 popover.current_signature += 1;
13547 }
13548 cx.notify();
13549 }
13550 }
13551
13552 pub fn move_to_previous_word_start(
13553 &mut self,
13554 _: &MoveToPreviousWordStart,
13555 window: &mut Window,
13556 cx: &mut Context<Self>,
13557 ) {
13558 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13559 self.change_selections(Default::default(), window, cx, |s| {
13560 s.move_cursors_with(|map, head, _| {
13561 (
13562 movement::previous_word_start(map, head),
13563 SelectionGoal::None,
13564 )
13565 });
13566 })
13567 }
13568
13569 pub fn move_to_previous_subword_start(
13570 &mut self,
13571 _: &MoveToPreviousSubwordStart,
13572 window: &mut Window,
13573 cx: &mut Context<Self>,
13574 ) {
13575 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13576 self.change_selections(Default::default(), window, cx, |s| {
13577 s.move_cursors_with(|map, head, _| {
13578 (
13579 movement::previous_subword_start(map, head),
13580 SelectionGoal::None,
13581 )
13582 });
13583 })
13584 }
13585
13586 pub fn select_to_previous_word_start(
13587 &mut self,
13588 _: &SelectToPreviousWordStart,
13589 window: &mut Window,
13590 cx: &mut Context<Self>,
13591 ) {
13592 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13593 self.change_selections(Default::default(), window, cx, |s| {
13594 s.move_heads_with(|map, head, _| {
13595 (
13596 movement::previous_word_start(map, head),
13597 SelectionGoal::None,
13598 )
13599 });
13600 })
13601 }
13602
13603 pub fn select_to_previous_subword_start(
13604 &mut self,
13605 _: &SelectToPreviousSubwordStart,
13606 window: &mut Window,
13607 cx: &mut Context<Self>,
13608 ) {
13609 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13610 self.change_selections(Default::default(), window, cx, |s| {
13611 s.move_heads_with(|map, head, _| {
13612 (
13613 movement::previous_subword_start(map, head),
13614 SelectionGoal::None,
13615 )
13616 });
13617 })
13618 }
13619
13620 pub fn delete_to_previous_word_start(
13621 &mut self,
13622 action: &DeleteToPreviousWordStart,
13623 window: &mut Window,
13624 cx: &mut Context<Self>,
13625 ) {
13626 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13627 self.transact(window, cx, |this, window, cx| {
13628 this.select_autoclose_pair(window, cx);
13629 this.change_selections(Default::default(), window, cx, |s| {
13630 s.move_with(|map, selection| {
13631 if selection.is_empty() {
13632 let mut cursor = if action.ignore_newlines {
13633 movement::previous_word_start(map, selection.head())
13634 } else {
13635 movement::previous_word_start_or_newline(map, selection.head())
13636 };
13637 cursor = movement::adjust_greedy_deletion(
13638 map,
13639 selection.head(),
13640 cursor,
13641 action.ignore_brackets,
13642 );
13643 selection.set_head(cursor, SelectionGoal::None);
13644 }
13645 });
13646 });
13647 this.insert("", window, cx);
13648 });
13649 }
13650
13651 pub fn delete_to_previous_subword_start(
13652 &mut self,
13653 _: &DeleteToPreviousSubwordStart,
13654 window: &mut Window,
13655 cx: &mut Context<Self>,
13656 ) {
13657 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13658 self.transact(window, cx, |this, window, cx| {
13659 this.select_autoclose_pair(window, cx);
13660 this.change_selections(Default::default(), window, cx, |s| {
13661 s.move_with(|map, selection| {
13662 if selection.is_empty() {
13663 let mut cursor = movement::previous_subword_start(map, selection.head());
13664 cursor =
13665 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13666 selection.set_head(cursor, SelectionGoal::None);
13667 }
13668 });
13669 });
13670 this.insert("", window, cx);
13671 });
13672 }
13673
13674 pub fn move_to_next_word_end(
13675 &mut self,
13676 _: &MoveToNextWordEnd,
13677 window: &mut Window,
13678 cx: &mut Context<Self>,
13679 ) {
13680 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13681 self.change_selections(Default::default(), window, cx, |s| {
13682 s.move_cursors_with(|map, head, _| {
13683 (movement::next_word_end(map, head), SelectionGoal::None)
13684 });
13685 })
13686 }
13687
13688 pub fn move_to_next_subword_end(
13689 &mut self,
13690 _: &MoveToNextSubwordEnd,
13691 window: &mut Window,
13692 cx: &mut Context<Self>,
13693 ) {
13694 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13695 self.change_selections(Default::default(), window, cx, |s| {
13696 s.move_cursors_with(|map, head, _| {
13697 (movement::next_subword_end(map, head), SelectionGoal::None)
13698 });
13699 })
13700 }
13701
13702 pub fn select_to_next_word_end(
13703 &mut self,
13704 _: &SelectToNextWordEnd,
13705 window: &mut Window,
13706 cx: &mut Context<Self>,
13707 ) {
13708 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13709 self.change_selections(Default::default(), window, cx, |s| {
13710 s.move_heads_with(|map, head, _| {
13711 (movement::next_word_end(map, head), SelectionGoal::None)
13712 });
13713 })
13714 }
13715
13716 pub fn select_to_next_subword_end(
13717 &mut self,
13718 _: &SelectToNextSubwordEnd,
13719 window: &mut Window,
13720 cx: &mut Context<Self>,
13721 ) {
13722 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13723 self.change_selections(Default::default(), window, cx, |s| {
13724 s.move_heads_with(|map, head, _| {
13725 (movement::next_subword_end(map, head), SelectionGoal::None)
13726 });
13727 })
13728 }
13729
13730 pub fn delete_to_next_word_end(
13731 &mut self,
13732 action: &DeleteToNextWordEnd,
13733 window: &mut Window,
13734 cx: &mut Context<Self>,
13735 ) {
13736 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13737 self.transact(window, cx, |this, window, cx| {
13738 this.change_selections(Default::default(), window, cx, |s| {
13739 s.move_with(|map, selection| {
13740 if selection.is_empty() {
13741 let mut cursor = if action.ignore_newlines {
13742 movement::next_word_end(map, selection.head())
13743 } else {
13744 movement::next_word_end_or_newline(map, selection.head())
13745 };
13746 cursor = movement::adjust_greedy_deletion(
13747 map,
13748 selection.head(),
13749 cursor,
13750 action.ignore_brackets,
13751 );
13752 selection.set_head(cursor, SelectionGoal::None);
13753 }
13754 });
13755 });
13756 this.insert("", window, cx);
13757 });
13758 }
13759
13760 pub fn delete_to_next_subword_end(
13761 &mut self,
13762 _: &DeleteToNextSubwordEnd,
13763 window: &mut Window,
13764 cx: &mut Context<Self>,
13765 ) {
13766 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13767 self.transact(window, cx, |this, window, cx| {
13768 this.change_selections(Default::default(), window, cx, |s| {
13769 s.move_with(|map, selection| {
13770 if selection.is_empty() {
13771 let mut cursor = movement::next_subword_end(map, selection.head());
13772 cursor =
13773 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13774 selection.set_head(cursor, SelectionGoal::None);
13775 }
13776 });
13777 });
13778 this.insert("", window, cx);
13779 });
13780 }
13781
13782 pub fn move_to_beginning_of_line(
13783 &mut self,
13784 action: &MoveToBeginningOfLine,
13785 window: &mut Window,
13786 cx: &mut Context<Self>,
13787 ) {
13788 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13789 self.change_selections(Default::default(), window, cx, |s| {
13790 s.move_cursors_with(|map, head, _| {
13791 (
13792 movement::indented_line_beginning(
13793 map,
13794 head,
13795 action.stop_at_soft_wraps,
13796 action.stop_at_indent,
13797 ),
13798 SelectionGoal::None,
13799 )
13800 });
13801 })
13802 }
13803
13804 pub fn select_to_beginning_of_line(
13805 &mut self,
13806 action: &SelectToBeginningOfLine,
13807 window: &mut Window,
13808 cx: &mut Context<Self>,
13809 ) {
13810 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13811 self.change_selections(Default::default(), window, cx, |s| {
13812 s.move_heads_with(|map, head, _| {
13813 (
13814 movement::indented_line_beginning(
13815 map,
13816 head,
13817 action.stop_at_soft_wraps,
13818 action.stop_at_indent,
13819 ),
13820 SelectionGoal::None,
13821 )
13822 });
13823 });
13824 }
13825
13826 pub fn delete_to_beginning_of_line(
13827 &mut self,
13828 action: &DeleteToBeginningOfLine,
13829 window: &mut Window,
13830 cx: &mut Context<Self>,
13831 ) {
13832 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13833 self.transact(window, cx, |this, window, cx| {
13834 this.change_selections(Default::default(), window, cx, |s| {
13835 s.move_with(|_, selection| {
13836 selection.reversed = true;
13837 });
13838 });
13839
13840 this.select_to_beginning_of_line(
13841 &SelectToBeginningOfLine {
13842 stop_at_soft_wraps: false,
13843 stop_at_indent: action.stop_at_indent,
13844 },
13845 window,
13846 cx,
13847 );
13848 this.backspace(&Backspace, window, cx);
13849 });
13850 }
13851
13852 pub fn move_to_end_of_line(
13853 &mut self,
13854 action: &MoveToEndOfLine,
13855 window: &mut Window,
13856 cx: &mut Context<Self>,
13857 ) {
13858 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13859 self.change_selections(Default::default(), window, cx, |s| {
13860 s.move_cursors_with(|map, head, _| {
13861 (
13862 movement::line_end(map, head, action.stop_at_soft_wraps),
13863 SelectionGoal::None,
13864 )
13865 });
13866 })
13867 }
13868
13869 pub fn select_to_end_of_line(
13870 &mut self,
13871 action: &SelectToEndOfLine,
13872 window: &mut Window,
13873 cx: &mut Context<Self>,
13874 ) {
13875 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13876 self.change_selections(Default::default(), window, cx, |s| {
13877 s.move_heads_with(|map, head, _| {
13878 (
13879 movement::line_end(map, head, action.stop_at_soft_wraps),
13880 SelectionGoal::None,
13881 )
13882 });
13883 })
13884 }
13885
13886 pub fn delete_to_end_of_line(
13887 &mut self,
13888 _: &DeleteToEndOfLine,
13889 window: &mut Window,
13890 cx: &mut Context<Self>,
13891 ) {
13892 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13893 self.transact(window, cx, |this, window, cx| {
13894 this.select_to_end_of_line(
13895 &SelectToEndOfLine {
13896 stop_at_soft_wraps: false,
13897 },
13898 window,
13899 cx,
13900 );
13901 this.delete(&Delete, window, cx);
13902 });
13903 }
13904
13905 pub fn cut_to_end_of_line(
13906 &mut self,
13907 action: &CutToEndOfLine,
13908 window: &mut Window,
13909 cx: &mut Context<Self>,
13910 ) {
13911 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13912 self.transact(window, cx, |this, window, cx| {
13913 this.select_to_end_of_line(
13914 &SelectToEndOfLine {
13915 stop_at_soft_wraps: false,
13916 },
13917 window,
13918 cx,
13919 );
13920 if !action.stop_at_newlines {
13921 this.change_selections(Default::default(), window, cx, |s| {
13922 s.move_with(|_, sel| {
13923 if sel.is_empty() {
13924 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13925 }
13926 });
13927 });
13928 }
13929 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13930 let item = this.cut_common(false, window, cx);
13931 cx.write_to_clipboard(item);
13932 });
13933 }
13934
13935 pub fn move_to_start_of_paragraph(
13936 &mut self,
13937 _: &MoveToStartOfParagraph,
13938 window: &mut Window,
13939 cx: &mut Context<Self>,
13940 ) {
13941 if matches!(self.mode, EditorMode::SingleLine) {
13942 cx.propagate();
13943 return;
13944 }
13945 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13946 self.change_selections(Default::default(), window, cx, |s| {
13947 s.move_with(|map, selection| {
13948 selection.collapse_to(
13949 movement::start_of_paragraph(map, selection.head(), 1),
13950 SelectionGoal::None,
13951 )
13952 });
13953 })
13954 }
13955
13956 pub fn move_to_end_of_paragraph(
13957 &mut self,
13958 _: &MoveToEndOfParagraph,
13959 window: &mut Window,
13960 cx: &mut Context<Self>,
13961 ) {
13962 if matches!(self.mode, EditorMode::SingleLine) {
13963 cx.propagate();
13964 return;
13965 }
13966 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13967 self.change_selections(Default::default(), window, cx, |s| {
13968 s.move_with(|map, selection| {
13969 selection.collapse_to(
13970 movement::end_of_paragraph(map, selection.head(), 1),
13971 SelectionGoal::None,
13972 )
13973 });
13974 })
13975 }
13976
13977 pub fn select_to_start_of_paragraph(
13978 &mut self,
13979 _: &SelectToStartOfParagraph,
13980 window: &mut Window,
13981 cx: &mut Context<Self>,
13982 ) {
13983 if matches!(self.mode, EditorMode::SingleLine) {
13984 cx.propagate();
13985 return;
13986 }
13987 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13988 self.change_selections(Default::default(), window, cx, |s| {
13989 s.move_heads_with(|map, head, _| {
13990 (
13991 movement::start_of_paragraph(map, head, 1),
13992 SelectionGoal::None,
13993 )
13994 });
13995 })
13996 }
13997
13998 pub fn select_to_end_of_paragraph(
13999 &mut self,
14000 _: &SelectToEndOfParagraph,
14001 window: &mut Window,
14002 cx: &mut Context<Self>,
14003 ) {
14004 if matches!(self.mode, EditorMode::SingleLine) {
14005 cx.propagate();
14006 return;
14007 }
14008 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14009 self.change_selections(Default::default(), window, cx, |s| {
14010 s.move_heads_with(|map, head, _| {
14011 (
14012 movement::end_of_paragraph(map, head, 1),
14013 SelectionGoal::None,
14014 )
14015 });
14016 })
14017 }
14018
14019 pub fn move_to_start_of_excerpt(
14020 &mut self,
14021 _: &MoveToStartOfExcerpt,
14022 window: &mut Window,
14023 cx: &mut Context<Self>,
14024 ) {
14025 if matches!(self.mode, EditorMode::SingleLine) {
14026 cx.propagate();
14027 return;
14028 }
14029 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14030 self.change_selections(Default::default(), window, cx, |s| {
14031 s.move_with(|map, selection| {
14032 selection.collapse_to(
14033 movement::start_of_excerpt(
14034 map,
14035 selection.head(),
14036 workspace::searchable::Direction::Prev,
14037 ),
14038 SelectionGoal::None,
14039 )
14040 });
14041 })
14042 }
14043
14044 pub fn move_to_start_of_next_excerpt(
14045 &mut self,
14046 _: &MoveToStartOfNextExcerpt,
14047 window: &mut Window,
14048 cx: &mut Context<Self>,
14049 ) {
14050 if matches!(self.mode, EditorMode::SingleLine) {
14051 cx.propagate();
14052 return;
14053 }
14054
14055 self.change_selections(Default::default(), window, cx, |s| {
14056 s.move_with(|map, selection| {
14057 selection.collapse_to(
14058 movement::start_of_excerpt(
14059 map,
14060 selection.head(),
14061 workspace::searchable::Direction::Next,
14062 ),
14063 SelectionGoal::None,
14064 )
14065 });
14066 })
14067 }
14068
14069 pub fn move_to_end_of_excerpt(
14070 &mut self,
14071 _: &MoveToEndOfExcerpt,
14072 window: &mut Window,
14073 cx: &mut Context<Self>,
14074 ) {
14075 if matches!(self.mode, EditorMode::SingleLine) {
14076 cx.propagate();
14077 return;
14078 }
14079 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14080 self.change_selections(Default::default(), window, cx, |s| {
14081 s.move_with(|map, selection| {
14082 selection.collapse_to(
14083 movement::end_of_excerpt(
14084 map,
14085 selection.head(),
14086 workspace::searchable::Direction::Next,
14087 ),
14088 SelectionGoal::None,
14089 )
14090 });
14091 })
14092 }
14093
14094 pub fn move_to_end_of_previous_excerpt(
14095 &mut self,
14096 _: &MoveToEndOfPreviousExcerpt,
14097 window: &mut Window,
14098 cx: &mut Context<Self>,
14099 ) {
14100 if matches!(self.mode, EditorMode::SingleLine) {
14101 cx.propagate();
14102 return;
14103 }
14104 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14105 self.change_selections(Default::default(), window, cx, |s| {
14106 s.move_with(|map, selection| {
14107 selection.collapse_to(
14108 movement::end_of_excerpt(
14109 map,
14110 selection.head(),
14111 workspace::searchable::Direction::Prev,
14112 ),
14113 SelectionGoal::None,
14114 )
14115 });
14116 })
14117 }
14118
14119 pub fn select_to_start_of_excerpt(
14120 &mut self,
14121 _: &SelectToStartOfExcerpt,
14122 window: &mut Window,
14123 cx: &mut Context<Self>,
14124 ) {
14125 if matches!(self.mode, EditorMode::SingleLine) {
14126 cx.propagate();
14127 return;
14128 }
14129 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14130 self.change_selections(Default::default(), window, cx, |s| {
14131 s.move_heads_with(|map, head, _| {
14132 (
14133 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14134 SelectionGoal::None,
14135 )
14136 });
14137 })
14138 }
14139
14140 pub fn select_to_start_of_next_excerpt(
14141 &mut self,
14142 _: &SelectToStartOfNextExcerpt,
14143 window: &mut Window,
14144 cx: &mut Context<Self>,
14145 ) {
14146 if matches!(self.mode, EditorMode::SingleLine) {
14147 cx.propagate();
14148 return;
14149 }
14150 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14151 self.change_selections(Default::default(), window, cx, |s| {
14152 s.move_heads_with(|map, head, _| {
14153 (
14154 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14155 SelectionGoal::None,
14156 )
14157 });
14158 })
14159 }
14160
14161 pub fn select_to_end_of_excerpt(
14162 &mut self,
14163 _: &SelectToEndOfExcerpt,
14164 window: &mut Window,
14165 cx: &mut Context<Self>,
14166 ) {
14167 if matches!(self.mode, EditorMode::SingleLine) {
14168 cx.propagate();
14169 return;
14170 }
14171 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14172 self.change_selections(Default::default(), window, cx, |s| {
14173 s.move_heads_with(|map, head, _| {
14174 (
14175 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14176 SelectionGoal::None,
14177 )
14178 });
14179 })
14180 }
14181
14182 pub fn select_to_end_of_previous_excerpt(
14183 &mut self,
14184 _: &SelectToEndOfPreviousExcerpt,
14185 window: &mut Window,
14186 cx: &mut Context<Self>,
14187 ) {
14188 if matches!(self.mode, EditorMode::SingleLine) {
14189 cx.propagate();
14190 return;
14191 }
14192 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14193 self.change_selections(Default::default(), window, cx, |s| {
14194 s.move_heads_with(|map, head, _| {
14195 (
14196 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14197 SelectionGoal::None,
14198 )
14199 });
14200 })
14201 }
14202
14203 pub fn move_to_beginning(
14204 &mut self,
14205 _: &MoveToBeginning,
14206 window: &mut Window,
14207 cx: &mut Context<Self>,
14208 ) {
14209 if matches!(self.mode, EditorMode::SingleLine) {
14210 cx.propagate();
14211 return;
14212 }
14213 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14214 self.change_selections(Default::default(), window, cx, |s| {
14215 s.select_ranges(vec![0..0]);
14216 });
14217 }
14218
14219 pub fn select_to_beginning(
14220 &mut self,
14221 _: &SelectToBeginning,
14222 window: &mut Window,
14223 cx: &mut Context<Self>,
14224 ) {
14225 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14226 selection.set_head(Point::zero(), SelectionGoal::None);
14227 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14228 self.change_selections(Default::default(), window, cx, |s| {
14229 s.select(vec![selection]);
14230 });
14231 }
14232
14233 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14234 if matches!(self.mode, EditorMode::SingleLine) {
14235 cx.propagate();
14236 return;
14237 }
14238 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14239 let cursor = self.buffer.read(cx).read(cx).len();
14240 self.change_selections(Default::default(), window, cx, |s| {
14241 s.select_ranges(vec![cursor..cursor])
14242 });
14243 }
14244
14245 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14246 self.nav_history = nav_history;
14247 }
14248
14249 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14250 self.nav_history.as_ref()
14251 }
14252
14253 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14254 self.push_to_nav_history(
14255 self.selections.newest_anchor().head(),
14256 None,
14257 false,
14258 true,
14259 cx,
14260 );
14261 }
14262
14263 fn push_to_nav_history(
14264 &mut self,
14265 cursor_anchor: Anchor,
14266 new_position: Option<Point>,
14267 is_deactivate: bool,
14268 always: bool,
14269 cx: &mut Context<Self>,
14270 ) {
14271 if let Some(nav_history) = self.nav_history.as_mut() {
14272 let buffer = self.buffer.read(cx).read(cx);
14273 let cursor_position = cursor_anchor.to_point(&buffer);
14274 let scroll_state = self.scroll_manager.anchor();
14275 let scroll_top_row = scroll_state.top_row(&buffer);
14276 drop(buffer);
14277
14278 if let Some(new_position) = new_position {
14279 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14280 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14281 return;
14282 }
14283 }
14284
14285 nav_history.push(
14286 Some(NavigationData {
14287 cursor_anchor,
14288 cursor_position,
14289 scroll_anchor: scroll_state,
14290 scroll_top_row,
14291 }),
14292 cx,
14293 );
14294 cx.emit(EditorEvent::PushedToNavHistory {
14295 anchor: cursor_anchor,
14296 is_deactivate,
14297 })
14298 }
14299 }
14300
14301 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14302 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14303 let buffer = self.buffer.read(cx).snapshot(cx);
14304 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14305 selection.set_head(buffer.len(), SelectionGoal::None);
14306 self.change_selections(Default::default(), window, cx, |s| {
14307 s.select(vec![selection]);
14308 });
14309 }
14310
14311 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14312 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14313 let end = self.buffer.read(cx).read(cx).len();
14314 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14315 s.select_ranges(vec![0..end]);
14316 });
14317 }
14318
14319 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14320 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14321 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14322 let mut selections = self.selections.all::<Point>(&display_map);
14323 let max_point = display_map.buffer_snapshot().max_point();
14324 for selection in &mut selections {
14325 let rows = selection.spanned_rows(true, &display_map);
14326 selection.start = Point::new(rows.start.0, 0);
14327 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14328 selection.reversed = false;
14329 }
14330 self.change_selections(Default::default(), window, cx, |s| {
14331 s.select(selections);
14332 });
14333 }
14334
14335 pub fn split_selection_into_lines(
14336 &mut self,
14337 action: &SplitSelectionIntoLines,
14338 window: &mut Window,
14339 cx: &mut Context<Self>,
14340 ) {
14341 let selections = self
14342 .selections
14343 .all::<Point>(&self.display_snapshot(cx))
14344 .into_iter()
14345 .map(|selection| selection.start..selection.end)
14346 .collect::<Vec<_>>();
14347 self.unfold_ranges(&selections, true, true, cx);
14348
14349 let mut new_selection_ranges = Vec::new();
14350 {
14351 let buffer = self.buffer.read(cx).read(cx);
14352 for selection in selections {
14353 for row in selection.start.row..selection.end.row {
14354 let line_start = Point::new(row, 0);
14355 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14356
14357 if action.keep_selections {
14358 // Keep the selection range for each line
14359 let selection_start = if row == selection.start.row {
14360 selection.start
14361 } else {
14362 line_start
14363 };
14364 new_selection_ranges.push(selection_start..line_end);
14365 } else {
14366 // Collapse to cursor at end of line
14367 new_selection_ranges.push(line_end..line_end);
14368 }
14369 }
14370
14371 let is_multiline_selection = selection.start.row != selection.end.row;
14372 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14373 // so this action feels more ergonomic when paired with other selection operations
14374 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14375 if !should_skip_last {
14376 if action.keep_selections {
14377 if is_multiline_selection {
14378 let line_start = Point::new(selection.end.row, 0);
14379 new_selection_ranges.push(line_start..selection.end);
14380 } else {
14381 new_selection_ranges.push(selection.start..selection.end);
14382 }
14383 } else {
14384 new_selection_ranges.push(selection.end..selection.end);
14385 }
14386 }
14387 }
14388 }
14389 self.change_selections(Default::default(), window, cx, |s| {
14390 s.select_ranges(new_selection_ranges);
14391 });
14392 }
14393
14394 pub fn add_selection_above(
14395 &mut self,
14396 action: &AddSelectionAbove,
14397 window: &mut Window,
14398 cx: &mut Context<Self>,
14399 ) {
14400 self.add_selection(true, action.skip_soft_wrap, window, cx);
14401 }
14402
14403 pub fn add_selection_below(
14404 &mut self,
14405 action: &AddSelectionBelow,
14406 window: &mut Window,
14407 cx: &mut Context<Self>,
14408 ) {
14409 self.add_selection(false, action.skip_soft_wrap, window, cx);
14410 }
14411
14412 fn add_selection(
14413 &mut self,
14414 above: bool,
14415 skip_soft_wrap: bool,
14416 window: &mut Window,
14417 cx: &mut Context<Self>,
14418 ) {
14419 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14420
14421 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14422 let all_selections = self.selections.all::<Point>(&display_map);
14423 let text_layout_details = self.text_layout_details(window);
14424
14425 let (mut columnar_selections, new_selections_to_columnarize) = {
14426 if let Some(state) = self.add_selections_state.as_ref() {
14427 let columnar_selection_ids: HashSet<_> = state
14428 .groups
14429 .iter()
14430 .flat_map(|group| group.stack.iter())
14431 .copied()
14432 .collect();
14433
14434 all_selections
14435 .into_iter()
14436 .partition(|s| columnar_selection_ids.contains(&s.id))
14437 } else {
14438 (Vec::new(), all_selections)
14439 }
14440 };
14441
14442 let mut state = self
14443 .add_selections_state
14444 .take()
14445 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14446
14447 for selection in new_selections_to_columnarize {
14448 let range = selection.display_range(&display_map).sorted();
14449 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14450 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14451 let positions = start_x.min(end_x)..start_x.max(end_x);
14452 let mut stack = Vec::new();
14453 for row in range.start.row().0..=range.end.row().0 {
14454 if let Some(selection) = self.selections.build_columnar_selection(
14455 &display_map,
14456 DisplayRow(row),
14457 &positions,
14458 selection.reversed,
14459 &text_layout_details,
14460 ) {
14461 stack.push(selection.id);
14462 columnar_selections.push(selection);
14463 }
14464 }
14465 if !stack.is_empty() {
14466 if above {
14467 stack.reverse();
14468 }
14469 state.groups.push(AddSelectionsGroup { above, stack });
14470 }
14471 }
14472
14473 let mut final_selections = Vec::new();
14474 let end_row = if above {
14475 DisplayRow(0)
14476 } else {
14477 display_map.max_point().row()
14478 };
14479
14480 let mut last_added_item_per_group = HashMap::default();
14481 for group in state.groups.iter_mut() {
14482 if let Some(last_id) = group.stack.last() {
14483 last_added_item_per_group.insert(*last_id, group);
14484 }
14485 }
14486
14487 for selection in columnar_selections {
14488 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14489 if above == group.above {
14490 let range = selection.display_range(&display_map).sorted();
14491 debug_assert_eq!(range.start.row(), range.end.row());
14492 let mut row = range.start.row();
14493 let positions =
14494 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14495 Pixels::from(start)..Pixels::from(end)
14496 } else {
14497 let start_x =
14498 display_map.x_for_display_point(range.start, &text_layout_details);
14499 let end_x =
14500 display_map.x_for_display_point(range.end, &text_layout_details);
14501 start_x.min(end_x)..start_x.max(end_x)
14502 };
14503
14504 let mut maybe_new_selection = None;
14505 let direction = if above { -1 } else { 1 };
14506
14507 while row != end_row {
14508 if skip_soft_wrap {
14509 row = display_map
14510 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14511 .row();
14512 } else if above {
14513 row.0 -= 1;
14514 } else {
14515 row.0 += 1;
14516 }
14517
14518 if let Some(new_selection) = self.selections.build_columnar_selection(
14519 &display_map,
14520 row,
14521 &positions,
14522 selection.reversed,
14523 &text_layout_details,
14524 ) {
14525 maybe_new_selection = Some(new_selection);
14526 break;
14527 }
14528 }
14529
14530 if let Some(new_selection) = maybe_new_selection {
14531 group.stack.push(new_selection.id);
14532 if above {
14533 final_selections.push(new_selection);
14534 final_selections.push(selection);
14535 } else {
14536 final_selections.push(selection);
14537 final_selections.push(new_selection);
14538 }
14539 } else {
14540 final_selections.push(selection);
14541 }
14542 } else {
14543 group.stack.pop();
14544 }
14545 } else {
14546 final_selections.push(selection);
14547 }
14548 }
14549
14550 self.change_selections(Default::default(), window, cx, |s| {
14551 s.select(final_selections);
14552 });
14553
14554 let final_selection_ids: HashSet<_> = self
14555 .selections
14556 .all::<Point>(&display_map)
14557 .iter()
14558 .map(|s| s.id)
14559 .collect();
14560 state.groups.retain_mut(|group| {
14561 // selections might get merged above so we remove invalid items from stacks
14562 group.stack.retain(|id| final_selection_ids.contains(id));
14563
14564 // single selection in stack can be treated as initial state
14565 group.stack.len() > 1
14566 });
14567
14568 if !state.groups.is_empty() {
14569 self.add_selections_state = Some(state);
14570 }
14571 }
14572
14573 fn select_match_ranges(
14574 &mut self,
14575 range: Range<usize>,
14576 reversed: bool,
14577 replace_newest: bool,
14578 auto_scroll: Option<Autoscroll>,
14579 window: &mut Window,
14580 cx: &mut Context<Editor>,
14581 ) {
14582 self.unfold_ranges(
14583 std::slice::from_ref(&range),
14584 false,
14585 auto_scroll.is_some(),
14586 cx,
14587 );
14588 let effects = if let Some(scroll) = auto_scroll {
14589 SelectionEffects::scroll(scroll)
14590 } else {
14591 SelectionEffects::no_scroll()
14592 };
14593 self.change_selections(effects, window, cx, |s| {
14594 if replace_newest {
14595 s.delete(s.newest_anchor().id);
14596 }
14597 if reversed {
14598 s.insert_range(range.end..range.start);
14599 } else {
14600 s.insert_range(range);
14601 }
14602 });
14603 }
14604
14605 pub fn select_next_match_internal(
14606 &mut self,
14607 display_map: &DisplaySnapshot,
14608 replace_newest: bool,
14609 autoscroll: Option<Autoscroll>,
14610 window: &mut Window,
14611 cx: &mut Context<Self>,
14612 ) -> Result<()> {
14613 let buffer = display_map.buffer_snapshot();
14614 let mut selections = self.selections.all::<usize>(&display_map);
14615 if let Some(mut select_next_state) = self.select_next_state.take() {
14616 let query = &select_next_state.query;
14617 if !select_next_state.done {
14618 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14619 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14620 let mut next_selected_range = None;
14621
14622 let bytes_after_last_selection =
14623 buffer.bytes_in_range(last_selection.end..buffer.len());
14624 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14625 let query_matches = query
14626 .stream_find_iter(bytes_after_last_selection)
14627 .map(|result| (last_selection.end, result))
14628 .chain(
14629 query
14630 .stream_find_iter(bytes_before_first_selection)
14631 .map(|result| (0, result)),
14632 );
14633
14634 for (start_offset, query_match) in query_matches {
14635 let query_match = query_match.unwrap(); // can only fail due to I/O
14636 let offset_range =
14637 start_offset + query_match.start()..start_offset + query_match.end();
14638
14639 if !select_next_state.wordwise
14640 || (!buffer.is_inside_word(offset_range.start, None)
14641 && !buffer.is_inside_word(offset_range.end, None))
14642 {
14643 let idx = selections
14644 .partition_point(|selection| selection.end <= offset_range.start);
14645 let overlaps = selections
14646 .get(idx)
14647 .map_or(false, |selection| selection.start < offset_range.end);
14648
14649 if !overlaps {
14650 next_selected_range = Some(offset_range);
14651 break;
14652 }
14653 }
14654 }
14655
14656 if let Some(next_selected_range) = next_selected_range {
14657 self.select_match_ranges(
14658 next_selected_range,
14659 last_selection.reversed,
14660 replace_newest,
14661 autoscroll,
14662 window,
14663 cx,
14664 );
14665 } else {
14666 select_next_state.done = true;
14667 }
14668 }
14669
14670 self.select_next_state = Some(select_next_state);
14671 } else {
14672 let mut only_carets = true;
14673 let mut same_text_selected = true;
14674 let mut selected_text = None;
14675
14676 let mut selections_iter = selections.iter().peekable();
14677 while let Some(selection) = selections_iter.next() {
14678 if selection.start != selection.end {
14679 only_carets = false;
14680 }
14681
14682 if same_text_selected {
14683 if selected_text.is_none() {
14684 selected_text =
14685 Some(buffer.text_for_range(selection.range()).collect::<String>());
14686 }
14687
14688 if let Some(next_selection) = selections_iter.peek() {
14689 if next_selection.range().len() == selection.range().len() {
14690 let next_selected_text = buffer
14691 .text_for_range(next_selection.range())
14692 .collect::<String>();
14693 if Some(next_selected_text) != selected_text {
14694 same_text_selected = false;
14695 selected_text = None;
14696 }
14697 } else {
14698 same_text_selected = false;
14699 selected_text = None;
14700 }
14701 }
14702 }
14703 }
14704
14705 if only_carets {
14706 for selection in &mut selections {
14707 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14708 selection.start = word_range.start;
14709 selection.end = word_range.end;
14710 selection.goal = SelectionGoal::None;
14711 selection.reversed = false;
14712 self.select_match_ranges(
14713 selection.start..selection.end,
14714 selection.reversed,
14715 replace_newest,
14716 autoscroll,
14717 window,
14718 cx,
14719 );
14720 }
14721
14722 if selections.len() == 1 {
14723 let selection = selections
14724 .last()
14725 .expect("ensured that there's only one selection");
14726 let query = buffer
14727 .text_for_range(selection.start..selection.end)
14728 .collect::<String>();
14729 let is_empty = query.is_empty();
14730 let select_state = SelectNextState {
14731 query: self.build_query(&[query], cx)?,
14732 wordwise: true,
14733 done: is_empty,
14734 };
14735 self.select_next_state = Some(select_state);
14736 } else {
14737 self.select_next_state = None;
14738 }
14739 } else if let Some(selected_text) = selected_text {
14740 self.select_next_state = Some(SelectNextState {
14741 query: self.build_query(&[selected_text], cx)?,
14742 wordwise: false,
14743 done: false,
14744 });
14745 self.select_next_match_internal(
14746 display_map,
14747 replace_newest,
14748 autoscroll,
14749 window,
14750 cx,
14751 )?;
14752 }
14753 }
14754 Ok(())
14755 }
14756
14757 pub fn select_all_matches(
14758 &mut self,
14759 _action: &SelectAllMatches,
14760 window: &mut Window,
14761 cx: &mut Context<Self>,
14762 ) -> Result<()> {
14763 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14764
14765 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14766
14767 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14768 let Some(select_next_state) = self.select_next_state.as_mut() else {
14769 return Ok(());
14770 };
14771 if select_next_state.done {
14772 return Ok(());
14773 }
14774
14775 let mut new_selections = Vec::new();
14776
14777 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14778 let buffer = display_map.buffer_snapshot();
14779 let query_matches = select_next_state
14780 .query
14781 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14782
14783 for query_match in query_matches.into_iter() {
14784 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14785 let offset_range = if reversed {
14786 query_match.end()..query_match.start()
14787 } else {
14788 query_match.start()..query_match.end()
14789 };
14790
14791 if !select_next_state.wordwise
14792 || (!buffer.is_inside_word(offset_range.start, None)
14793 && !buffer.is_inside_word(offset_range.end, None))
14794 {
14795 new_selections.push(offset_range.start..offset_range.end);
14796 }
14797 }
14798
14799 select_next_state.done = true;
14800
14801 if new_selections.is_empty() {
14802 log::error!("bug: new_selections is empty in select_all_matches");
14803 return Ok(());
14804 }
14805
14806 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14807 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14808 selections.select_ranges(new_selections)
14809 });
14810
14811 Ok(())
14812 }
14813
14814 pub fn select_next(
14815 &mut self,
14816 action: &SelectNext,
14817 window: &mut Window,
14818 cx: &mut Context<Self>,
14819 ) -> Result<()> {
14820 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14821 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14822 self.select_next_match_internal(
14823 &display_map,
14824 action.replace_newest,
14825 Some(Autoscroll::newest()),
14826 window,
14827 cx,
14828 )?;
14829 Ok(())
14830 }
14831
14832 pub fn select_previous(
14833 &mut self,
14834 action: &SelectPrevious,
14835 window: &mut Window,
14836 cx: &mut Context<Self>,
14837 ) -> Result<()> {
14838 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14839 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14840 let buffer = display_map.buffer_snapshot();
14841 let mut selections = self.selections.all::<usize>(&display_map);
14842 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14843 let query = &select_prev_state.query;
14844 if !select_prev_state.done {
14845 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14846 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14847 let mut next_selected_range = None;
14848 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14849 let bytes_before_last_selection =
14850 buffer.reversed_bytes_in_range(0..last_selection.start);
14851 let bytes_after_first_selection =
14852 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14853 let query_matches = query
14854 .stream_find_iter(bytes_before_last_selection)
14855 .map(|result| (last_selection.start, result))
14856 .chain(
14857 query
14858 .stream_find_iter(bytes_after_first_selection)
14859 .map(|result| (buffer.len(), result)),
14860 );
14861 for (end_offset, query_match) in query_matches {
14862 let query_match = query_match.unwrap(); // can only fail due to I/O
14863 let offset_range =
14864 end_offset - query_match.end()..end_offset - query_match.start();
14865
14866 if !select_prev_state.wordwise
14867 || (!buffer.is_inside_word(offset_range.start, None)
14868 && !buffer.is_inside_word(offset_range.end, None))
14869 {
14870 next_selected_range = Some(offset_range);
14871 break;
14872 }
14873 }
14874
14875 if let Some(next_selected_range) = next_selected_range {
14876 self.select_match_ranges(
14877 next_selected_range,
14878 last_selection.reversed,
14879 action.replace_newest,
14880 Some(Autoscroll::newest()),
14881 window,
14882 cx,
14883 );
14884 } else {
14885 select_prev_state.done = true;
14886 }
14887 }
14888
14889 self.select_prev_state = Some(select_prev_state);
14890 } else {
14891 let mut only_carets = true;
14892 let mut same_text_selected = true;
14893 let mut selected_text = None;
14894
14895 let mut selections_iter = selections.iter().peekable();
14896 while let Some(selection) = selections_iter.next() {
14897 if selection.start != selection.end {
14898 only_carets = false;
14899 }
14900
14901 if same_text_selected {
14902 if selected_text.is_none() {
14903 selected_text =
14904 Some(buffer.text_for_range(selection.range()).collect::<String>());
14905 }
14906
14907 if let Some(next_selection) = selections_iter.peek() {
14908 if next_selection.range().len() == selection.range().len() {
14909 let next_selected_text = buffer
14910 .text_for_range(next_selection.range())
14911 .collect::<String>();
14912 if Some(next_selected_text) != selected_text {
14913 same_text_selected = false;
14914 selected_text = None;
14915 }
14916 } else {
14917 same_text_selected = false;
14918 selected_text = None;
14919 }
14920 }
14921 }
14922 }
14923
14924 if only_carets {
14925 for selection in &mut selections {
14926 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14927 selection.start = word_range.start;
14928 selection.end = word_range.end;
14929 selection.goal = SelectionGoal::None;
14930 selection.reversed = false;
14931 self.select_match_ranges(
14932 selection.start..selection.end,
14933 selection.reversed,
14934 action.replace_newest,
14935 Some(Autoscroll::newest()),
14936 window,
14937 cx,
14938 );
14939 }
14940 if selections.len() == 1 {
14941 let selection = selections
14942 .last()
14943 .expect("ensured that there's only one selection");
14944 let query = buffer
14945 .text_for_range(selection.start..selection.end)
14946 .collect::<String>();
14947 let is_empty = query.is_empty();
14948 let select_state = SelectNextState {
14949 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
14950 wordwise: true,
14951 done: is_empty,
14952 };
14953 self.select_prev_state = Some(select_state);
14954 } else {
14955 self.select_prev_state = None;
14956 }
14957 } else if let Some(selected_text) = selected_text {
14958 self.select_prev_state = Some(SelectNextState {
14959 query: self
14960 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
14961 wordwise: false,
14962 done: false,
14963 });
14964 self.select_previous(action, window, cx)?;
14965 }
14966 }
14967 Ok(())
14968 }
14969
14970 /// Builds an `AhoCorasick` automaton from the provided patterns, while
14971 /// setting the case sensitivity based on the global
14972 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
14973 /// editor's settings.
14974 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
14975 where
14976 I: IntoIterator<Item = P>,
14977 P: AsRef<[u8]>,
14978 {
14979 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
14980 || EditorSettings::get_global(cx).search.case_sensitive,
14981 |value| value,
14982 );
14983
14984 let mut builder = AhoCorasickBuilder::new();
14985 builder.ascii_case_insensitive(!case_sensitive);
14986 builder.build(patterns)
14987 }
14988
14989 pub fn find_next_match(
14990 &mut self,
14991 _: &FindNextMatch,
14992 window: &mut Window,
14993 cx: &mut Context<Self>,
14994 ) -> Result<()> {
14995 let selections = self.selections.disjoint_anchors_arc();
14996 match selections.first() {
14997 Some(first) if selections.len() >= 2 => {
14998 self.change_selections(Default::default(), window, cx, |s| {
14999 s.select_ranges([first.range()]);
15000 });
15001 }
15002 _ => self.select_next(
15003 &SelectNext {
15004 replace_newest: true,
15005 },
15006 window,
15007 cx,
15008 )?,
15009 }
15010 Ok(())
15011 }
15012
15013 pub fn find_previous_match(
15014 &mut self,
15015 _: &FindPreviousMatch,
15016 window: &mut Window,
15017 cx: &mut Context<Self>,
15018 ) -> Result<()> {
15019 let selections = self.selections.disjoint_anchors_arc();
15020 match selections.last() {
15021 Some(last) if selections.len() >= 2 => {
15022 self.change_selections(Default::default(), window, cx, |s| {
15023 s.select_ranges([last.range()]);
15024 });
15025 }
15026 _ => self.select_previous(
15027 &SelectPrevious {
15028 replace_newest: true,
15029 },
15030 window,
15031 cx,
15032 )?,
15033 }
15034 Ok(())
15035 }
15036
15037 pub fn toggle_comments(
15038 &mut self,
15039 action: &ToggleComments,
15040 window: &mut Window,
15041 cx: &mut Context<Self>,
15042 ) {
15043 if self.read_only(cx) {
15044 return;
15045 }
15046 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15047 let text_layout_details = &self.text_layout_details(window);
15048 self.transact(window, cx, |this, window, cx| {
15049 let mut selections = this
15050 .selections
15051 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15052 let mut edits = Vec::new();
15053 let mut selection_edit_ranges = Vec::new();
15054 let mut last_toggled_row = None;
15055 let snapshot = this.buffer.read(cx).read(cx);
15056 let empty_str: Arc<str> = Arc::default();
15057 let mut suffixes_inserted = Vec::new();
15058 let ignore_indent = action.ignore_indent;
15059
15060 fn comment_prefix_range(
15061 snapshot: &MultiBufferSnapshot,
15062 row: MultiBufferRow,
15063 comment_prefix: &str,
15064 comment_prefix_whitespace: &str,
15065 ignore_indent: bool,
15066 ) -> Range<Point> {
15067 let indent_size = if ignore_indent {
15068 0
15069 } else {
15070 snapshot.indent_size_for_line(row).len
15071 };
15072
15073 let start = Point::new(row.0, indent_size);
15074
15075 let mut line_bytes = snapshot
15076 .bytes_in_range(start..snapshot.max_point())
15077 .flatten()
15078 .copied();
15079
15080 // If this line currently begins with the line comment prefix, then record
15081 // the range containing the prefix.
15082 if line_bytes
15083 .by_ref()
15084 .take(comment_prefix.len())
15085 .eq(comment_prefix.bytes())
15086 {
15087 // Include any whitespace that matches the comment prefix.
15088 let matching_whitespace_len = line_bytes
15089 .zip(comment_prefix_whitespace.bytes())
15090 .take_while(|(a, b)| a == b)
15091 .count() as u32;
15092 let end = Point::new(
15093 start.row,
15094 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15095 );
15096 start..end
15097 } else {
15098 start..start
15099 }
15100 }
15101
15102 fn comment_suffix_range(
15103 snapshot: &MultiBufferSnapshot,
15104 row: MultiBufferRow,
15105 comment_suffix: &str,
15106 comment_suffix_has_leading_space: bool,
15107 ) -> Range<Point> {
15108 let end = Point::new(row.0, snapshot.line_len(row));
15109 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15110
15111 let mut line_end_bytes = snapshot
15112 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15113 .flatten()
15114 .copied();
15115
15116 let leading_space_len = if suffix_start_column > 0
15117 && line_end_bytes.next() == Some(b' ')
15118 && comment_suffix_has_leading_space
15119 {
15120 1
15121 } else {
15122 0
15123 };
15124
15125 // If this line currently begins with the line comment prefix, then record
15126 // the range containing the prefix.
15127 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15128 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15129 start..end
15130 } else {
15131 end..end
15132 }
15133 }
15134
15135 // TODO: Handle selections that cross excerpts
15136 for selection in &mut selections {
15137 let start_column = snapshot
15138 .indent_size_for_line(MultiBufferRow(selection.start.row))
15139 .len;
15140 let language = if let Some(language) =
15141 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15142 {
15143 language
15144 } else {
15145 continue;
15146 };
15147
15148 selection_edit_ranges.clear();
15149
15150 // If multiple selections contain a given row, avoid processing that
15151 // row more than once.
15152 let mut start_row = MultiBufferRow(selection.start.row);
15153 if last_toggled_row == Some(start_row) {
15154 start_row = start_row.next_row();
15155 }
15156 let end_row =
15157 if selection.end.row > selection.start.row && selection.end.column == 0 {
15158 MultiBufferRow(selection.end.row - 1)
15159 } else {
15160 MultiBufferRow(selection.end.row)
15161 };
15162 last_toggled_row = Some(end_row);
15163
15164 if start_row > end_row {
15165 continue;
15166 }
15167
15168 // If the language has line comments, toggle those.
15169 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15170
15171 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15172 if ignore_indent {
15173 full_comment_prefixes = full_comment_prefixes
15174 .into_iter()
15175 .map(|s| Arc::from(s.trim_end()))
15176 .collect();
15177 }
15178
15179 if !full_comment_prefixes.is_empty() {
15180 let first_prefix = full_comment_prefixes
15181 .first()
15182 .expect("prefixes is non-empty");
15183 let prefix_trimmed_lengths = full_comment_prefixes
15184 .iter()
15185 .map(|p| p.trim_end_matches(' ').len())
15186 .collect::<SmallVec<[usize; 4]>>();
15187
15188 let mut all_selection_lines_are_comments = true;
15189
15190 for row in start_row.0..=end_row.0 {
15191 let row = MultiBufferRow(row);
15192 if start_row < end_row && snapshot.is_line_blank(row) {
15193 continue;
15194 }
15195
15196 let prefix_range = full_comment_prefixes
15197 .iter()
15198 .zip(prefix_trimmed_lengths.iter().copied())
15199 .map(|(prefix, trimmed_prefix_len)| {
15200 comment_prefix_range(
15201 snapshot.deref(),
15202 row,
15203 &prefix[..trimmed_prefix_len],
15204 &prefix[trimmed_prefix_len..],
15205 ignore_indent,
15206 )
15207 })
15208 .max_by_key(|range| range.end.column - range.start.column)
15209 .expect("prefixes is non-empty");
15210
15211 if prefix_range.is_empty() {
15212 all_selection_lines_are_comments = false;
15213 }
15214
15215 selection_edit_ranges.push(prefix_range);
15216 }
15217
15218 if all_selection_lines_are_comments {
15219 edits.extend(
15220 selection_edit_ranges
15221 .iter()
15222 .cloned()
15223 .map(|range| (range, empty_str.clone())),
15224 );
15225 } else {
15226 let min_column = selection_edit_ranges
15227 .iter()
15228 .map(|range| range.start.column)
15229 .min()
15230 .unwrap_or(0);
15231 edits.extend(selection_edit_ranges.iter().map(|range| {
15232 let position = Point::new(range.start.row, min_column);
15233 (position..position, first_prefix.clone())
15234 }));
15235 }
15236 } else if let Some(BlockCommentConfig {
15237 start: full_comment_prefix,
15238 end: comment_suffix,
15239 ..
15240 }) = language.block_comment()
15241 {
15242 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15243 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15244 let prefix_range = comment_prefix_range(
15245 snapshot.deref(),
15246 start_row,
15247 comment_prefix,
15248 comment_prefix_whitespace,
15249 ignore_indent,
15250 );
15251 let suffix_range = comment_suffix_range(
15252 snapshot.deref(),
15253 end_row,
15254 comment_suffix.trim_start_matches(' '),
15255 comment_suffix.starts_with(' '),
15256 );
15257
15258 if prefix_range.is_empty() || suffix_range.is_empty() {
15259 edits.push((
15260 prefix_range.start..prefix_range.start,
15261 full_comment_prefix.clone(),
15262 ));
15263 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15264 suffixes_inserted.push((end_row, comment_suffix.len()));
15265 } else {
15266 edits.push((prefix_range, empty_str.clone()));
15267 edits.push((suffix_range, empty_str.clone()));
15268 }
15269 } else {
15270 continue;
15271 }
15272 }
15273
15274 drop(snapshot);
15275 this.buffer.update(cx, |buffer, cx| {
15276 buffer.edit(edits, None, cx);
15277 });
15278
15279 // Adjust selections so that they end before any comment suffixes that
15280 // were inserted.
15281 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15282 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15283 let snapshot = this.buffer.read(cx).read(cx);
15284 for selection in &mut selections {
15285 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15286 match row.cmp(&MultiBufferRow(selection.end.row)) {
15287 Ordering::Less => {
15288 suffixes_inserted.next();
15289 continue;
15290 }
15291 Ordering::Greater => break,
15292 Ordering::Equal => {
15293 if selection.end.column == snapshot.line_len(row) {
15294 if selection.is_empty() {
15295 selection.start.column -= suffix_len as u32;
15296 }
15297 selection.end.column -= suffix_len as u32;
15298 }
15299 break;
15300 }
15301 }
15302 }
15303 }
15304
15305 drop(snapshot);
15306 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15307
15308 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15309 let selections_on_single_row = selections.windows(2).all(|selections| {
15310 selections[0].start.row == selections[1].start.row
15311 && selections[0].end.row == selections[1].end.row
15312 && selections[0].start.row == selections[0].end.row
15313 });
15314 let selections_selecting = selections
15315 .iter()
15316 .any(|selection| selection.start != selection.end);
15317 let advance_downwards = action.advance_downwards
15318 && selections_on_single_row
15319 && !selections_selecting
15320 && !matches!(this.mode, EditorMode::SingleLine);
15321
15322 if advance_downwards {
15323 let snapshot = this.buffer.read(cx).snapshot(cx);
15324
15325 this.change_selections(Default::default(), window, cx, |s| {
15326 s.move_cursors_with(|display_snapshot, display_point, _| {
15327 let mut point = display_point.to_point(display_snapshot);
15328 point.row += 1;
15329 point = snapshot.clip_point(point, Bias::Left);
15330 let display_point = point.to_display_point(display_snapshot);
15331 let goal = SelectionGoal::HorizontalPosition(
15332 display_snapshot
15333 .x_for_display_point(display_point, text_layout_details)
15334 .into(),
15335 );
15336 (display_point, goal)
15337 })
15338 });
15339 }
15340 });
15341 }
15342
15343 pub fn select_enclosing_symbol(
15344 &mut self,
15345 _: &SelectEnclosingSymbol,
15346 window: &mut Window,
15347 cx: &mut Context<Self>,
15348 ) {
15349 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15350
15351 let buffer = self.buffer.read(cx).snapshot(cx);
15352 let old_selections = self
15353 .selections
15354 .all::<usize>(&self.display_snapshot(cx))
15355 .into_boxed_slice();
15356
15357 fn update_selection(
15358 selection: &Selection<usize>,
15359 buffer_snap: &MultiBufferSnapshot,
15360 ) -> Option<Selection<usize>> {
15361 let cursor = selection.head();
15362 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15363 for symbol in symbols.iter().rev() {
15364 let start = symbol.range.start.to_offset(buffer_snap);
15365 let end = symbol.range.end.to_offset(buffer_snap);
15366 let new_range = start..end;
15367 if start < selection.start || end > selection.end {
15368 return Some(Selection {
15369 id: selection.id,
15370 start: new_range.start,
15371 end: new_range.end,
15372 goal: SelectionGoal::None,
15373 reversed: selection.reversed,
15374 });
15375 }
15376 }
15377 None
15378 }
15379
15380 let mut selected_larger_symbol = false;
15381 let new_selections = old_selections
15382 .iter()
15383 .map(|selection| match update_selection(selection, &buffer) {
15384 Some(new_selection) => {
15385 if new_selection.range() != selection.range() {
15386 selected_larger_symbol = true;
15387 }
15388 new_selection
15389 }
15390 None => selection.clone(),
15391 })
15392 .collect::<Vec<_>>();
15393
15394 if selected_larger_symbol {
15395 self.change_selections(Default::default(), window, cx, |s| {
15396 s.select(new_selections);
15397 });
15398 }
15399 }
15400
15401 pub fn select_larger_syntax_node(
15402 &mut self,
15403 _: &SelectLargerSyntaxNode,
15404 window: &mut Window,
15405 cx: &mut Context<Self>,
15406 ) {
15407 let Some(visible_row_count) = self.visible_row_count() else {
15408 return;
15409 };
15410 let old_selections: Box<[_]> = self
15411 .selections
15412 .all::<usize>(&self.display_snapshot(cx))
15413 .into();
15414 if old_selections.is_empty() {
15415 return;
15416 }
15417
15418 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15419
15420 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15421 let buffer = self.buffer.read(cx).snapshot(cx);
15422
15423 let mut selected_larger_node = false;
15424 let mut new_selections = old_selections
15425 .iter()
15426 .map(|selection| {
15427 let old_range = selection.start..selection.end;
15428
15429 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15430 // manually select word at selection
15431 if ["string_content", "inline"].contains(&node.kind()) {
15432 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15433 // ignore if word is already selected
15434 if !word_range.is_empty() && old_range != word_range {
15435 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15436 // only select word if start and end point belongs to same word
15437 if word_range == last_word_range {
15438 selected_larger_node = true;
15439 return Selection {
15440 id: selection.id,
15441 start: word_range.start,
15442 end: word_range.end,
15443 goal: SelectionGoal::None,
15444 reversed: selection.reversed,
15445 };
15446 }
15447 }
15448 }
15449 }
15450
15451 let mut new_range = old_range.clone();
15452 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15453 new_range = range;
15454 if !node.is_named() {
15455 continue;
15456 }
15457 if !display_map.intersects_fold(new_range.start)
15458 && !display_map.intersects_fold(new_range.end)
15459 {
15460 break;
15461 }
15462 }
15463
15464 selected_larger_node |= new_range != old_range;
15465 Selection {
15466 id: selection.id,
15467 start: new_range.start,
15468 end: new_range.end,
15469 goal: SelectionGoal::None,
15470 reversed: selection.reversed,
15471 }
15472 })
15473 .collect::<Vec<_>>();
15474
15475 if !selected_larger_node {
15476 return; // don't put this call in the history
15477 }
15478
15479 // scroll based on transformation done to the last selection created by the user
15480 let (last_old, last_new) = old_selections
15481 .last()
15482 .zip(new_selections.last().cloned())
15483 .expect("old_selections isn't empty");
15484
15485 // revert selection
15486 let is_selection_reversed = {
15487 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15488 new_selections.last_mut().expect("checked above").reversed =
15489 should_newest_selection_be_reversed;
15490 should_newest_selection_be_reversed
15491 };
15492
15493 if selected_larger_node {
15494 self.select_syntax_node_history.disable_clearing = true;
15495 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15496 s.select(new_selections.clone());
15497 });
15498 self.select_syntax_node_history.disable_clearing = false;
15499 }
15500
15501 let start_row = last_new.start.to_display_point(&display_map).row().0;
15502 let end_row = last_new.end.to_display_point(&display_map).row().0;
15503 let selection_height = end_row - start_row + 1;
15504 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15505
15506 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15507 let scroll_behavior = if fits_on_the_screen {
15508 self.request_autoscroll(Autoscroll::fit(), cx);
15509 SelectSyntaxNodeScrollBehavior::FitSelection
15510 } else if is_selection_reversed {
15511 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15512 SelectSyntaxNodeScrollBehavior::CursorTop
15513 } else {
15514 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15515 SelectSyntaxNodeScrollBehavior::CursorBottom
15516 };
15517
15518 self.select_syntax_node_history.push((
15519 old_selections,
15520 scroll_behavior,
15521 is_selection_reversed,
15522 ));
15523 }
15524
15525 pub fn select_smaller_syntax_node(
15526 &mut self,
15527 _: &SelectSmallerSyntaxNode,
15528 window: &mut Window,
15529 cx: &mut Context<Self>,
15530 ) {
15531 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15532
15533 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15534 self.select_syntax_node_history.pop()
15535 {
15536 if let Some(selection) = selections.last_mut() {
15537 selection.reversed = is_selection_reversed;
15538 }
15539
15540 self.select_syntax_node_history.disable_clearing = true;
15541 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15542 s.select(selections.to_vec());
15543 });
15544 self.select_syntax_node_history.disable_clearing = false;
15545
15546 match scroll_behavior {
15547 SelectSyntaxNodeScrollBehavior::CursorTop => {
15548 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15549 }
15550 SelectSyntaxNodeScrollBehavior::FitSelection => {
15551 self.request_autoscroll(Autoscroll::fit(), cx);
15552 }
15553 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15554 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15555 }
15556 }
15557 }
15558 }
15559
15560 pub fn unwrap_syntax_node(
15561 &mut self,
15562 _: &UnwrapSyntaxNode,
15563 window: &mut Window,
15564 cx: &mut Context<Self>,
15565 ) {
15566 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15567
15568 let buffer = self.buffer.read(cx).snapshot(cx);
15569 let selections = self
15570 .selections
15571 .all::<usize>(&self.display_snapshot(cx))
15572 .into_iter()
15573 // subtracting the offset requires sorting
15574 .sorted_by_key(|i| i.start);
15575
15576 let full_edits = selections
15577 .into_iter()
15578 .filter_map(|selection| {
15579 let child = if selection.is_empty()
15580 && let Some((_, ancestor_range)) =
15581 buffer.syntax_ancestor(selection.start..selection.end)
15582 {
15583 ancestor_range
15584 } else {
15585 selection.range()
15586 };
15587
15588 let mut parent = child.clone();
15589 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15590 parent = ancestor_range;
15591 if parent.start < child.start || parent.end > child.end {
15592 break;
15593 }
15594 }
15595
15596 if parent == child {
15597 return None;
15598 }
15599 let text = buffer.text_for_range(child).collect::<String>();
15600 Some((selection.id, parent, text))
15601 })
15602 .collect::<Vec<_>>();
15603 if full_edits.is_empty() {
15604 return;
15605 }
15606
15607 self.transact(window, cx, |this, window, cx| {
15608 this.buffer.update(cx, |buffer, cx| {
15609 buffer.edit(
15610 full_edits
15611 .iter()
15612 .map(|(_, p, t)| (p.clone(), t.clone()))
15613 .collect::<Vec<_>>(),
15614 None,
15615 cx,
15616 );
15617 });
15618 this.change_selections(Default::default(), window, cx, |s| {
15619 let mut offset = 0;
15620 let mut selections = vec![];
15621 for (id, parent, text) in full_edits {
15622 let start = parent.start - offset;
15623 offset += parent.len() - text.len();
15624 selections.push(Selection {
15625 id,
15626 start,
15627 end: start + text.len(),
15628 reversed: false,
15629 goal: Default::default(),
15630 });
15631 }
15632 s.select(selections);
15633 });
15634 });
15635 }
15636
15637 pub fn select_next_syntax_node(
15638 &mut self,
15639 _: &SelectNextSyntaxNode,
15640 window: &mut Window,
15641 cx: &mut Context<Self>,
15642 ) {
15643 let old_selections: Box<[_]> = self
15644 .selections
15645 .all::<usize>(&self.display_snapshot(cx))
15646 .into();
15647 if old_selections.is_empty() {
15648 return;
15649 }
15650
15651 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15652
15653 let buffer = self.buffer.read(cx).snapshot(cx);
15654 let mut selected_sibling = false;
15655
15656 let new_selections = old_selections
15657 .iter()
15658 .map(|selection| {
15659 let old_range = selection.start..selection.end;
15660
15661 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15662 let new_range = node.byte_range();
15663 selected_sibling = true;
15664 Selection {
15665 id: selection.id,
15666 start: new_range.start,
15667 end: new_range.end,
15668 goal: SelectionGoal::None,
15669 reversed: selection.reversed,
15670 }
15671 } else {
15672 selection.clone()
15673 }
15674 })
15675 .collect::<Vec<_>>();
15676
15677 if selected_sibling {
15678 self.change_selections(
15679 SelectionEffects::scroll(Autoscroll::fit()),
15680 window,
15681 cx,
15682 |s| {
15683 s.select(new_selections);
15684 },
15685 );
15686 }
15687 }
15688
15689 pub fn select_prev_syntax_node(
15690 &mut self,
15691 _: &SelectPreviousSyntaxNode,
15692 window: &mut Window,
15693 cx: &mut Context<Self>,
15694 ) {
15695 let old_selections: Box<[_]> = self
15696 .selections
15697 .all::<usize>(&self.display_snapshot(cx))
15698 .into();
15699 if old_selections.is_empty() {
15700 return;
15701 }
15702
15703 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15704
15705 let buffer = self.buffer.read(cx).snapshot(cx);
15706 let mut selected_sibling = false;
15707
15708 let new_selections = old_selections
15709 .iter()
15710 .map(|selection| {
15711 let old_range = selection.start..selection.end;
15712
15713 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15714 let new_range = node.byte_range();
15715 selected_sibling = true;
15716 Selection {
15717 id: selection.id,
15718 start: new_range.start,
15719 end: new_range.end,
15720 goal: SelectionGoal::None,
15721 reversed: selection.reversed,
15722 }
15723 } else {
15724 selection.clone()
15725 }
15726 })
15727 .collect::<Vec<_>>();
15728
15729 if selected_sibling {
15730 self.change_selections(
15731 SelectionEffects::scroll(Autoscroll::fit()),
15732 window,
15733 cx,
15734 |s| {
15735 s.select(new_selections);
15736 },
15737 );
15738 }
15739 }
15740
15741 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15742 if !EditorSettings::get_global(cx).gutter.runnables {
15743 self.clear_tasks();
15744 return Task::ready(());
15745 }
15746 let project = self.project().map(Entity::downgrade);
15747 let task_sources = self.lsp_task_sources(cx);
15748 let multi_buffer = self.buffer.downgrade();
15749 cx.spawn_in(window, async move |editor, cx| {
15750 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15751 let Some(project) = project.and_then(|p| p.upgrade()) else {
15752 return;
15753 };
15754 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15755 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15756 }) else {
15757 return;
15758 };
15759
15760 let hide_runnables = project
15761 .update(cx, |project, _| project.is_via_collab())
15762 .unwrap_or(true);
15763 if hide_runnables {
15764 return;
15765 }
15766 let new_rows =
15767 cx.background_spawn({
15768 let snapshot = display_snapshot.clone();
15769 async move {
15770 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15771 }
15772 })
15773 .await;
15774 let Ok(lsp_tasks) =
15775 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15776 else {
15777 return;
15778 };
15779 let lsp_tasks = lsp_tasks.await;
15780
15781 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15782 lsp_tasks
15783 .into_iter()
15784 .flat_map(|(kind, tasks)| {
15785 tasks.into_iter().filter_map(move |(location, task)| {
15786 Some((kind.clone(), location?, task))
15787 })
15788 })
15789 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15790 let buffer = location.target.buffer;
15791 let buffer_snapshot = buffer.read(cx).snapshot();
15792 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15793 |(excerpt_id, snapshot, _)| {
15794 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15795 display_snapshot
15796 .buffer_snapshot()
15797 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15798 } else {
15799 None
15800 }
15801 },
15802 );
15803 if let Some(offset) = offset {
15804 let task_buffer_range =
15805 location.target.range.to_point(&buffer_snapshot);
15806 let context_buffer_range =
15807 task_buffer_range.to_offset(&buffer_snapshot);
15808 let context_range = BufferOffset(context_buffer_range.start)
15809 ..BufferOffset(context_buffer_range.end);
15810
15811 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15812 .or_insert_with(|| RunnableTasks {
15813 templates: Vec::new(),
15814 offset,
15815 column: task_buffer_range.start.column,
15816 extra_variables: HashMap::default(),
15817 context_range,
15818 })
15819 .templates
15820 .push((kind, task.original_task().clone()));
15821 }
15822
15823 acc
15824 })
15825 }) else {
15826 return;
15827 };
15828
15829 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15830 buffer.language_settings(cx).tasks.prefer_lsp
15831 }) else {
15832 return;
15833 };
15834
15835 let rows = Self::runnable_rows(
15836 project,
15837 display_snapshot,
15838 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15839 new_rows,
15840 cx.clone(),
15841 )
15842 .await;
15843 editor
15844 .update(cx, |editor, _| {
15845 editor.clear_tasks();
15846 for (key, mut value) in rows {
15847 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15848 value.templates.extend(lsp_tasks.templates);
15849 }
15850
15851 editor.insert_tasks(key, value);
15852 }
15853 for (key, value) in lsp_tasks_by_rows {
15854 editor.insert_tasks(key, value);
15855 }
15856 })
15857 .ok();
15858 })
15859 }
15860 fn fetch_runnable_ranges(
15861 snapshot: &DisplaySnapshot,
15862 range: Range<Anchor>,
15863 ) -> Vec<language::RunnableRange> {
15864 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15865 }
15866
15867 fn runnable_rows(
15868 project: Entity<Project>,
15869 snapshot: DisplaySnapshot,
15870 prefer_lsp: bool,
15871 runnable_ranges: Vec<RunnableRange>,
15872 cx: AsyncWindowContext,
15873 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15874 cx.spawn(async move |cx| {
15875 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15876 for mut runnable in runnable_ranges {
15877 let Some(tasks) = cx
15878 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15879 .ok()
15880 else {
15881 continue;
15882 };
15883 let mut tasks = tasks.await;
15884
15885 if prefer_lsp {
15886 tasks.retain(|(task_kind, _)| {
15887 !matches!(task_kind, TaskSourceKind::Language { .. })
15888 });
15889 }
15890 if tasks.is_empty() {
15891 continue;
15892 }
15893
15894 let point = runnable
15895 .run_range
15896 .start
15897 .to_point(&snapshot.buffer_snapshot());
15898 let Some(row) = snapshot
15899 .buffer_snapshot()
15900 .buffer_line_for_row(MultiBufferRow(point.row))
15901 .map(|(_, range)| range.start.row)
15902 else {
15903 continue;
15904 };
15905
15906 let context_range =
15907 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15908 runnable_rows.push((
15909 (runnable.buffer_id, row),
15910 RunnableTasks {
15911 templates: tasks,
15912 offset: snapshot
15913 .buffer_snapshot()
15914 .anchor_before(runnable.run_range.start),
15915 context_range,
15916 column: point.column,
15917 extra_variables: runnable.extra_captures,
15918 },
15919 ));
15920 }
15921 runnable_rows
15922 })
15923 }
15924
15925 fn templates_with_tags(
15926 project: &Entity<Project>,
15927 runnable: &mut Runnable,
15928 cx: &mut App,
15929 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15930 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15931 let (worktree_id, file) = project
15932 .buffer_for_id(runnable.buffer, cx)
15933 .and_then(|buffer| buffer.read(cx).file())
15934 .map(|file| (file.worktree_id(cx), file.clone()))
15935 .unzip();
15936
15937 (
15938 project.task_store().read(cx).task_inventory().cloned(),
15939 worktree_id,
15940 file,
15941 )
15942 });
15943
15944 let tags = mem::take(&mut runnable.tags);
15945 let language = runnable.language.clone();
15946 cx.spawn(async move |cx| {
15947 let mut templates_with_tags = Vec::new();
15948 if let Some(inventory) = inventory {
15949 for RunnableTag(tag) in tags {
15950 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15951 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15952 }) else {
15953 return templates_with_tags;
15954 };
15955 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15956 move |(_, template)| {
15957 template.tags.iter().any(|source_tag| source_tag == &tag)
15958 },
15959 ));
15960 }
15961 }
15962 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15963
15964 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15965 // Strongest source wins; if we have worktree tag binding, prefer that to
15966 // global and language bindings;
15967 // if we have a global binding, prefer that to language binding.
15968 let first_mismatch = templates_with_tags
15969 .iter()
15970 .position(|(tag_source, _)| tag_source != leading_tag_source);
15971 if let Some(index) = first_mismatch {
15972 templates_with_tags.truncate(index);
15973 }
15974 }
15975
15976 templates_with_tags
15977 })
15978 }
15979
15980 pub fn move_to_enclosing_bracket(
15981 &mut self,
15982 _: &MoveToEnclosingBracket,
15983 window: &mut Window,
15984 cx: &mut Context<Self>,
15985 ) {
15986 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15987 self.change_selections(Default::default(), window, cx, |s| {
15988 s.move_offsets_with(|snapshot, selection| {
15989 let Some(enclosing_bracket_ranges) =
15990 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15991 else {
15992 return;
15993 };
15994
15995 let mut best_length = usize::MAX;
15996 let mut best_inside = false;
15997 let mut best_in_bracket_range = false;
15998 let mut best_destination = None;
15999 for (open, close) in enclosing_bracket_ranges {
16000 let close = close.to_inclusive();
16001 let length = close.end() - open.start;
16002 let inside = selection.start >= open.end && selection.end <= *close.start();
16003 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16004 || close.contains(&selection.head());
16005
16006 // If best is next to a bracket and current isn't, skip
16007 if !in_bracket_range && best_in_bracket_range {
16008 continue;
16009 }
16010
16011 // Prefer smaller lengths unless best is inside and current isn't
16012 if length > best_length && (best_inside || !inside) {
16013 continue;
16014 }
16015
16016 best_length = length;
16017 best_inside = inside;
16018 best_in_bracket_range = in_bracket_range;
16019 best_destination = Some(
16020 if close.contains(&selection.start) && close.contains(&selection.end) {
16021 if inside { open.end } else { open.start }
16022 } else if inside {
16023 *close.start()
16024 } else {
16025 *close.end()
16026 },
16027 );
16028 }
16029
16030 if let Some(destination) = best_destination {
16031 selection.collapse_to(destination, SelectionGoal::None);
16032 }
16033 })
16034 });
16035 }
16036
16037 pub fn undo_selection(
16038 &mut self,
16039 _: &UndoSelection,
16040 window: &mut Window,
16041 cx: &mut Context<Self>,
16042 ) {
16043 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16044 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16045 self.selection_history.mode = SelectionHistoryMode::Undoing;
16046 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16047 this.end_selection(window, cx);
16048 this.change_selections(
16049 SelectionEffects::scroll(Autoscroll::newest()),
16050 window,
16051 cx,
16052 |s| s.select_anchors(entry.selections.to_vec()),
16053 );
16054 });
16055 self.selection_history.mode = SelectionHistoryMode::Normal;
16056
16057 self.select_next_state = entry.select_next_state;
16058 self.select_prev_state = entry.select_prev_state;
16059 self.add_selections_state = entry.add_selections_state;
16060 }
16061 }
16062
16063 pub fn redo_selection(
16064 &mut self,
16065 _: &RedoSelection,
16066 window: &mut Window,
16067 cx: &mut Context<Self>,
16068 ) {
16069 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16070 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16071 self.selection_history.mode = SelectionHistoryMode::Redoing;
16072 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16073 this.end_selection(window, cx);
16074 this.change_selections(
16075 SelectionEffects::scroll(Autoscroll::newest()),
16076 window,
16077 cx,
16078 |s| s.select_anchors(entry.selections.to_vec()),
16079 );
16080 });
16081 self.selection_history.mode = SelectionHistoryMode::Normal;
16082
16083 self.select_next_state = entry.select_next_state;
16084 self.select_prev_state = entry.select_prev_state;
16085 self.add_selections_state = entry.add_selections_state;
16086 }
16087 }
16088
16089 pub fn expand_excerpts(
16090 &mut self,
16091 action: &ExpandExcerpts,
16092 _: &mut Window,
16093 cx: &mut Context<Self>,
16094 ) {
16095 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16096 }
16097
16098 pub fn expand_excerpts_down(
16099 &mut self,
16100 action: &ExpandExcerptsDown,
16101 _: &mut Window,
16102 cx: &mut Context<Self>,
16103 ) {
16104 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16105 }
16106
16107 pub fn expand_excerpts_up(
16108 &mut self,
16109 action: &ExpandExcerptsUp,
16110 _: &mut Window,
16111 cx: &mut Context<Self>,
16112 ) {
16113 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16114 }
16115
16116 pub fn expand_excerpts_for_direction(
16117 &mut self,
16118 lines: u32,
16119 direction: ExpandExcerptDirection,
16120
16121 cx: &mut Context<Self>,
16122 ) {
16123 let selections = self.selections.disjoint_anchors_arc();
16124
16125 let lines = if lines == 0 {
16126 EditorSettings::get_global(cx).expand_excerpt_lines
16127 } else {
16128 lines
16129 };
16130
16131 self.buffer.update(cx, |buffer, cx| {
16132 let snapshot = buffer.snapshot(cx);
16133 let mut excerpt_ids = selections
16134 .iter()
16135 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16136 .collect::<Vec<_>>();
16137 excerpt_ids.sort();
16138 excerpt_ids.dedup();
16139 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16140 })
16141 }
16142
16143 pub fn expand_excerpt(
16144 &mut self,
16145 excerpt: ExcerptId,
16146 direction: ExpandExcerptDirection,
16147 window: &mut Window,
16148 cx: &mut Context<Self>,
16149 ) {
16150 let current_scroll_position = self.scroll_position(cx);
16151 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16152 let mut scroll = None;
16153
16154 if direction == ExpandExcerptDirection::Down {
16155 let multi_buffer = self.buffer.read(cx);
16156 let snapshot = multi_buffer.snapshot(cx);
16157 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16158 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16159 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16160 {
16161 let buffer_snapshot = buffer.read(cx).snapshot();
16162 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16163 let last_row = buffer_snapshot.max_point().row;
16164 let lines_below = last_row.saturating_sub(excerpt_end_row);
16165 if lines_below >= lines_to_expand {
16166 scroll = Some(
16167 current_scroll_position
16168 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16169 );
16170 }
16171 }
16172 }
16173 if direction == ExpandExcerptDirection::Up
16174 && self
16175 .buffer
16176 .read(cx)
16177 .snapshot(cx)
16178 .excerpt_before(excerpt)
16179 .is_none()
16180 {
16181 scroll = Some(current_scroll_position);
16182 }
16183
16184 self.buffer.update(cx, |buffer, cx| {
16185 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16186 });
16187
16188 if let Some(new_scroll_position) = scroll {
16189 self.set_scroll_position(new_scroll_position, window, cx);
16190 }
16191 }
16192
16193 pub fn go_to_singleton_buffer_point(
16194 &mut self,
16195 point: Point,
16196 window: &mut Window,
16197 cx: &mut Context<Self>,
16198 ) {
16199 self.go_to_singleton_buffer_range(point..point, window, cx);
16200 }
16201
16202 pub fn go_to_singleton_buffer_range(
16203 &mut self,
16204 range: Range<Point>,
16205 window: &mut Window,
16206 cx: &mut Context<Self>,
16207 ) {
16208 let multibuffer = self.buffer().read(cx);
16209 let Some(buffer) = multibuffer.as_singleton() else {
16210 return;
16211 };
16212 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16213 return;
16214 };
16215 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16216 return;
16217 };
16218 self.change_selections(
16219 SelectionEffects::default().nav_history(true),
16220 window,
16221 cx,
16222 |s| s.select_anchor_ranges([start..end]),
16223 );
16224 }
16225
16226 pub fn go_to_diagnostic(
16227 &mut self,
16228 action: &GoToDiagnostic,
16229 window: &mut Window,
16230 cx: &mut Context<Self>,
16231 ) {
16232 if !self.diagnostics_enabled() {
16233 return;
16234 }
16235 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16236 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16237 }
16238
16239 pub fn go_to_prev_diagnostic(
16240 &mut self,
16241 action: &GoToPreviousDiagnostic,
16242 window: &mut Window,
16243 cx: &mut Context<Self>,
16244 ) {
16245 if !self.diagnostics_enabled() {
16246 return;
16247 }
16248 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16249 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16250 }
16251
16252 pub fn go_to_diagnostic_impl(
16253 &mut self,
16254 direction: Direction,
16255 severity: GoToDiagnosticSeverityFilter,
16256 window: &mut Window,
16257 cx: &mut Context<Self>,
16258 ) {
16259 let buffer = self.buffer.read(cx).snapshot(cx);
16260 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16261
16262 let mut active_group_id = None;
16263 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16264 && active_group.active_range.start.to_offset(&buffer) == selection.start
16265 {
16266 active_group_id = Some(active_group.group_id);
16267 }
16268
16269 fn filtered<'a>(
16270 severity: GoToDiagnosticSeverityFilter,
16271 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16272 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16273 diagnostics
16274 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16275 .filter(|entry| entry.range.start != entry.range.end)
16276 .filter(|entry| !entry.diagnostic.is_unnecessary)
16277 }
16278
16279 let before = filtered(
16280 severity,
16281 buffer
16282 .diagnostics_in_range(0..selection.start)
16283 .filter(|entry| entry.range.start <= selection.start),
16284 );
16285 let after = filtered(
16286 severity,
16287 buffer
16288 .diagnostics_in_range(selection.start..buffer.len())
16289 .filter(|entry| entry.range.start >= selection.start),
16290 );
16291
16292 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16293 if direction == Direction::Prev {
16294 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16295 {
16296 for diagnostic in prev_diagnostics.into_iter().rev() {
16297 if diagnostic.range.start != selection.start
16298 || active_group_id
16299 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16300 {
16301 found = Some(diagnostic);
16302 break 'outer;
16303 }
16304 }
16305 }
16306 } else {
16307 for diagnostic in after.chain(before) {
16308 if diagnostic.range.start != selection.start
16309 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16310 {
16311 found = Some(diagnostic);
16312 break;
16313 }
16314 }
16315 }
16316 let Some(next_diagnostic) = found else {
16317 return;
16318 };
16319
16320 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16321 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16322 return;
16323 };
16324 let snapshot = self.snapshot(window, cx);
16325 if snapshot.intersects_fold(next_diagnostic.range.start) {
16326 self.unfold_ranges(
16327 std::slice::from_ref(&next_diagnostic.range),
16328 true,
16329 false,
16330 cx,
16331 );
16332 }
16333 self.change_selections(Default::default(), window, cx, |s| {
16334 s.select_ranges(vec![
16335 next_diagnostic.range.start..next_diagnostic.range.start,
16336 ])
16337 });
16338 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16339 self.refresh_edit_prediction(false, true, window, cx);
16340 }
16341
16342 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16343 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16344 let snapshot = self.snapshot(window, cx);
16345 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16346 self.go_to_hunk_before_or_after_position(
16347 &snapshot,
16348 selection.head(),
16349 Direction::Next,
16350 window,
16351 cx,
16352 );
16353 }
16354
16355 pub fn go_to_hunk_before_or_after_position(
16356 &mut self,
16357 snapshot: &EditorSnapshot,
16358 position: Point,
16359 direction: Direction,
16360 window: &mut Window,
16361 cx: &mut Context<Editor>,
16362 ) {
16363 let row = if direction == Direction::Next {
16364 self.hunk_after_position(snapshot, position)
16365 .map(|hunk| hunk.row_range.start)
16366 } else {
16367 self.hunk_before_position(snapshot, position)
16368 };
16369
16370 if let Some(row) = row {
16371 let destination = Point::new(row.0, 0);
16372 let autoscroll = Autoscroll::center();
16373
16374 self.unfold_ranges(&[destination..destination], false, false, cx);
16375 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16376 s.select_ranges([destination..destination]);
16377 });
16378 }
16379 }
16380
16381 fn hunk_after_position(
16382 &mut self,
16383 snapshot: &EditorSnapshot,
16384 position: Point,
16385 ) -> Option<MultiBufferDiffHunk> {
16386 snapshot
16387 .buffer_snapshot()
16388 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16389 .find(|hunk| hunk.row_range.start.0 > position.row)
16390 .or_else(|| {
16391 snapshot
16392 .buffer_snapshot()
16393 .diff_hunks_in_range(Point::zero()..position)
16394 .find(|hunk| hunk.row_range.end.0 < position.row)
16395 })
16396 }
16397
16398 fn go_to_prev_hunk(
16399 &mut self,
16400 _: &GoToPreviousHunk,
16401 window: &mut Window,
16402 cx: &mut Context<Self>,
16403 ) {
16404 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16405 let snapshot = self.snapshot(window, cx);
16406 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16407 self.go_to_hunk_before_or_after_position(
16408 &snapshot,
16409 selection.head(),
16410 Direction::Prev,
16411 window,
16412 cx,
16413 );
16414 }
16415
16416 fn hunk_before_position(
16417 &mut self,
16418 snapshot: &EditorSnapshot,
16419 position: Point,
16420 ) -> Option<MultiBufferRow> {
16421 snapshot
16422 .buffer_snapshot()
16423 .diff_hunk_before(position)
16424 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16425 }
16426
16427 fn go_to_next_change(
16428 &mut self,
16429 _: &GoToNextChange,
16430 window: &mut Window,
16431 cx: &mut Context<Self>,
16432 ) {
16433 if let Some(selections) = self
16434 .change_list
16435 .next_change(1, Direction::Next)
16436 .map(|s| s.to_vec())
16437 {
16438 self.change_selections(Default::default(), window, cx, |s| {
16439 let map = s.display_snapshot();
16440 s.select_display_ranges(selections.iter().map(|a| {
16441 let point = a.to_display_point(&map);
16442 point..point
16443 }))
16444 })
16445 }
16446 }
16447
16448 fn go_to_previous_change(
16449 &mut self,
16450 _: &GoToPreviousChange,
16451 window: &mut Window,
16452 cx: &mut Context<Self>,
16453 ) {
16454 if let Some(selections) = self
16455 .change_list
16456 .next_change(1, Direction::Prev)
16457 .map(|s| s.to_vec())
16458 {
16459 self.change_selections(Default::default(), window, cx, |s| {
16460 let map = s.display_snapshot();
16461 s.select_display_ranges(selections.iter().map(|a| {
16462 let point = a.to_display_point(&map);
16463 point..point
16464 }))
16465 })
16466 }
16467 }
16468
16469 pub fn go_to_next_document_highlight(
16470 &mut self,
16471 _: &GoToNextDocumentHighlight,
16472 window: &mut Window,
16473 cx: &mut Context<Self>,
16474 ) {
16475 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16476 }
16477
16478 pub fn go_to_prev_document_highlight(
16479 &mut self,
16480 _: &GoToPreviousDocumentHighlight,
16481 window: &mut Window,
16482 cx: &mut Context<Self>,
16483 ) {
16484 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16485 }
16486
16487 pub fn go_to_document_highlight_before_or_after_position(
16488 &mut self,
16489 direction: Direction,
16490 window: &mut Window,
16491 cx: &mut Context<Editor>,
16492 ) {
16493 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16494 let snapshot = self.snapshot(window, cx);
16495 let buffer = &snapshot.buffer_snapshot();
16496 let position = self
16497 .selections
16498 .newest::<Point>(&snapshot.display_snapshot)
16499 .head();
16500 let anchor_position = buffer.anchor_after(position);
16501
16502 // Get all document highlights (both read and write)
16503 let mut all_highlights = Vec::new();
16504
16505 if let Some((_, read_highlights)) = self
16506 .background_highlights
16507 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16508 {
16509 all_highlights.extend(read_highlights.iter());
16510 }
16511
16512 if let Some((_, write_highlights)) = self
16513 .background_highlights
16514 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16515 {
16516 all_highlights.extend(write_highlights.iter());
16517 }
16518
16519 if all_highlights.is_empty() {
16520 return;
16521 }
16522
16523 // Sort highlights by position
16524 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16525
16526 let target_highlight = match direction {
16527 Direction::Next => {
16528 // Find the first highlight after the current position
16529 all_highlights
16530 .iter()
16531 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16532 }
16533 Direction::Prev => {
16534 // Find the last highlight before the current position
16535 all_highlights
16536 .iter()
16537 .rev()
16538 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16539 }
16540 };
16541
16542 if let Some(highlight) = target_highlight {
16543 let destination = highlight.start.to_point(buffer);
16544 let autoscroll = Autoscroll::center();
16545
16546 self.unfold_ranges(&[destination..destination], false, false, cx);
16547 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16548 s.select_ranges([destination..destination]);
16549 });
16550 }
16551 }
16552
16553 fn go_to_line<T: 'static>(
16554 &mut self,
16555 position: Anchor,
16556 highlight_color: Option<Hsla>,
16557 window: &mut Window,
16558 cx: &mut Context<Self>,
16559 ) {
16560 let snapshot = self.snapshot(window, cx).display_snapshot;
16561 let position = position.to_point(&snapshot.buffer_snapshot());
16562 let start = snapshot
16563 .buffer_snapshot()
16564 .clip_point(Point::new(position.row, 0), Bias::Left);
16565 let end = start + Point::new(1, 0);
16566 let start = snapshot.buffer_snapshot().anchor_before(start);
16567 let end = snapshot.buffer_snapshot().anchor_before(end);
16568
16569 self.highlight_rows::<T>(
16570 start..end,
16571 highlight_color
16572 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16573 Default::default(),
16574 cx,
16575 );
16576
16577 if self.buffer.read(cx).is_singleton() {
16578 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16579 }
16580 }
16581
16582 pub fn go_to_definition(
16583 &mut self,
16584 _: &GoToDefinition,
16585 window: &mut Window,
16586 cx: &mut Context<Self>,
16587 ) -> Task<Result<Navigated>> {
16588 let definition =
16589 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16590 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16591 cx.spawn_in(window, async move |editor, cx| {
16592 if definition.await? == Navigated::Yes {
16593 return Ok(Navigated::Yes);
16594 }
16595 match fallback_strategy {
16596 GoToDefinitionFallback::None => Ok(Navigated::No),
16597 GoToDefinitionFallback::FindAllReferences => {
16598 match editor.update_in(cx, |editor, window, cx| {
16599 editor.find_all_references(&FindAllReferences, window, cx)
16600 })? {
16601 Some(references) => references.await,
16602 None => Ok(Navigated::No),
16603 }
16604 }
16605 }
16606 })
16607 }
16608
16609 pub fn go_to_declaration(
16610 &mut self,
16611 _: &GoToDeclaration,
16612 window: &mut Window,
16613 cx: &mut Context<Self>,
16614 ) -> Task<Result<Navigated>> {
16615 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16616 }
16617
16618 pub fn go_to_declaration_split(
16619 &mut self,
16620 _: &GoToDeclaration,
16621 window: &mut Window,
16622 cx: &mut Context<Self>,
16623 ) -> Task<Result<Navigated>> {
16624 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16625 }
16626
16627 pub fn go_to_implementation(
16628 &mut self,
16629 _: &GoToImplementation,
16630 window: &mut Window,
16631 cx: &mut Context<Self>,
16632 ) -> Task<Result<Navigated>> {
16633 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16634 }
16635
16636 pub fn go_to_implementation_split(
16637 &mut self,
16638 _: &GoToImplementationSplit,
16639 window: &mut Window,
16640 cx: &mut Context<Self>,
16641 ) -> Task<Result<Navigated>> {
16642 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16643 }
16644
16645 pub fn go_to_type_definition(
16646 &mut self,
16647 _: &GoToTypeDefinition,
16648 window: &mut Window,
16649 cx: &mut Context<Self>,
16650 ) -> Task<Result<Navigated>> {
16651 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16652 }
16653
16654 pub fn go_to_definition_split(
16655 &mut self,
16656 _: &GoToDefinitionSplit,
16657 window: &mut Window,
16658 cx: &mut Context<Self>,
16659 ) -> Task<Result<Navigated>> {
16660 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16661 }
16662
16663 pub fn go_to_type_definition_split(
16664 &mut self,
16665 _: &GoToTypeDefinitionSplit,
16666 window: &mut Window,
16667 cx: &mut Context<Self>,
16668 ) -> Task<Result<Navigated>> {
16669 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16670 }
16671
16672 fn go_to_definition_of_kind(
16673 &mut self,
16674 kind: GotoDefinitionKind,
16675 split: bool,
16676 window: &mut Window,
16677 cx: &mut Context<Self>,
16678 ) -> Task<Result<Navigated>> {
16679 let Some(provider) = self.semantics_provider.clone() else {
16680 return Task::ready(Ok(Navigated::No));
16681 };
16682 let head = self
16683 .selections
16684 .newest::<usize>(&self.display_snapshot(cx))
16685 .head();
16686 let buffer = self.buffer.read(cx);
16687 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16688 return Task::ready(Ok(Navigated::No));
16689 };
16690 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16691 return Task::ready(Ok(Navigated::No));
16692 };
16693
16694 cx.spawn_in(window, async move |editor, cx| {
16695 let Some(definitions) = definitions.await? else {
16696 return Ok(Navigated::No);
16697 };
16698 let navigated = editor
16699 .update_in(cx, |editor, window, cx| {
16700 editor.navigate_to_hover_links(
16701 Some(kind),
16702 definitions
16703 .into_iter()
16704 .filter(|location| {
16705 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16706 })
16707 .map(HoverLink::Text)
16708 .collect::<Vec<_>>(),
16709 split,
16710 window,
16711 cx,
16712 )
16713 })?
16714 .await?;
16715 anyhow::Ok(navigated)
16716 })
16717 }
16718
16719 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16720 let selection = self.selections.newest_anchor();
16721 let head = selection.head();
16722 let tail = selection.tail();
16723
16724 let Some((buffer, start_position)) =
16725 self.buffer.read(cx).text_anchor_for_position(head, cx)
16726 else {
16727 return;
16728 };
16729
16730 let end_position = if head != tail {
16731 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16732 return;
16733 };
16734 Some(pos)
16735 } else {
16736 None
16737 };
16738
16739 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16740 let url = if let Some(end_pos) = end_position {
16741 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16742 } else {
16743 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16744 };
16745
16746 if let Some(url) = url {
16747 cx.update(|window, cx| {
16748 if parse_zed_link(&url, cx).is_some() {
16749 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16750 } else {
16751 cx.open_url(&url);
16752 }
16753 })?;
16754 }
16755
16756 anyhow::Ok(())
16757 });
16758
16759 url_finder.detach();
16760 }
16761
16762 pub fn open_selected_filename(
16763 &mut self,
16764 _: &OpenSelectedFilename,
16765 window: &mut Window,
16766 cx: &mut Context<Self>,
16767 ) {
16768 let Some(workspace) = self.workspace() else {
16769 return;
16770 };
16771
16772 let position = self.selections.newest_anchor().head();
16773
16774 let Some((buffer, buffer_position)) =
16775 self.buffer.read(cx).text_anchor_for_position(position, cx)
16776 else {
16777 return;
16778 };
16779
16780 let project = self.project.clone();
16781
16782 cx.spawn_in(window, async move |_, cx| {
16783 let result = find_file(&buffer, project, buffer_position, cx).await;
16784
16785 if let Some((_, path)) = result {
16786 workspace
16787 .update_in(cx, |workspace, window, cx| {
16788 workspace.open_resolved_path(path, window, cx)
16789 })?
16790 .await?;
16791 }
16792 anyhow::Ok(())
16793 })
16794 .detach();
16795 }
16796
16797 pub(crate) fn navigate_to_hover_links(
16798 &mut self,
16799 kind: Option<GotoDefinitionKind>,
16800 definitions: Vec<HoverLink>,
16801 split: bool,
16802 window: &mut Window,
16803 cx: &mut Context<Editor>,
16804 ) -> Task<Result<Navigated>> {
16805 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16806 let mut first_url_or_file = None;
16807 let definitions: Vec<_> = definitions
16808 .into_iter()
16809 .filter_map(|def| match def {
16810 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16811 HoverLink::InlayHint(lsp_location, server_id) => {
16812 let computation =
16813 self.compute_target_location(lsp_location, server_id, window, cx);
16814 Some(cx.background_spawn(computation))
16815 }
16816 HoverLink::Url(url) => {
16817 first_url_or_file = Some(Either::Left(url));
16818 None
16819 }
16820 HoverLink::File(path) => {
16821 first_url_or_file = Some(Either::Right(path));
16822 None
16823 }
16824 })
16825 .collect();
16826
16827 let workspace = self.workspace();
16828
16829 cx.spawn_in(window, async move |editor, cx| {
16830 let locations: Vec<Location> = future::join_all(definitions)
16831 .await
16832 .into_iter()
16833 .filter_map(|location| location.transpose())
16834 .collect::<Result<_>>()
16835 .context("location tasks")?;
16836 let mut locations = cx.update(|_, cx| {
16837 locations
16838 .into_iter()
16839 .map(|location| {
16840 let buffer = location.buffer.read(cx);
16841 (location.buffer, location.range.to_point(buffer))
16842 })
16843 .into_group_map()
16844 })?;
16845 let mut num_locations = 0;
16846 for ranges in locations.values_mut() {
16847 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16848 ranges.dedup();
16849 num_locations += ranges.len();
16850 }
16851
16852 if num_locations > 1 {
16853 let Some(workspace) = workspace else {
16854 return Ok(Navigated::No);
16855 };
16856
16857 let tab_kind = match kind {
16858 Some(GotoDefinitionKind::Implementation) => "Implementations",
16859 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16860 Some(GotoDefinitionKind::Declaration) => "Declarations",
16861 Some(GotoDefinitionKind::Type) => "Types",
16862 };
16863 let title = editor
16864 .update_in(cx, |_, _, cx| {
16865 let target = locations
16866 .iter()
16867 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16868 .map(|(buffer, location)| {
16869 buffer
16870 .read(cx)
16871 .text_for_range(location.clone())
16872 .collect::<String>()
16873 })
16874 .filter(|text| !text.contains('\n'))
16875 .unique()
16876 .take(3)
16877 .join(", ");
16878 if target.is_empty() {
16879 tab_kind.to_owned()
16880 } else {
16881 format!("{tab_kind} for {target}")
16882 }
16883 })
16884 .context("buffer title")?;
16885
16886 let opened = workspace
16887 .update_in(cx, |workspace, window, cx| {
16888 Self::open_locations_in_multibuffer(
16889 workspace,
16890 locations,
16891 title,
16892 split,
16893 MultibufferSelectionMode::First,
16894 window,
16895 cx,
16896 )
16897 })
16898 .is_ok();
16899
16900 anyhow::Ok(Navigated::from_bool(opened))
16901 } else if num_locations == 0 {
16902 // If there is one url or file, open it directly
16903 match first_url_or_file {
16904 Some(Either::Left(url)) => {
16905 cx.update(|_, cx| cx.open_url(&url))?;
16906 Ok(Navigated::Yes)
16907 }
16908 Some(Either::Right(path)) => {
16909 let Some(workspace) = workspace else {
16910 return Ok(Navigated::No);
16911 };
16912
16913 workspace
16914 .update_in(cx, |workspace, window, cx| {
16915 workspace.open_resolved_path(path, window, cx)
16916 })?
16917 .await?;
16918 Ok(Navigated::Yes)
16919 }
16920 None => Ok(Navigated::No),
16921 }
16922 } else {
16923 let Some(workspace) = workspace else {
16924 return Ok(Navigated::No);
16925 };
16926
16927 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16928 let target_range = target_ranges.first().unwrap().clone();
16929
16930 editor.update_in(cx, |editor, window, cx| {
16931 let range = target_range.to_point(target_buffer.read(cx));
16932 let range = editor.range_for_match(&range);
16933 let range = collapse_multiline_range(range);
16934
16935 if !split
16936 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16937 {
16938 editor.go_to_singleton_buffer_range(range, window, cx);
16939 } else {
16940 let pane = workspace.read(cx).active_pane().clone();
16941 window.defer(cx, move |window, cx| {
16942 let target_editor: Entity<Self> =
16943 workspace.update(cx, |workspace, cx| {
16944 let pane = if split {
16945 workspace.adjacent_pane(window, cx)
16946 } else {
16947 workspace.active_pane().clone()
16948 };
16949
16950 workspace.open_project_item(
16951 pane,
16952 target_buffer.clone(),
16953 true,
16954 true,
16955 window,
16956 cx,
16957 )
16958 });
16959 target_editor.update(cx, |target_editor, cx| {
16960 // When selecting a definition in a different buffer, disable the nav history
16961 // to avoid creating a history entry at the previous cursor location.
16962 pane.update(cx, |pane, _| pane.disable_history());
16963 target_editor.go_to_singleton_buffer_range(range, window, cx);
16964 pane.update(cx, |pane, _| pane.enable_history());
16965 });
16966 });
16967 }
16968 Navigated::Yes
16969 })
16970 }
16971 })
16972 }
16973
16974 fn compute_target_location(
16975 &self,
16976 lsp_location: lsp::Location,
16977 server_id: LanguageServerId,
16978 window: &mut Window,
16979 cx: &mut Context<Self>,
16980 ) -> Task<anyhow::Result<Option<Location>>> {
16981 let Some(project) = self.project.clone() else {
16982 return Task::ready(Ok(None));
16983 };
16984
16985 cx.spawn_in(window, async move |editor, cx| {
16986 let location_task = editor.update(cx, |_, cx| {
16987 project.update(cx, |project, cx| {
16988 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16989 })
16990 })?;
16991 let location = Some({
16992 let target_buffer_handle = location_task.await.context("open local buffer")?;
16993 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16994 let target_start = target_buffer
16995 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16996 let target_end = target_buffer
16997 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16998 target_buffer.anchor_after(target_start)
16999 ..target_buffer.anchor_before(target_end)
17000 })?;
17001 Location {
17002 buffer: target_buffer_handle,
17003 range,
17004 }
17005 });
17006 Ok(location)
17007 })
17008 }
17009
17010 fn go_to_next_reference(
17011 &mut self,
17012 _: &GoToNextReference,
17013 window: &mut Window,
17014 cx: &mut Context<Self>,
17015 ) {
17016 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17017 if let Some(task) = task {
17018 task.detach();
17019 };
17020 }
17021
17022 fn go_to_prev_reference(
17023 &mut self,
17024 _: &GoToPreviousReference,
17025 window: &mut Window,
17026 cx: &mut Context<Self>,
17027 ) {
17028 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17029 if let Some(task) = task {
17030 task.detach();
17031 };
17032 }
17033
17034 pub fn go_to_reference_before_or_after_position(
17035 &mut self,
17036 direction: Direction,
17037 count: usize,
17038 window: &mut Window,
17039 cx: &mut Context<Self>,
17040 ) -> Option<Task<Result<()>>> {
17041 let selection = self.selections.newest_anchor();
17042 let head = selection.head();
17043
17044 let multi_buffer = self.buffer.read(cx);
17045
17046 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17047 let workspace = self.workspace()?;
17048 let project = workspace.read(cx).project().clone();
17049 let references =
17050 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17051 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17052 let Some(locations) = references.await? else {
17053 return Ok(());
17054 };
17055
17056 if locations.is_empty() {
17057 // totally normal - the cursor may be on something which is not
17058 // a symbol (e.g. a keyword)
17059 log::info!("no references found under cursor");
17060 return Ok(());
17061 }
17062
17063 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17064
17065 let multi_buffer_snapshot =
17066 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
17067
17068 let (locations, current_location_index) =
17069 multi_buffer.update(cx, |multi_buffer, cx| {
17070 let mut locations = locations
17071 .into_iter()
17072 .filter_map(|loc| {
17073 let start = multi_buffer.buffer_anchor_to_anchor(
17074 &loc.buffer,
17075 loc.range.start,
17076 cx,
17077 )?;
17078 let end = multi_buffer.buffer_anchor_to_anchor(
17079 &loc.buffer,
17080 loc.range.end,
17081 cx,
17082 )?;
17083 Some(start..end)
17084 })
17085 .collect::<Vec<_>>();
17086
17087 // There is an O(n) implementation, but given this list will be
17088 // small (usually <100 items), the extra O(log(n)) factor isn't
17089 // worth the (surprisingly large amount of) extra complexity.
17090 locations
17091 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17092
17093 let head_offset = head.to_offset(&multi_buffer_snapshot);
17094
17095 let current_location_index = locations.iter().position(|loc| {
17096 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17097 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17098 });
17099
17100 (locations, current_location_index)
17101 })?;
17102
17103 let Some(current_location_index) = current_location_index else {
17104 // This indicates something has gone wrong, because we already
17105 // handle the "no references" case above
17106 log::error!(
17107 "failed to find current reference under cursor. Total references: {}",
17108 locations.len()
17109 );
17110 return Ok(());
17111 };
17112
17113 let destination_location_index = match direction {
17114 Direction::Next => (current_location_index + count) % locations.len(),
17115 Direction::Prev => {
17116 (current_location_index + locations.len() - count % locations.len())
17117 % locations.len()
17118 }
17119 };
17120
17121 // TODO(cameron): is this needed?
17122 // the thinking is to avoid "jumping to the current location" (avoid
17123 // polluting "jumplist" in vim terms)
17124 if current_location_index == destination_location_index {
17125 return Ok(());
17126 }
17127
17128 let Range { start, end } = locations[destination_location_index];
17129
17130 editor.update_in(cx, |editor, window, cx| {
17131 let effects = SelectionEffects::default();
17132
17133 editor.unfold_ranges(&[start..end], false, false, cx);
17134 editor.change_selections(effects, window, cx, |s| {
17135 s.select_ranges([start..start]);
17136 });
17137 })?;
17138
17139 Ok(())
17140 }))
17141 }
17142
17143 pub fn find_all_references(
17144 &mut self,
17145 _: &FindAllReferences,
17146 window: &mut Window,
17147 cx: &mut Context<Self>,
17148 ) -> Option<Task<Result<Navigated>>> {
17149 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
17150 let multi_buffer = self.buffer.read(cx);
17151 let head = selection.head();
17152
17153 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17154 let head_anchor = multi_buffer_snapshot.anchor_at(
17155 head,
17156 if head < selection.tail() {
17157 Bias::Right
17158 } else {
17159 Bias::Left
17160 },
17161 );
17162
17163 match self
17164 .find_all_references_task_sources
17165 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17166 {
17167 Ok(_) => {
17168 log::info!(
17169 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17170 );
17171 return None;
17172 }
17173 Err(i) => {
17174 self.find_all_references_task_sources.insert(i, head_anchor);
17175 }
17176 }
17177
17178 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17179 let workspace = self.workspace()?;
17180 let project = workspace.read(cx).project().clone();
17181 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17182 Some(cx.spawn_in(window, async move |editor, cx| {
17183 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17184 if let Ok(i) = editor
17185 .find_all_references_task_sources
17186 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17187 {
17188 editor.find_all_references_task_sources.remove(i);
17189 }
17190 });
17191
17192 let Some(locations) = references.await? else {
17193 return anyhow::Ok(Navigated::No);
17194 };
17195 let mut locations = cx.update(|_, cx| {
17196 locations
17197 .into_iter()
17198 .map(|location| {
17199 let buffer = location.buffer.read(cx);
17200 (location.buffer, location.range.to_point(buffer))
17201 })
17202 .into_group_map()
17203 })?;
17204 if locations.is_empty() {
17205 return anyhow::Ok(Navigated::No);
17206 }
17207 for ranges in locations.values_mut() {
17208 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17209 ranges.dedup();
17210 }
17211
17212 workspace.update_in(cx, |workspace, window, cx| {
17213 let target = locations
17214 .iter()
17215 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17216 .map(|(buffer, location)| {
17217 buffer
17218 .read(cx)
17219 .text_for_range(location.clone())
17220 .collect::<String>()
17221 })
17222 .filter(|text| !text.contains('\n'))
17223 .unique()
17224 .take(3)
17225 .join(", ");
17226 let title = if target.is_empty() {
17227 "References".to_owned()
17228 } else {
17229 format!("References to {target}")
17230 };
17231 Self::open_locations_in_multibuffer(
17232 workspace,
17233 locations,
17234 title,
17235 false,
17236 MultibufferSelectionMode::First,
17237 window,
17238 cx,
17239 );
17240 Navigated::Yes
17241 })
17242 }))
17243 }
17244
17245 /// Opens a multibuffer with the given project locations in it
17246 pub fn open_locations_in_multibuffer(
17247 workspace: &mut Workspace,
17248 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17249 title: String,
17250 split: bool,
17251 multibuffer_selection_mode: MultibufferSelectionMode,
17252 window: &mut Window,
17253 cx: &mut Context<Workspace>,
17254 ) {
17255 if locations.is_empty() {
17256 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17257 return;
17258 }
17259
17260 let capability = workspace.project().read(cx).capability();
17261 let mut ranges = <Vec<Range<Anchor>>>::new();
17262
17263 // a key to find existing multibuffer editors with the same set of locations
17264 // to prevent us from opening more and more multibuffer tabs for searches and the like
17265 let mut key = (title.clone(), vec![]);
17266 let excerpt_buffer = cx.new(|cx| {
17267 let key = &mut key.1;
17268 let mut multibuffer = MultiBuffer::new(capability);
17269 for (buffer, mut ranges_for_buffer) in locations {
17270 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17271 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17272 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17273 PathKey::for_buffer(&buffer, cx),
17274 buffer.clone(),
17275 ranges_for_buffer,
17276 multibuffer_context_lines(cx),
17277 cx,
17278 );
17279 ranges.extend(new_ranges)
17280 }
17281
17282 multibuffer.with_title(title)
17283 });
17284 let existing = workspace.active_pane().update(cx, |pane, cx| {
17285 pane.items()
17286 .filter_map(|item| item.downcast::<Editor>())
17287 .find(|editor| {
17288 editor
17289 .read(cx)
17290 .lookup_key
17291 .as_ref()
17292 .and_then(|it| {
17293 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17294 })
17295 .is_some_and(|it| *it == key)
17296 })
17297 });
17298 let editor = existing.unwrap_or_else(|| {
17299 cx.new(|cx| {
17300 let mut editor = Editor::for_multibuffer(
17301 excerpt_buffer,
17302 Some(workspace.project().clone()),
17303 window,
17304 cx,
17305 );
17306 editor.lookup_key = Some(Box::new(key));
17307 editor
17308 })
17309 });
17310 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17311 MultibufferSelectionMode::First => {
17312 if let Some(first_range) = ranges.first() {
17313 editor.change_selections(
17314 SelectionEffects::no_scroll(),
17315 window,
17316 cx,
17317 |selections| {
17318 selections.clear_disjoint();
17319 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17320 },
17321 );
17322 }
17323 editor.highlight_background::<Self>(
17324 &ranges,
17325 |theme| theme.colors().editor_highlighted_line_background,
17326 cx,
17327 );
17328 }
17329 MultibufferSelectionMode::All => {
17330 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17331 selections.clear_disjoint();
17332 selections.select_anchor_ranges(ranges);
17333 });
17334 }
17335 });
17336
17337 let item = Box::new(editor);
17338 let item_id = item.item_id();
17339
17340 if split {
17341 let pane = workspace.adjacent_pane(window, cx);
17342 workspace.add_item(pane, item, None, true, true, window, cx);
17343 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17344 let (preview_item_id, preview_item_idx) =
17345 workspace.active_pane().read_with(cx, |pane, _| {
17346 (pane.preview_item_id(), pane.preview_item_idx())
17347 });
17348
17349 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17350
17351 if let Some(preview_item_id) = preview_item_id {
17352 workspace.active_pane().update(cx, |pane, cx| {
17353 pane.remove_item(preview_item_id, false, false, window, cx);
17354 });
17355 }
17356 } else {
17357 workspace.add_item_to_active_pane(item, None, true, window, cx);
17358 }
17359 workspace.active_pane().update(cx, |pane, cx| {
17360 pane.set_preview_item_id(Some(item_id), cx);
17361 });
17362 }
17363
17364 pub fn rename(
17365 &mut self,
17366 _: &Rename,
17367 window: &mut Window,
17368 cx: &mut Context<Self>,
17369 ) -> Option<Task<Result<()>>> {
17370 use language::ToOffset as _;
17371
17372 let provider = self.semantics_provider.clone()?;
17373 let selection = self.selections.newest_anchor().clone();
17374 let (cursor_buffer, cursor_buffer_position) = self
17375 .buffer
17376 .read(cx)
17377 .text_anchor_for_position(selection.head(), cx)?;
17378 let (tail_buffer, cursor_buffer_position_end) = self
17379 .buffer
17380 .read(cx)
17381 .text_anchor_for_position(selection.tail(), cx)?;
17382 if tail_buffer != cursor_buffer {
17383 return None;
17384 }
17385
17386 let snapshot = cursor_buffer.read(cx).snapshot();
17387 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17388 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17389 let prepare_rename = provider
17390 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17391 .unwrap_or_else(|| Task::ready(Ok(None)));
17392 drop(snapshot);
17393
17394 Some(cx.spawn_in(window, async move |this, cx| {
17395 let rename_range = if let Some(range) = prepare_rename.await? {
17396 Some(range)
17397 } else {
17398 this.update(cx, |this, cx| {
17399 let buffer = this.buffer.read(cx).snapshot(cx);
17400 let mut buffer_highlights = this
17401 .document_highlights_for_position(selection.head(), &buffer)
17402 .filter(|highlight| {
17403 highlight.start.excerpt_id == selection.head().excerpt_id
17404 && highlight.end.excerpt_id == selection.head().excerpt_id
17405 });
17406 buffer_highlights
17407 .next()
17408 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17409 })?
17410 };
17411 if let Some(rename_range) = rename_range {
17412 this.update_in(cx, |this, window, cx| {
17413 let snapshot = cursor_buffer.read(cx).snapshot();
17414 let rename_buffer_range = rename_range.to_offset(&snapshot);
17415 let cursor_offset_in_rename_range =
17416 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17417 let cursor_offset_in_rename_range_end =
17418 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17419
17420 this.take_rename(false, window, cx);
17421 let buffer = this.buffer.read(cx).read(cx);
17422 let cursor_offset = selection.head().to_offset(&buffer);
17423 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17424 let rename_end = rename_start + rename_buffer_range.len();
17425 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17426 let mut old_highlight_id = None;
17427 let old_name: Arc<str> = buffer
17428 .chunks(rename_start..rename_end, true)
17429 .map(|chunk| {
17430 if old_highlight_id.is_none() {
17431 old_highlight_id = chunk.syntax_highlight_id;
17432 }
17433 chunk.text
17434 })
17435 .collect::<String>()
17436 .into();
17437
17438 drop(buffer);
17439
17440 // Position the selection in the rename editor so that it matches the current selection.
17441 this.show_local_selections = false;
17442 let rename_editor = cx.new(|cx| {
17443 let mut editor = Editor::single_line(window, cx);
17444 editor.buffer.update(cx, |buffer, cx| {
17445 buffer.edit([(0..0, old_name.clone())], None, cx)
17446 });
17447 let rename_selection_range = match cursor_offset_in_rename_range
17448 .cmp(&cursor_offset_in_rename_range_end)
17449 {
17450 Ordering::Equal => {
17451 editor.select_all(&SelectAll, window, cx);
17452 return editor;
17453 }
17454 Ordering::Less => {
17455 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17456 }
17457 Ordering::Greater => {
17458 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17459 }
17460 };
17461 if rename_selection_range.end > old_name.len() {
17462 editor.select_all(&SelectAll, window, cx);
17463 } else {
17464 editor.change_selections(Default::default(), window, cx, |s| {
17465 s.select_ranges([rename_selection_range]);
17466 });
17467 }
17468 editor
17469 });
17470 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17471 if e == &EditorEvent::Focused {
17472 cx.emit(EditorEvent::FocusedIn)
17473 }
17474 })
17475 .detach();
17476
17477 let write_highlights =
17478 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17479 let read_highlights =
17480 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17481 let ranges = write_highlights
17482 .iter()
17483 .flat_map(|(_, ranges)| ranges.iter())
17484 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17485 .cloned()
17486 .collect();
17487
17488 this.highlight_text::<Rename>(
17489 ranges,
17490 HighlightStyle {
17491 fade_out: Some(0.6),
17492 ..Default::default()
17493 },
17494 cx,
17495 );
17496 let rename_focus_handle = rename_editor.focus_handle(cx);
17497 window.focus(&rename_focus_handle);
17498 let block_id = this.insert_blocks(
17499 [BlockProperties {
17500 style: BlockStyle::Flex,
17501 placement: BlockPlacement::Below(range.start),
17502 height: Some(1),
17503 render: Arc::new({
17504 let rename_editor = rename_editor.clone();
17505 move |cx: &mut BlockContext| {
17506 let mut text_style = cx.editor_style.text.clone();
17507 if let Some(highlight_style) = old_highlight_id
17508 .and_then(|h| h.style(&cx.editor_style.syntax))
17509 {
17510 text_style = text_style.highlight(highlight_style);
17511 }
17512 div()
17513 .block_mouse_except_scroll()
17514 .pl(cx.anchor_x)
17515 .child(EditorElement::new(
17516 &rename_editor,
17517 EditorStyle {
17518 background: cx.theme().system().transparent,
17519 local_player: cx.editor_style.local_player,
17520 text: text_style,
17521 scrollbar_width: cx.editor_style.scrollbar_width,
17522 syntax: cx.editor_style.syntax.clone(),
17523 status: cx.editor_style.status.clone(),
17524 inlay_hints_style: HighlightStyle {
17525 font_weight: Some(FontWeight::BOLD),
17526 ..make_inlay_hints_style(cx.app)
17527 },
17528 edit_prediction_styles: make_suggestion_styles(
17529 cx.app,
17530 ),
17531 ..EditorStyle::default()
17532 },
17533 ))
17534 .into_any_element()
17535 }
17536 }),
17537 priority: 0,
17538 }],
17539 Some(Autoscroll::fit()),
17540 cx,
17541 )[0];
17542 this.pending_rename = Some(RenameState {
17543 range,
17544 old_name,
17545 editor: rename_editor,
17546 block_id,
17547 });
17548 })?;
17549 }
17550
17551 Ok(())
17552 }))
17553 }
17554
17555 pub fn confirm_rename(
17556 &mut self,
17557 _: &ConfirmRename,
17558 window: &mut Window,
17559 cx: &mut Context<Self>,
17560 ) -> Option<Task<Result<()>>> {
17561 let rename = self.take_rename(false, window, cx)?;
17562 let workspace = self.workspace()?.downgrade();
17563 let (buffer, start) = self
17564 .buffer
17565 .read(cx)
17566 .text_anchor_for_position(rename.range.start, cx)?;
17567 let (end_buffer, _) = self
17568 .buffer
17569 .read(cx)
17570 .text_anchor_for_position(rename.range.end, cx)?;
17571 if buffer != end_buffer {
17572 return None;
17573 }
17574
17575 let old_name = rename.old_name;
17576 let new_name = rename.editor.read(cx).text(cx);
17577
17578 let rename = self.semantics_provider.as_ref()?.perform_rename(
17579 &buffer,
17580 start,
17581 new_name.clone(),
17582 cx,
17583 )?;
17584
17585 Some(cx.spawn_in(window, async move |editor, cx| {
17586 let project_transaction = rename.await?;
17587 Self::open_project_transaction(
17588 &editor,
17589 workspace,
17590 project_transaction,
17591 format!("Rename: {} → {}", old_name, new_name),
17592 cx,
17593 )
17594 .await?;
17595
17596 editor.update(cx, |editor, cx| {
17597 editor.refresh_document_highlights(cx);
17598 })?;
17599 Ok(())
17600 }))
17601 }
17602
17603 fn take_rename(
17604 &mut self,
17605 moving_cursor: bool,
17606 window: &mut Window,
17607 cx: &mut Context<Self>,
17608 ) -> Option<RenameState> {
17609 let rename = self.pending_rename.take()?;
17610 if rename.editor.focus_handle(cx).is_focused(window) {
17611 window.focus(&self.focus_handle);
17612 }
17613
17614 self.remove_blocks(
17615 [rename.block_id].into_iter().collect(),
17616 Some(Autoscroll::fit()),
17617 cx,
17618 );
17619 self.clear_highlights::<Rename>(cx);
17620 self.show_local_selections = true;
17621
17622 if moving_cursor {
17623 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17624 editor
17625 .selections
17626 .newest::<usize>(&editor.display_snapshot(cx))
17627 .head()
17628 });
17629
17630 // Update the selection to match the position of the selection inside
17631 // the rename editor.
17632 let snapshot = self.buffer.read(cx).read(cx);
17633 let rename_range = rename.range.to_offset(&snapshot);
17634 let cursor_in_editor = snapshot
17635 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17636 .min(rename_range.end);
17637 drop(snapshot);
17638
17639 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17640 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17641 });
17642 } else {
17643 self.refresh_document_highlights(cx);
17644 }
17645
17646 Some(rename)
17647 }
17648
17649 pub fn pending_rename(&self) -> Option<&RenameState> {
17650 self.pending_rename.as_ref()
17651 }
17652
17653 fn format(
17654 &mut self,
17655 _: &Format,
17656 window: &mut Window,
17657 cx: &mut Context<Self>,
17658 ) -> Option<Task<Result<()>>> {
17659 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17660
17661 let project = match &self.project {
17662 Some(project) => project.clone(),
17663 None => return None,
17664 };
17665
17666 Some(self.perform_format(
17667 project,
17668 FormatTrigger::Manual,
17669 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17670 window,
17671 cx,
17672 ))
17673 }
17674
17675 fn format_selections(
17676 &mut self,
17677 _: &FormatSelections,
17678 window: &mut Window,
17679 cx: &mut Context<Self>,
17680 ) -> Option<Task<Result<()>>> {
17681 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17682
17683 let project = match &self.project {
17684 Some(project) => project.clone(),
17685 None => return None,
17686 };
17687
17688 let ranges = self
17689 .selections
17690 .all_adjusted(&self.display_snapshot(cx))
17691 .into_iter()
17692 .map(|selection| selection.range())
17693 .collect_vec();
17694
17695 Some(self.perform_format(
17696 project,
17697 FormatTrigger::Manual,
17698 FormatTarget::Ranges(ranges),
17699 window,
17700 cx,
17701 ))
17702 }
17703
17704 fn perform_format(
17705 &mut self,
17706 project: Entity<Project>,
17707 trigger: FormatTrigger,
17708 target: FormatTarget,
17709 window: &mut Window,
17710 cx: &mut Context<Self>,
17711 ) -> Task<Result<()>> {
17712 let buffer = self.buffer.clone();
17713 let (buffers, target) = match target {
17714 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17715 FormatTarget::Ranges(selection_ranges) => {
17716 let multi_buffer = buffer.read(cx);
17717 let snapshot = multi_buffer.read(cx);
17718 let mut buffers = HashSet::default();
17719 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17720 BTreeMap::new();
17721 for selection_range in selection_ranges {
17722 for (buffer, buffer_range, _) in
17723 snapshot.range_to_buffer_ranges(selection_range)
17724 {
17725 let buffer_id = buffer.remote_id();
17726 let start = buffer.anchor_before(buffer_range.start);
17727 let end = buffer.anchor_after(buffer_range.end);
17728 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17729 buffer_id_to_ranges
17730 .entry(buffer_id)
17731 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17732 .or_insert_with(|| vec![start..end]);
17733 }
17734 }
17735 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17736 }
17737 };
17738
17739 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17740 let selections_prev = transaction_id_prev
17741 .and_then(|transaction_id_prev| {
17742 // default to selections as they were after the last edit, if we have them,
17743 // instead of how they are now.
17744 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17745 // will take you back to where you made the last edit, instead of staying where you scrolled
17746 self.selection_history
17747 .transaction(transaction_id_prev)
17748 .map(|t| t.0.clone())
17749 })
17750 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17751
17752 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17753 let format = project.update(cx, |project, cx| {
17754 project.format(buffers, target, true, trigger, cx)
17755 });
17756
17757 cx.spawn_in(window, async move |editor, cx| {
17758 let transaction = futures::select_biased! {
17759 transaction = format.log_err().fuse() => transaction,
17760 () = timeout => {
17761 log::warn!("timed out waiting for formatting");
17762 None
17763 }
17764 };
17765
17766 buffer
17767 .update(cx, |buffer, cx| {
17768 if let Some(transaction) = transaction
17769 && !buffer.is_singleton()
17770 {
17771 buffer.push_transaction(&transaction.0, cx);
17772 }
17773 cx.notify();
17774 })
17775 .ok();
17776
17777 if let Some(transaction_id_now) =
17778 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17779 {
17780 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17781 if has_new_transaction {
17782 _ = editor.update(cx, |editor, _| {
17783 editor
17784 .selection_history
17785 .insert_transaction(transaction_id_now, selections_prev);
17786 });
17787 }
17788 }
17789
17790 Ok(())
17791 })
17792 }
17793
17794 fn organize_imports(
17795 &mut self,
17796 _: &OrganizeImports,
17797 window: &mut Window,
17798 cx: &mut Context<Self>,
17799 ) -> Option<Task<Result<()>>> {
17800 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17801 let project = match &self.project {
17802 Some(project) => project.clone(),
17803 None => return None,
17804 };
17805 Some(self.perform_code_action_kind(
17806 project,
17807 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17808 window,
17809 cx,
17810 ))
17811 }
17812
17813 fn perform_code_action_kind(
17814 &mut self,
17815 project: Entity<Project>,
17816 kind: CodeActionKind,
17817 window: &mut Window,
17818 cx: &mut Context<Self>,
17819 ) -> Task<Result<()>> {
17820 let buffer = self.buffer.clone();
17821 let buffers = buffer.read(cx).all_buffers();
17822 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17823 let apply_action = project.update(cx, |project, cx| {
17824 project.apply_code_action_kind(buffers, kind, true, cx)
17825 });
17826 cx.spawn_in(window, async move |_, cx| {
17827 let transaction = futures::select_biased! {
17828 () = timeout => {
17829 log::warn!("timed out waiting for executing code action");
17830 None
17831 }
17832 transaction = apply_action.log_err().fuse() => transaction,
17833 };
17834 buffer
17835 .update(cx, |buffer, cx| {
17836 // check if we need this
17837 if let Some(transaction) = transaction
17838 && !buffer.is_singleton()
17839 {
17840 buffer.push_transaction(&transaction.0, cx);
17841 }
17842 cx.notify();
17843 })
17844 .ok();
17845 Ok(())
17846 })
17847 }
17848
17849 pub fn restart_language_server(
17850 &mut self,
17851 _: &RestartLanguageServer,
17852 _: &mut Window,
17853 cx: &mut Context<Self>,
17854 ) {
17855 if let Some(project) = self.project.clone() {
17856 self.buffer.update(cx, |multi_buffer, cx| {
17857 project.update(cx, |project, cx| {
17858 project.restart_language_servers_for_buffers(
17859 multi_buffer.all_buffers().into_iter().collect(),
17860 HashSet::default(),
17861 cx,
17862 );
17863 });
17864 })
17865 }
17866 }
17867
17868 pub fn stop_language_server(
17869 &mut self,
17870 _: &StopLanguageServer,
17871 _: &mut Window,
17872 cx: &mut Context<Self>,
17873 ) {
17874 if let Some(project) = self.project.clone() {
17875 self.buffer.update(cx, |multi_buffer, cx| {
17876 project.update(cx, |project, cx| {
17877 project.stop_language_servers_for_buffers(
17878 multi_buffer.all_buffers().into_iter().collect(),
17879 HashSet::default(),
17880 cx,
17881 );
17882 });
17883 });
17884 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17885 }
17886 }
17887
17888 fn cancel_language_server_work(
17889 workspace: &mut Workspace,
17890 _: &actions::CancelLanguageServerWork,
17891 _: &mut Window,
17892 cx: &mut Context<Workspace>,
17893 ) {
17894 let project = workspace.project();
17895 let buffers = workspace
17896 .active_item(cx)
17897 .and_then(|item| item.act_as::<Editor>(cx))
17898 .map_or(HashSet::default(), |editor| {
17899 editor.read(cx).buffer.read(cx).all_buffers()
17900 });
17901 project.update(cx, |project, cx| {
17902 project.cancel_language_server_work_for_buffers(buffers, cx);
17903 });
17904 }
17905
17906 fn show_character_palette(
17907 &mut self,
17908 _: &ShowCharacterPalette,
17909 window: &mut Window,
17910 _: &mut Context<Self>,
17911 ) {
17912 window.show_character_palette();
17913 }
17914
17915 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17916 if !self.diagnostics_enabled() {
17917 return;
17918 }
17919
17920 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17921 let buffer = self.buffer.read(cx).snapshot(cx);
17922 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17923 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17924 let is_valid = buffer
17925 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17926 .any(|entry| {
17927 entry.diagnostic.is_primary
17928 && !entry.range.is_empty()
17929 && entry.range.start == primary_range_start
17930 && entry.diagnostic.message == active_diagnostics.active_message
17931 });
17932
17933 if !is_valid {
17934 self.dismiss_diagnostics(cx);
17935 }
17936 }
17937 }
17938
17939 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17940 match &self.active_diagnostics {
17941 ActiveDiagnostic::Group(group) => Some(group),
17942 _ => None,
17943 }
17944 }
17945
17946 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17947 if !self.diagnostics_enabled() {
17948 return;
17949 }
17950 self.dismiss_diagnostics(cx);
17951 self.active_diagnostics = ActiveDiagnostic::All;
17952 }
17953
17954 fn activate_diagnostics(
17955 &mut self,
17956 buffer_id: BufferId,
17957 diagnostic: DiagnosticEntryRef<'_, usize>,
17958 window: &mut Window,
17959 cx: &mut Context<Self>,
17960 ) {
17961 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17962 return;
17963 }
17964 self.dismiss_diagnostics(cx);
17965 let snapshot = self.snapshot(window, cx);
17966 let buffer = self.buffer.read(cx).snapshot(cx);
17967 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17968 return;
17969 };
17970
17971 let diagnostic_group = buffer
17972 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17973 .collect::<Vec<_>>();
17974
17975 let language_registry = self
17976 .project()
17977 .map(|project| project.read(cx).languages().clone());
17978
17979 let blocks = renderer.render_group(
17980 diagnostic_group,
17981 buffer_id,
17982 snapshot,
17983 cx.weak_entity(),
17984 language_registry,
17985 cx,
17986 );
17987
17988 let blocks = self.display_map.update(cx, |display_map, cx| {
17989 display_map.insert_blocks(blocks, cx).into_iter().collect()
17990 });
17991 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17992 active_range: buffer.anchor_before(diagnostic.range.start)
17993 ..buffer.anchor_after(diagnostic.range.end),
17994 active_message: diagnostic.diagnostic.message.clone(),
17995 group_id: diagnostic.diagnostic.group_id,
17996 blocks,
17997 });
17998 cx.notify();
17999 }
18000
18001 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18002 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18003 return;
18004 };
18005
18006 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18007 if let ActiveDiagnostic::Group(group) = prev {
18008 self.display_map.update(cx, |display_map, cx| {
18009 display_map.remove_blocks(group.blocks, cx);
18010 });
18011 cx.notify();
18012 }
18013 }
18014
18015 /// Disable inline diagnostics rendering for this editor.
18016 pub fn disable_inline_diagnostics(&mut self) {
18017 self.inline_diagnostics_enabled = false;
18018 self.inline_diagnostics_update = Task::ready(());
18019 self.inline_diagnostics.clear();
18020 }
18021
18022 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18023 self.diagnostics_enabled = false;
18024 self.dismiss_diagnostics(cx);
18025 self.inline_diagnostics_update = Task::ready(());
18026 self.inline_diagnostics.clear();
18027 }
18028
18029 pub fn disable_word_completions(&mut self) {
18030 self.word_completions_enabled = false;
18031 }
18032
18033 pub fn diagnostics_enabled(&self) -> bool {
18034 self.diagnostics_enabled && self.mode.is_full()
18035 }
18036
18037 pub fn inline_diagnostics_enabled(&self) -> bool {
18038 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18039 }
18040
18041 pub fn show_inline_diagnostics(&self) -> bool {
18042 self.show_inline_diagnostics
18043 }
18044
18045 pub fn toggle_inline_diagnostics(
18046 &mut self,
18047 _: &ToggleInlineDiagnostics,
18048 window: &mut Window,
18049 cx: &mut Context<Editor>,
18050 ) {
18051 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18052 self.refresh_inline_diagnostics(false, window, cx);
18053 }
18054
18055 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18056 self.diagnostics_max_severity = severity;
18057 self.display_map.update(cx, |display_map, _| {
18058 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18059 });
18060 }
18061
18062 pub fn toggle_diagnostics(
18063 &mut self,
18064 _: &ToggleDiagnostics,
18065 window: &mut Window,
18066 cx: &mut Context<Editor>,
18067 ) {
18068 if !self.diagnostics_enabled() {
18069 return;
18070 }
18071
18072 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18073 EditorSettings::get_global(cx)
18074 .diagnostics_max_severity
18075 .filter(|severity| severity != &DiagnosticSeverity::Off)
18076 .unwrap_or(DiagnosticSeverity::Hint)
18077 } else {
18078 DiagnosticSeverity::Off
18079 };
18080 self.set_max_diagnostics_severity(new_severity, cx);
18081 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18082 self.active_diagnostics = ActiveDiagnostic::None;
18083 self.inline_diagnostics_update = Task::ready(());
18084 self.inline_diagnostics.clear();
18085 } else {
18086 self.refresh_inline_diagnostics(false, window, cx);
18087 }
18088
18089 cx.notify();
18090 }
18091
18092 pub fn toggle_minimap(
18093 &mut self,
18094 _: &ToggleMinimap,
18095 window: &mut Window,
18096 cx: &mut Context<Editor>,
18097 ) {
18098 if self.supports_minimap(cx) {
18099 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18100 }
18101 }
18102
18103 fn refresh_inline_diagnostics(
18104 &mut self,
18105 debounce: bool,
18106 window: &mut Window,
18107 cx: &mut Context<Self>,
18108 ) {
18109 let max_severity = ProjectSettings::get_global(cx)
18110 .diagnostics
18111 .inline
18112 .max_severity
18113 .unwrap_or(self.diagnostics_max_severity);
18114
18115 if !self.inline_diagnostics_enabled()
18116 || !self.diagnostics_enabled()
18117 || !self.show_inline_diagnostics
18118 || max_severity == DiagnosticSeverity::Off
18119 {
18120 self.inline_diagnostics_update = Task::ready(());
18121 self.inline_diagnostics.clear();
18122 return;
18123 }
18124
18125 let debounce_ms = ProjectSettings::get_global(cx)
18126 .diagnostics
18127 .inline
18128 .update_debounce_ms;
18129 let debounce = if debounce && debounce_ms > 0 {
18130 Some(Duration::from_millis(debounce_ms))
18131 } else {
18132 None
18133 };
18134 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18135 if let Some(debounce) = debounce {
18136 cx.background_executor().timer(debounce).await;
18137 }
18138 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18139 editor
18140 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18141 .ok()
18142 }) else {
18143 return;
18144 };
18145
18146 let new_inline_diagnostics = cx
18147 .background_spawn(async move {
18148 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18149 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
18150 let message = diagnostic_entry
18151 .diagnostic
18152 .message
18153 .split_once('\n')
18154 .map(|(line, _)| line)
18155 .map(SharedString::new)
18156 .unwrap_or_else(|| {
18157 SharedString::new(&*diagnostic_entry.diagnostic.message)
18158 });
18159 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18160 let (Ok(i) | Err(i)) = inline_diagnostics
18161 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18162 inline_diagnostics.insert(
18163 i,
18164 (
18165 start_anchor,
18166 InlineDiagnostic {
18167 message,
18168 group_id: diagnostic_entry.diagnostic.group_id,
18169 start: diagnostic_entry.range.start.to_point(&snapshot),
18170 is_primary: diagnostic_entry.diagnostic.is_primary,
18171 severity: diagnostic_entry.diagnostic.severity,
18172 },
18173 ),
18174 );
18175 }
18176 inline_diagnostics
18177 })
18178 .await;
18179
18180 editor
18181 .update(cx, |editor, cx| {
18182 editor.inline_diagnostics = new_inline_diagnostics;
18183 cx.notify();
18184 })
18185 .ok();
18186 });
18187 }
18188
18189 fn pull_diagnostics(
18190 &mut self,
18191 buffer_id: Option<BufferId>,
18192 window: &Window,
18193 cx: &mut Context<Self>,
18194 ) -> Option<()> {
18195 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18196 return None;
18197 }
18198 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18199 .diagnostics
18200 .lsp_pull_diagnostics;
18201 if !pull_diagnostics_settings.enabled {
18202 return None;
18203 }
18204 let project = self.project()?.downgrade();
18205 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18206 let mut buffers = self.buffer.read(cx).all_buffers();
18207 buffers.retain(|buffer| {
18208 let buffer_id_to_retain = buffer.read(cx).remote_id();
18209 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18210 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18211 });
18212 if buffers.is_empty() {
18213 self.pull_diagnostics_task = Task::ready(());
18214 return None;
18215 }
18216
18217 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18218 cx.background_executor().timer(debounce).await;
18219
18220 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18221 buffers
18222 .into_iter()
18223 .filter_map(|buffer| {
18224 project
18225 .update(cx, |project, cx| {
18226 project.lsp_store().update(cx, |lsp_store, cx| {
18227 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18228 })
18229 })
18230 .ok()
18231 })
18232 .collect::<FuturesUnordered<_>>()
18233 }) else {
18234 return;
18235 };
18236
18237 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18238 match pull_task {
18239 Ok(()) => {
18240 if editor
18241 .update_in(cx, |editor, window, cx| {
18242 editor.update_diagnostics_state(window, cx);
18243 })
18244 .is_err()
18245 {
18246 return;
18247 }
18248 }
18249 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18250 }
18251 }
18252 });
18253
18254 Some(())
18255 }
18256
18257 pub fn set_selections_from_remote(
18258 &mut self,
18259 selections: Vec<Selection<Anchor>>,
18260 pending_selection: Option<Selection<Anchor>>,
18261 window: &mut Window,
18262 cx: &mut Context<Self>,
18263 ) {
18264 let old_cursor_position = self.selections.newest_anchor().head();
18265 self.selections
18266 .change_with(&self.display_snapshot(cx), |s| {
18267 s.select_anchors(selections);
18268 if let Some(pending_selection) = pending_selection {
18269 s.set_pending(pending_selection, SelectMode::Character);
18270 } else {
18271 s.clear_pending();
18272 }
18273 });
18274 self.selections_did_change(
18275 false,
18276 &old_cursor_position,
18277 SelectionEffects::default(),
18278 window,
18279 cx,
18280 );
18281 }
18282
18283 pub fn transact(
18284 &mut self,
18285 window: &mut Window,
18286 cx: &mut Context<Self>,
18287 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18288 ) -> Option<TransactionId> {
18289 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18290 this.start_transaction_at(Instant::now(), window, cx);
18291 update(this, window, cx);
18292 this.end_transaction_at(Instant::now(), cx)
18293 })
18294 }
18295
18296 pub fn start_transaction_at(
18297 &mut self,
18298 now: Instant,
18299 window: &mut Window,
18300 cx: &mut Context<Self>,
18301 ) -> Option<TransactionId> {
18302 self.end_selection(window, cx);
18303 if let Some(tx_id) = self
18304 .buffer
18305 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18306 {
18307 self.selection_history
18308 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18309 cx.emit(EditorEvent::TransactionBegun {
18310 transaction_id: tx_id,
18311 });
18312 Some(tx_id)
18313 } else {
18314 None
18315 }
18316 }
18317
18318 pub fn end_transaction_at(
18319 &mut self,
18320 now: Instant,
18321 cx: &mut Context<Self>,
18322 ) -> Option<TransactionId> {
18323 if let Some(transaction_id) = self
18324 .buffer
18325 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18326 {
18327 if let Some((_, end_selections)) =
18328 self.selection_history.transaction_mut(transaction_id)
18329 {
18330 *end_selections = Some(self.selections.disjoint_anchors_arc());
18331 } else {
18332 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18333 }
18334
18335 cx.emit(EditorEvent::Edited { transaction_id });
18336 Some(transaction_id)
18337 } else {
18338 None
18339 }
18340 }
18341
18342 pub fn modify_transaction_selection_history(
18343 &mut self,
18344 transaction_id: TransactionId,
18345 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18346 ) -> bool {
18347 self.selection_history
18348 .transaction_mut(transaction_id)
18349 .map(modify)
18350 .is_some()
18351 }
18352
18353 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18354 if self.selection_mark_mode {
18355 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18356 s.move_with(|_, sel| {
18357 sel.collapse_to(sel.head(), SelectionGoal::None);
18358 });
18359 })
18360 }
18361 self.selection_mark_mode = true;
18362 cx.notify();
18363 }
18364
18365 pub fn swap_selection_ends(
18366 &mut self,
18367 _: &actions::SwapSelectionEnds,
18368 window: &mut Window,
18369 cx: &mut Context<Self>,
18370 ) {
18371 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18372 s.move_with(|_, sel| {
18373 if sel.start != sel.end {
18374 sel.reversed = !sel.reversed
18375 }
18376 });
18377 });
18378 self.request_autoscroll(Autoscroll::newest(), cx);
18379 cx.notify();
18380 }
18381
18382 pub fn toggle_focus(
18383 workspace: &mut Workspace,
18384 _: &actions::ToggleFocus,
18385 window: &mut Window,
18386 cx: &mut Context<Workspace>,
18387 ) {
18388 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18389 return;
18390 };
18391 workspace.activate_item(&item, true, true, window, cx);
18392 }
18393
18394 pub fn toggle_fold(
18395 &mut self,
18396 _: &actions::ToggleFold,
18397 window: &mut Window,
18398 cx: &mut Context<Self>,
18399 ) {
18400 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18401 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18402 let selection = self.selections.newest::<Point>(&display_map);
18403
18404 let range = if selection.is_empty() {
18405 let point = selection.head().to_display_point(&display_map);
18406 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18407 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18408 .to_point(&display_map);
18409 start..end
18410 } else {
18411 selection.range()
18412 };
18413 if display_map.folds_in_range(range).next().is_some() {
18414 self.unfold_lines(&Default::default(), window, cx)
18415 } else {
18416 self.fold(&Default::default(), window, cx)
18417 }
18418 } else {
18419 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18420 let buffer_ids: HashSet<_> = self
18421 .selections
18422 .disjoint_anchor_ranges()
18423 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18424 .collect();
18425
18426 let should_unfold = buffer_ids
18427 .iter()
18428 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18429
18430 for buffer_id in buffer_ids {
18431 if should_unfold {
18432 self.unfold_buffer(buffer_id, cx);
18433 } else {
18434 self.fold_buffer(buffer_id, cx);
18435 }
18436 }
18437 }
18438 }
18439
18440 pub fn toggle_fold_recursive(
18441 &mut self,
18442 _: &actions::ToggleFoldRecursive,
18443 window: &mut Window,
18444 cx: &mut Context<Self>,
18445 ) {
18446 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18447
18448 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18449 let range = if selection.is_empty() {
18450 let point = selection.head().to_display_point(&display_map);
18451 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18452 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18453 .to_point(&display_map);
18454 start..end
18455 } else {
18456 selection.range()
18457 };
18458 if display_map.folds_in_range(range).next().is_some() {
18459 self.unfold_recursive(&Default::default(), window, cx)
18460 } else {
18461 self.fold_recursive(&Default::default(), window, cx)
18462 }
18463 }
18464
18465 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18466 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18467 let mut to_fold = Vec::new();
18468 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18469 let selections = self.selections.all_adjusted(&display_map);
18470
18471 for selection in selections {
18472 let range = selection.range().sorted();
18473 let buffer_start_row = range.start.row;
18474
18475 if range.start.row != range.end.row {
18476 let mut found = false;
18477 let mut row = range.start.row;
18478 while row <= range.end.row {
18479 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18480 {
18481 found = true;
18482 row = crease.range().end.row + 1;
18483 to_fold.push(crease);
18484 } else {
18485 row += 1
18486 }
18487 }
18488 if found {
18489 continue;
18490 }
18491 }
18492
18493 for row in (0..=range.start.row).rev() {
18494 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18495 && crease.range().end.row >= buffer_start_row
18496 {
18497 to_fold.push(crease);
18498 if row <= range.start.row {
18499 break;
18500 }
18501 }
18502 }
18503 }
18504
18505 self.fold_creases(to_fold, true, window, cx);
18506 } else {
18507 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18508 let buffer_ids = self
18509 .selections
18510 .disjoint_anchor_ranges()
18511 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18512 .collect::<HashSet<_>>();
18513 for buffer_id in buffer_ids {
18514 self.fold_buffer(buffer_id, cx);
18515 }
18516 }
18517 }
18518
18519 pub fn toggle_fold_all(
18520 &mut self,
18521 _: &actions::ToggleFoldAll,
18522 window: &mut Window,
18523 cx: &mut Context<Self>,
18524 ) {
18525 if self.buffer.read(cx).is_singleton() {
18526 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18527 let has_folds = display_map
18528 .folds_in_range(0..display_map.buffer_snapshot().len())
18529 .next()
18530 .is_some();
18531
18532 if has_folds {
18533 self.unfold_all(&actions::UnfoldAll, window, cx);
18534 } else {
18535 self.fold_all(&actions::FoldAll, window, cx);
18536 }
18537 } else {
18538 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18539 let should_unfold = buffer_ids
18540 .iter()
18541 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18542
18543 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18544 editor
18545 .update_in(cx, |editor, _, cx| {
18546 for buffer_id in buffer_ids {
18547 if should_unfold {
18548 editor.unfold_buffer(buffer_id, cx);
18549 } else {
18550 editor.fold_buffer(buffer_id, cx);
18551 }
18552 }
18553 })
18554 .ok();
18555 });
18556 }
18557 }
18558
18559 fn fold_at_level(
18560 &mut self,
18561 fold_at: &FoldAtLevel,
18562 window: &mut Window,
18563 cx: &mut Context<Self>,
18564 ) {
18565 if !self.buffer.read(cx).is_singleton() {
18566 return;
18567 }
18568
18569 let fold_at_level = fold_at.0;
18570 let snapshot = self.buffer.read(cx).snapshot(cx);
18571 let mut to_fold = Vec::new();
18572 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18573
18574 let row_ranges_to_keep: Vec<Range<u32>> = self
18575 .selections
18576 .all::<Point>(&self.display_snapshot(cx))
18577 .into_iter()
18578 .map(|sel| sel.start.row..sel.end.row)
18579 .collect();
18580
18581 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18582 while start_row < end_row {
18583 match self
18584 .snapshot(window, cx)
18585 .crease_for_buffer_row(MultiBufferRow(start_row))
18586 {
18587 Some(crease) => {
18588 let nested_start_row = crease.range().start.row + 1;
18589 let nested_end_row = crease.range().end.row;
18590
18591 if current_level < fold_at_level {
18592 stack.push((nested_start_row, nested_end_row, current_level + 1));
18593 } else if current_level == fold_at_level {
18594 // Fold iff there is no selection completely contained within the fold region
18595 if !row_ranges_to_keep.iter().any(|selection| {
18596 selection.end >= nested_start_row
18597 && selection.start <= nested_end_row
18598 }) {
18599 to_fold.push(crease);
18600 }
18601 }
18602
18603 start_row = nested_end_row + 1;
18604 }
18605 None => start_row += 1,
18606 }
18607 }
18608 }
18609
18610 self.fold_creases(to_fold, true, window, cx);
18611 }
18612
18613 pub fn fold_at_level_1(
18614 &mut self,
18615 _: &actions::FoldAtLevel1,
18616 window: &mut Window,
18617 cx: &mut Context<Self>,
18618 ) {
18619 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18620 }
18621
18622 pub fn fold_at_level_2(
18623 &mut self,
18624 _: &actions::FoldAtLevel2,
18625 window: &mut Window,
18626 cx: &mut Context<Self>,
18627 ) {
18628 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18629 }
18630
18631 pub fn fold_at_level_3(
18632 &mut self,
18633 _: &actions::FoldAtLevel3,
18634 window: &mut Window,
18635 cx: &mut Context<Self>,
18636 ) {
18637 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18638 }
18639
18640 pub fn fold_at_level_4(
18641 &mut self,
18642 _: &actions::FoldAtLevel4,
18643 window: &mut Window,
18644 cx: &mut Context<Self>,
18645 ) {
18646 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18647 }
18648
18649 pub fn fold_at_level_5(
18650 &mut self,
18651 _: &actions::FoldAtLevel5,
18652 window: &mut Window,
18653 cx: &mut Context<Self>,
18654 ) {
18655 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18656 }
18657
18658 pub fn fold_at_level_6(
18659 &mut self,
18660 _: &actions::FoldAtLevel6,
18661 window: &mut Window,
18662 cx: &mut Context<Self>,
18663 ) {
18664 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18665 }
18666
18667 pub fn fold_at_level_7(
18668 &mut self,
18669 _: &actions::FoldAtLevel7,
18670 window: &mut Window,
18671 cx: &mut Context<Self>,
18672 ) {
18673 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18674 }
18675
18676 pub fn fold_at_level_8(
18677 &mut self,
18678 _: &actions::FoldAtLevel8,
18679 window: &mut Window,
18680 cx: &mut Context<Self>,
18681 ) {
18682 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18683 }
18684
18685 pub fn fold_at_level_9(
18686 &mut self,
18687 _: &actions::FoldAtLevel9,
18688 window: &mut Window,
18689 cx: &mut Context<Self>,
18690 ) {
18691 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18692 }
18693
18694 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18695 if self.buffer.read(cx).is_singleton() {
18696 let mut fold_ranges = Vec::new();
18697 let snapshot = self.buffer.read(cx).snapshot(cx);
18698
18699 for row in 0..snapshot.max_row().0 {
18700 if let Some(foldable_range) = self
18701 .snapshot(window, cx)
18702 .crease_for_buffer_row(MultiBufferRow(row))
18703 {
18704 fold_ranges.push(foldable_range);
18705 }
18706 }
18707
18708 self.fold_creases(fold_ranges, true, window, cx);
18709 } else {
18710 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18711 editor
18712 .update_in(cx, |editor, _, cx| {
18713 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18714 editor.fold_buffer(buffer_id, cx);
18715 }
18716 })
18717 .ok();
18718 });
18719 }
18720 }
18721
18722 pub fn fold_function_bodies(
18723 &mut self,
18724 _: &actions::FoldFunctionBodies,
18725 window: &mut Window,
18726 cx: &mut Context<Self>,
18727 ) {
18728 let snapshot = self.buffer.read(cx).snapshot(cx);
18729
18730 let ranges = snapshot
18731 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18732 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18733 .collect::<Vec<_>>();
18734
18735 let creases = ranges
18736 .into_iter()
18737 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18738 .collect();
18739
18740 self.fold_creases(creases, true, window, cx);
18741 }
18742
18743 pub fn fold_recursive(
18744 &mut self,
18745 _: &actions::FoldRecursive,
18746 window: &mut Window,
18747 cx: &mut Context<Self>,
18748 ) {
18749 let mut to_fold = Vec::new();
18750 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18751 let selections = self.selections.all_adjusted(&display_map);
18752
18753 for selection in selections {
18754 let range = selection.range().sorted();
18755 let buffer_start_row = range.start.row;
18756
18757 if range.start.row != range.end.row {
18758 let mut found = false;
18759 for row in range.start.row..=range.end.row {
18760 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18761 found = true;
18762 to_fold.push(crease);
18763 }
18764 }
18765 if found {
18766 continue;
18767 }
18768 }
18769
18770 for row in (0..=range.start.row).rev() {
18771 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18772 if crease.range().end.row >= buffer_start_row {
18773 to_fold.push(crease);
18774 } else {
18775 break;
18776 }
18777 }
18778 }
18779 }
18780
18781 self.fold_creases(to_fold, true, window, cx);
18782 }
18783
18784 pub fn fold_at(
18785 &mut self,
18786 buffer_row: MultiBufferRow,
18787 window: &mut Window,
18788 cx: &mut Context<Self>,
18789 ) {
18790 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18791
18792 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18793 let autoscroll = self
18794 .selections
18795 .all::<Point>(&display_map)
18796 .iter()
18797 .any(|selection| crease.range().overlaps(&selection.range()));
18798
18799 self.fold_creases(vec![crease], autoscroll, window, cx);
18800 }
18801 }
18802
18803 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18804 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18805 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18806 let buffer = display_map.buffer_snapshot();
18807 let selections = self.selections.all::<Point>(&display_map);
18808 let ranges = selections
18809 .iter()
18810 .map(|s| {
18811 let range = s.display_range(&display_map).sorted();
18812 let mut start = range.start.to_point(&display_map);
18813 let mut end = range.end.to_point(&display_map);
18814 start.column = 0;
18815 end.column = buffer.line_len(MultiBufferRow(end.row));
18816 start..end
18817 })
18818 .collect::<Vec<_>>();
18819
18820 self.unfold_ranges(&ranges, true, true, cx);
18821 } else {
18822 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18823 let buffer_ids = self
18824 .selections
18825 .disjoint_anchor_ranges()
18826 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18827 .collect::<HashSet<_>>();
18828 for buffer_id in buffer_ids {
18829 self.unfold_buffer(buffer_id, cx);
18830 }
18831 }
18832 }
18833
18834 pub fn unfold_recursive(
18835 &mut self,
18836 _: &UnfoldRecursive,
18837 _window: &mut Window,
18838 cx: &mut Context<Self>,
18839 ) {
18840 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18841 let selections = self.selections.all::<Point>(&display_map);
18842 let ranges = selections
18843 .iter()
18844 .map(|s| {
18845 let mut range = s.display_range(&display_map).sorted();
18846 *range.start.column_mut() = 0;
18847 *range.end.column_mut() = display_map.line_len(range.end.row());
18848 let start = range.start.to_point(&display_map);
18849 let end = range.end.to_point(&display_map);
18850 start..end
18851 })
18852 .collect::<Vec<_>>();
18853
18854 self.unfold_ranges(&ranges, true, true, cx);
18855 }
18856
18857 pub fn unfold_at(
18858 &mut self,
18859 buffer_row: MultiBufferRow,
18860 _window: &mut Window,
18861 cx: &mut Context<Self>,
18862 ) {
18863 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18864
18865 let intersection_range = Point::new(buffer_row.0, 0)
18866 ..Point::new(
18867 buffer_row.0,
18868 display_map.buffer_snapshot().line_len(buffer_row),
18869 );
18870
18871 let autoscroll = self
18872 .selections
18873 .all::<Point>(&display_map)
18874 .iter()
18875 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18876
18877 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18878 }
18879
18880 pub fn unfold_all(
18881 &mut self,
18882 _: &actions::UnfoldAll,
18883 _window: &mut Window,
18884 cx: &mut Context<Self>,
18885 ) {
18886 if self.buffer.read(cx).is_singleton() {
18887 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18888 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18889 } else {
18890 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18891 editor
18892 .update(cx, |editor, cx| {
18893 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18894 editor.unfold_buffer(buffer_id, cx);
18895 }
18896 })
18897 .ok();
18898 });
18899 }
18900 }
18901
18902 pub fn fold_selected_ranges(
18903 &mut self,
18904 _: &FoldSelectedRanges,
18905 window: &mut Window,
18906 cx: &mut Context<Self>,
18907 ) {
18908 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18909 let selections = self.selections.all_adjusted(&display_map);
18910 let ranges = selections
18911 .into_iter()
18912 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18913 .collect::<Vec<_>>();
18914 self.fold_creases(ranges, true, window, cx);
18915 }
18916
18917 pub fn fold_ranges<T: ToOffset + Clone>(
18918 &mut self,
18919 ranges: Vec<Range<T>>,
18920 auto_scroll: bool,
18921 window: &mut Window,
18922 cx: &mut Context<Self>,
18923 ) {
18924 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18925 let ranges = ranges
18926 .into_iter()
18927 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18928 .collect::<Vec<_>>();
18929 self.fold_creases(ranges, auto_scroll, window, cx);
18930 }
18931
18932 pub fn fold_creases<T: ToOffset + Clone>(
18933 &mut self,
18934 creases: Vec<Crease<T>>,
18935 auto_scroll: bool,
18936 _window: &mut Window,
18937 cx: &mut Context<Self>,
18938 ) {
18939 if creases.is_empty() {
18940 return;
18941 }
18942
18943 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18944
18945 if auto_scroll {
18946 self.request_autoscroll(Autoscroll::fit(), cx);
18947 }
18948
18949 cx.notify();
18950
18951 self.scrollbar_marker_state.dirty = true;
18952 self.folds_did_change(cx);
18953 }
18954
18955 /// Removes any folds whose ranges intersect any of the given ranges.
18956 pub fn unfold_ranges<T: ToOffset + Clone>(
18957 &mut self,
18958 ranges: &[Range<T>],
18959 inclusive: bool,
18960 auto_scroll: bool,
18961 cx: &mut Context<Self>,
18962 ) {
18963 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18964 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18965 });
18966 self.folds_did_change(cx);
18967 }
18968
18969 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18970 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18971 return;
18972 }
18973
18974 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18975 self.display_map.update(cx, |display_map, cx| {
18976 display_map.fold_buffers([buffer_id], cx)
18977 });
18978
18979 let snapshot = self.display_snapshot(cx);
18980 self.selections.change_with(&snapshot, |selections| {
18981 selections.remove_selections_from_buffer(buffer_id);
18982 });
18983
18984 cx.emit(EditorEvent::BufferFoldToggled {
18985 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18986 folded: true,
18987 });
18988 cx.notify();
18989 }
18990
18991 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18992 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18993 return;
18994 }
18995 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18996 self.display_map.update(cx, |display_map, cx| {
18997 display_map.unfold_buffers([buffer_id], cx);
18998 });
18999 cx.emit(EditorEvent::BufferFoldToggled {
19000 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19001 folded: false,
19002 });
19003 cx.notify();
19004 }
19005
19006 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19007 self.display_map.read(cx).is_buffer_folded(buffer)
19008 }
19009
19010 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19011 self.display_map.read(cx).folded_buffers()
19012 }
19013
19014 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19015 self.display_map.update(cx, |display_map, cx| {
19016 display_map.disable_header_for_buffer(buffer_id, cx);
19017 });
19018 cx.notify();
19019 }
19020
19021 /// Removes any folds with the given ranges.
19022 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19023 &mut self,
19024 ranges: &[Range<T>],
19025 type_id: TypeId,
19026 auto_scroll: bool,
19027 cx: &mut Context<Self>,
19028 ) {
19029 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19030 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19031 });
19032 self.folds_did_change(cx);
19033 }
19034
19035 fn remove_folds_with<T: ToOffset + Clone>(
19036 &mut self,
19037 ranges: &[Range<T>],
19038 auto_scroll: bool,
19039 cx: &mut Context<Self>,
19040 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19041 ) {
19042 if ranges.is_empty() {
19043 return;
19044 }
19045
19046 let mut buffers_affected = HashSet::default();
19047 let multi_buffer = self.buffer().read(cx);
19048 for range in ranges {
19049 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19050 buffers_affected.insert(buffer.read(cx).remote_id());
19051 };
19052 }
19053
19054 self.display_map.update(cx, update);
19055
19056 if auto_scroll {
19057 self.request_autoscroll(Autoscroll::fit(), cx);
19058 }
19059
19060 cx.notify();
19061 self.scrollbar_marker_state.dirty = true;
19062 self.active_indent_guides_state.dirty = true;
19063 }
19064
19065 pub fn update_renderer_widths(
19066 &mut self,
19067 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19068 cx: &mut Context<Self>,
19069 ) -> bool {
19070 self.display_map
19071 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19072 }
19073
19074 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19075 self.display_map.read(cx).fold_placeholder.clone()
19076 }
19077
19078 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19079 self.buffer.update(cx, |buffer, cx| {
19080 buffer.set_all_diff_hunks_expanded(cx);
19081 });
19082 }
19083
19084 pub fn expand_all_diff_hunks(
19085 &mut self,
19086 _: &ExpandAllDiffHunks,
19087 _window: &mut Window,
19088 cx: &mut Context<Self>,
19089 ) {
19090 self.buffer.update(cx, |buffer, cx| {
19091 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19092 });
19093 }
19094
19095 pub fn collapse_all_diff_hunks(
19096 &mut self,
19097 _: &CollapseAllDiffHunks,
19098 _window: &mut Window,
19099 cx: &mut Context<Self>,
19100 ) {
19101 self.buffer.update(cx, |buffer, cx| {
19102 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19103 });
19104 }
19105
19106 pub fn toggle_selected_diff_hunks(
19107 &mut self,
19108 _: &ToggleSelectedDiffHunks,
19109 _window: &mut Window,
19110 cx: &mut Context<Self>,
19111 ) {
19112 let ranges: Vec<_> = self
19113 .selections
19114 .disjoint_anchors()
19115 .iter()
19116 .map(|s| s.range())
19117 .collect();
19118 self.toggle_diff_hunks_in_ranges(ranges, cx);
19119 }
19120
19121 pub fn diff_hunks_in_ranges<'a>(
19122 &'a self,
19123 ranges: &'a [Range<Anchor>],
19124 buffer: &'a MultiBufferSnapshot,
19125 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19126 ranges.iter().flat_map(move |range| {
19127 let end_excerpt_id = range.end.excerpt_id;
19128 let range = range.to_point(buffer);
19129 let mut peek_end = range.end;
19130 if range.end.row < buffer.max_row().0 {
19131 peek_end = Point::new(range.end.row + 1, 0);
19132 }
19133 buffer
19134 .diff_hunks_in_range(range.start..peek_end)
19135 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19136 })
19137 }
19138
19139 pub fn has_stageable_diff_hunks_in_ranges(
19140 &self,
19141 ranges: &[Range<Anchor>],
19142 snapshot: &MultiBufferSnapshot,
19143 ) -> bool {
19144 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19145 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19146 }
19147
19148 pub fn toggle_staged_selected_diff_hunks(
19149 &mut self,
19150 _: &::git::ToggleStaged,
19151 _: &mut Window,
19152 cx: &mut Context<Self>,
19153 ) {
19154 let snapshot = self.buffer.read(cx).snapshot(cx);
19155 let ranges: Vec<_> = self
19156 .selections
19157 .disjoint_anchors()
19158 .iter()
19159 .map(|s| s.range())
19160 .collect();
19161 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19162 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19163 }
19164
19165 pub fn set_render_diff_hunk_controls(
19166 &mut self,
19167 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19168 cx: &mut Context<Self>,
19169 ) {
19170 self.render_diff_hunk_controls = render_diff_hunk_controls;
19171 cx.notify();
19172 }
19173
19174 pub fn stage_and_next(
19175 &mut self,
19176 _: &::git::StageAndNext,
19177 window: &mut Window,
19178 cx: &mut Context<Self>,
19179 ) {
19180 self.do_stage_or_unstage_and_next(true, window, cx);
19181 }
19182
19183 pub fn unstage_and_next(
19184 &mut self,
19185 _: &::git::UnstageAndNext,
19186 window: &mut Window,
19187 cx: &mut Context<Self>,
19188 ) {
19189 self.do_stage_or_unstage_and_next(false, window, cx);
19190 }
19191
19192 pub fn stage_or_unstage_diff_hunks(
19193 &mut self,
19194 stage: bool,
19195 ranges: Vec<Range<Anchor>>,
19196 cx: &mut Context<Self>,
19197 ) {
19198 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19199 cx.spawn(async move |this, cx| {
19200 task.await?;
19201 this.update(cx, |this, cx| {
19202 let snapshot = this.buffer.read(cx).snapshot(cx);
19203 let chunk_by = this
19204 .diff_hunks_in_ranges(&ranges, &snapshot)
19205 .chunk_by(|hunk| hunk.buffer_id);
19206 for (buffer_id, hunks) in &chunk_by {
19207 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19208 }
19209 })
19210 })
19211 .detach_and_log_err(cx);
19212 }
19213
19214 fn save_buffers_for_ranges_if_needed(
19215 &mut self,
19216 ranges: &[Range<Anchor>],
19217 cx: &mut Context<Editor>,
19218 ) -> Task<Result<()>> {
19219 let multibuffer = self.buffer.read(cx);
19220 let snapshot = multibuffer.read(cx);
19221 let buffer_ids: HashSet<_> = ranges
19222 .iter()
19223 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19224 .collect();
19225 drop(snapshot);
19226
19227 let mut buffers = HashSet::default();
19228 for buffer_id in buffer_ids {
19229 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19230 let buffer = buffer_entity.read(cx);
19231 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19232 {
19233 buffers.insert(buffer_entity);
19234 }
19235 }
19236 }
19237
19238 if let Some(project) = &self.project {
19239 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19240 } else {
19241 Task::ready(Ok(()))
19242 }
19243 }
19244
19245 fn do_stage_or_unstage_and_next(
19246 &mut self,
19247 stage: bool,
19248 window: &mut Window,
19249 cx: &mut Context<Self>,
19250 ) {
19251 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19252
19253 if ranges.iter().any(|range| range.start != range.end) {
19254 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19255 return;
19256 }
19257
19258 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19259 let snapshot = self.snapshot(window, cx);
19260 let position = self
19261 .selections
19262 .newest::<Point>(&snapshot.display_snapshot)
19263 .head();
19264 let mut row = snapshot
19265 .buffer_snapshot()
19266 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19267 .find(|hunk| hunk.row_range.start.0 > position.row)
19268 .map(|hunk| hunk.row_range.start);
19269
19270 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19271 // Outside of the project diff editor, wrap around to the beginning.
19272 if !all_diff_hunks_expanded {
19273 row = row.or_else(|| {
19274 snapshot
19275 .buffer_snapshot()
19276 .diff_hunks_in_range(Point::zero()..position)
19277 .find(|hunk| hunk.row_range.end.0 < position.row)
19278 .map(|hunk| hunk.row_range.start)
19279 });
19280 }
19281
19282 if let Some(row) = row {
19283 let destination = Point::new(row.0, 0);
19284 let autoscroll = Autoscroll::center();
19285
19286 self.unfold_ranges(&[destination..destination], false, false, cx);
19287 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19288 s.select_ranges([destination..destination]);
19289 });
19290 }
19291 }
19292
19293 fn do_stage_or_unstage(
19294 &self,
19295 stage: bool,
19296 buffer_id: BufferId,
19297 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19298 cx: &mut App,
19299 ) -> Option<()> {
19300 let project = self.project()?;
19301 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19302 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19303 let buffer_snapshot = buffer.read(cx).snapshot();
19304 let file_exists = buffer_snapshot
19305 .file()
19306 .is_some_and(|file| file.disk_state().exists());
19307 diff.update(cx, |diff, cx| {
19308 diff.stage_or_unstage_hunks(
19309 stage,
19310 &hunks
19311 .map(|hunk| buffer_diff::DiffHunk {
19312 buffer_range: hunk.buffer_range,
19313 diff_base_byte_range: hunk.diff_base_byte_range,
19314 secondary_status: hunk.secondary_status,
19315 range: Point::zero()..Point::zero(), // unused
19316 })
19317 .collect::<Vec<_>>(),
19318 &buffer_snapshot,
19319 file_exists,
19320 cx,
19321 )
19322 });
19323 None
19324 }
19325
19326 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19327 let ranges: Vec<_> = self
19328 .selections
19329 .disjoint_anchors()
19330 .iter()
19331 .map(|s| s.range())
19332 .collect();
19333 self.buffer
19334 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19335 }
19336
19337 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19338 self.buffer.update(cx, |buffer, cx| {
19339 let ranges = vec![Anchor::min()..Anchor::max()];
19340 if !buffer.all_diff_hunks_expanded()
19341 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19342 {
19343 buffer.collapse_diff_hunks(ranges, cx);
19344 true
19345 } else {
19346 false
19347 }
19348 })
19349 }
19350
19351 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19352 if self.buffer.read(cx).all_diff_hunks_expanded() {
19353 return true;
19354 }
19355 let ranges = vec![Anchor::min()..Anchor::max()];
19356 self.buffer
19357 .read(cx)
19358 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19359 }
19360
19361 fn toggle_diff_hunks_in_ranges(
19362 &mut self,
19363 ranges: Vec<Range<Anchor>>,
19364 cx: &mut Context<Editor>,
19365 ) {
19366 self.buffer.update(cx, |buffer, cx| {
19367 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19368 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19369 })
19370 }
19371
19372 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19373 self.buffer.update(cx, |buffer, cx| {
19374 let snapshot = buffer.snapshot(cx);
19375 let excerpt_id = range.end.excerpt_id;
19376 let point_range = range.to_point(&snapshot);
19377 let expand = !buffer.single_hunk_is_expanded(range, cx);
19378 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19379 })
19380 }
19381
19382 pub(crate) fn apply_all_diff_hunks(
19383 &mut self,
19384 _: &ApplyAllDiffHunks,
19385 window: &mut Window,
19386 cx: &mut Context<Self>,
19387 ) {
19388 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19389
19390 let buffers = self.buffer.read(cx).all_buffers();
19391 for branch_buffer in buffers {
19392 branch_buffer.update(cx, |branch_buffer, cx| {
19393 branch_buffer.merge_into_base(Vec::new(), cx);
19394 });
19395 }
19396
19397 if let Some(project) = self.project.clone() {
19398 self.save(
19399 SaveOptions {
19400 format: true,
19401 autosave: false,
19402 },
19403 project,
19404 window,
19405 cx,
19406 )
19407 .detach_and_log_err(cx);
19408 }
19409 }
19410
19411 pub(crate) fn apply_selected_diff_hunks(
19412 &mut self,
19413 _: &ApplyDiffHunk,
19414 window: &mut Window,
19415 cx: &mut Context<Self>,
19416 ) {
19417 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19418 let snapshot = self.snapshot(window, cx);
19419 let hunks = snapshot.hunks_for_ranges(
19420 self.selections
19421 .all(&snapshot.display_snapshot)
19422 .into_iter()
19423 .map(|selection| selection.range()),
19424 );
19425 let mut ranges_by_buffer = HashMap::default();
19426 self.transact(window, cx, |editor, _window, cx| {
19427 for hunk in hunks {
19428 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19429 ranges_by_buffer
19430 .entry(buffer.clone())
19431 .or_insert_with(Vec::new)
19432 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19433 }
19434 }
19435
19436 for (buffer, ranges) in ranges_by_buffer {
19437 buffer.update(cx, |buffer, cx| {
19438 buffer.merge_into_base(ranges, cx);
19439 });
19440 }
19441 });
19442
19443 if let Some(project) = self.project.clone() {
19444 self.save(
19445 SaveOptions {
19446 format: true,
19447 autosave: false,
19448 },
19449 project,
19450 window,
19451 cx,
19452 )
19453 .detach_and_log_err(cx);
19454 }
19455 }
19456
19457 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19458 if hovered != self.gutter_hovered {
19459 self.gutter_hovered = hovered;
19460 cx.notify();
19461 }
19462 }
19463
19464 pub fn insert_blocks(
19465 &mut self,
19466 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19467 autoscroll: Option<Autoscroll>,
19468 cx: &mut Context<Self>,
19469 ) -> Vec<CustomBlockId> {
19470 let blocks = self
19471 .display_map
19472 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19473 if let Some(autoscroll) = autoscroll {
19474 self.request_autoscroll(autoscroll, cx);
19475 }
19476 cx.notify();
19477 blocks
19478 }
19479
19480 pub fn resize_blocks(
19481 &mut self,
19482 heights: HashMap<CustomBlockId, u32>,
19483 autoscroll: Option<Autoscroll>,
19484 cx: &mut Context<Self>,
19485 ) {
19486 self.display_map
19487 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19488 if let Some(autoscroll) = autoscroll {
19489 self.request_autoscroll(autoscroll, cx);
19490 }
19491 cx.notify();
19492 }
19493
19494 pub fn replace_blocks(
19495 &mut self,
19496 renderers: HashMap<CustomBlockId, RenderBlock>,
19497 autoscroll: Option<Autoscroll>,
19498 cx: &mut Context<Self>,
19499 ) {
19500 self.display_map
19501 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19502 if let Some(autoscroll) = autoscroll {
19503 self.request_autoscroll(autoscroll, cx);
19504 }
19505 cx.notify();
19506 }
19507
19508 pub fn remove_blocks(
19509 &mut self,
19510 block_ids: HashSet<CustomBlockId>,
19511 autoscroll: Option<Autoscroll>,
19512 cx: &mut Context<Self>,
19513 ) {
19514 self.display_map.update(cx, |display_map, cx| {
19515 display_map.remove_blocks(block_ids, cx)
19516 });
19517 if let Some(autoscroll) = autoscroll {
19518 self.request_autoscroll(autoscroll, cx);
19519 }
19520 cx.notify();
19521 }
19522
19523 pub fn row_for_block(
19524 &self,
19525 block_id: CustomBlockId,
19526 cx: &mut Context<Self>,
19527 ) -> Option<DisplayRow> {
19528 self.display_map
19529 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19530 }
19531
19532 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19533 self.focused_block = Some(focused_block);
19534 }
19535
19536 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19537 self.focused_block.take()
19538 }
19539
19540 pub fn insert_creases(
19541 &mut self,
19542 creases: impl IntoIterator<Item = Crease<Anchor>>,
19543 cx: &mut Context<Self>,
19544 ) -> Vec<CreaseId> {
19545 self.display_map
19546 .update(cx, |map, cx| map.insert_creases(creases, cx))
19547 }
19548
19549 pub fn remove_creases(
19550 &mut self,
19551 ids: impl IntoIterator<Item = CreaseId>,
19552 cx: &mut Context<Self>,
19553 ) -> Vec<(CreaseId, Range<Anchor>)> {
19554 self.display_map
19555 .update(cx, |map, cx| map.remove_creases(ids, cx))
19556 }
19557
19558 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19559 self.display_map
19560 .update(cx, |map, cx| map.snapshot(cx))
19561 .longest_row()
19562 }
19563
19564 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19565 self.display_map
19566 .update(cx, |map, cx| map.snapshot(cx))
19567 .max_point()
19568 }
19569
19570 pub fn text(&self, cx: &App) -> String {
19571 self.buffer.read(cx).read(cx).text()
19572 }
19573
19574 pub fn is_empty(&self, cx: &App) -> bool {
19575 self.buffer.read(cx).read(cx).is_empty()
19576 }
19577
19578 pub fn text_option(&self, cx: &App) -> Option<String> {
19579 let text = self.text(cx);
19580 let text = text.trim();
19581
19582 if text.is_empty() {
19583 return None;
19584 }
19585
19586 Some(text.to_string())
19587 }
19588
19589 pub fn set_text(
19590 &mut self,
19591 text: impl Into<Arc<str>>,
19592 window: &mut Window,
19593 cx: &mut Context<Self>,
19594 ) {
19595 self.transact(window, cx, |this, _, cx| {
19596 this.buffer
19597 .read(cx)
19598 .as_singleton()
19599 .expect("you can only call set_text on editors for singleton buffers")
19600 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19601 });
19602 }
19603
19604 pub fn display_text(&self, cx: &mut App) -> String {
19605 self.display_map
19606 .update(cx, |map, cx| map.snapshot(cx))
19607 .text()
19608 }
19609
19610 fn create_minimap(
19611 &self,
19612 minimap_settings: MinimapSettings,
19613 window: &mut Window,
19614 cx: &mut Context<Self>,
19615 ) -> Option<Entity<Self>> {
19616 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19617 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19618 }
19619
19620 fn initialize_new_minimap(
19621 &self,
19622 minimap_settings: MinimapSettings,
19623 window: &mut Window,
19624 cx: &mut Context<Self>,
19625 ) -> Entity<Self> {
19626 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19627
19628 let mut minimap = Editor::new_internal(
19629 EditorMode::Minimap {
19630 parent: cx.weak_entity(),
19631 },
19632 self.buffer.clone(),
19633 None,
19634 Some(self.display_map.clone()),
19635 window,
19636 cx,
19637 );
19638 minimap.scroll_manager.clone_state(&self.scroll_manager);
19639 minimap.set_text_style_refinement(TextStyleRefinement {
19640 font_size: Some(MINIMAP_FONT_SIZE),
19641 font_weight: Some(MINIMAP_FONT_WEIGHT),
19642 ..Default::default()
19643 });
19644 minimap.update_minimap_configuration(minimap_settings, cx);
19645 cx.new(|_| minimap)
19646 }
19647
19648 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19649 let current_line_highlight = minimap_settings
19650 .current_line_highlight
19651 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19652 self.set_current_line_highlight(Some(current_line_highlight));
19653 }
19654
19655 pub fn minimap(&self) -> Option<&Entity<Self>> {
19656 self.minimap
19657 .as_ref()
19658 .filter(|_| self.minimap_visibility.visible())
19659 }
19660
19661 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19662 let mut wrap_guides = smallvec![];
19663
19664 if self.show_wrap_guides == Some(false) {
19665 return wrap_guides;
19666 }
19667
19668 let settings = self.buffer.read(cx).language_settings(cx);
19669 if settings.show_wrap_guides {
19670 match self.soft_wrap_mode(cx) {
19671 SoftWrap::Column(soft_wrap) => {
19672 wrap_guides.push((soft_wrap as usize, true));
19673 }
19674 SoftWrap::Bounded(soft_wrap) => {
19675 wrap_guides.push((soft_wrap as usize, true));
19676 }
19677 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19678 }
19679 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19680 }
19681
19682 wrap_guides
19683 }
19684
19685 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19686 let settings = self.buffer.read(cx).language_settings(cx);
19687 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19688 match mode {
19689 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19690 SoftWrap::None
19691 }
19692 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19693 language_settings::SoftWrap::PreferredLineLength => {
19694 SoftWrap::Column(settings.preferred_line_length)
19695 }
19696 language_settings::SoftWrap::Bounded => {
19697 SoftWrap::Bounded(settings.preferred_line_length)
19698 }
19699 }
19700 }
19701
19702 pub fn set_soft_wrap_mode(
19703 &mut self,
19704 mode: language_settings::SoftWrap,
19705
19706 cx: &mut Context<Self>,
19707 ) {
19708 self.soft_wrap_mode_override = Some(mode);
19709 cx.notify();
19710 }
19711
19712 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19713 self.hard_wrap = hard_wrap;
19714 cx.notify();
19715 }
19716
19717 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19718 self.text_style_refinement = Some(style);
19719 }
19720
19721 /// called by the Element so we know what style we were most recently rendered with.
19722 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19723 // We intentionally do not inform the display map about the minimap style
19724 // so that wrapping is not recalculated and stays consistent for the editor
19725 // and its linked minimap.
19726 if !self.mode.is_minimap() {
19727 let font = style.text.font();
19728 let font_size = style.text.font_size.to_pixels(window.rem_size());
19729 let display_map = self
19730 .placeholder_display_map
19731 .as_ref()
19732 .filter(|_| self.is_empty(cx))
19733 .unwrap_or(&self.display_map);
19734
19735 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19736 }
19737 self.style = Some(style);
19738 }
19739
19740 pub fn style(&self) -> Option<&EditorStyle> {
19741 self.style.as_ref()
19742 }
19743
19744 // Called by the element. This method is not designed to be called outside of the editor
19745 // element's layout code because it does not notify when rewrapping is computed synchronously.
19746 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19747 if self.is_empty(cx) {
19748 self.placeholder_display_map
19749 .as_ref()
19750 .map_or(false, |display_map| {
19751 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19752 })
19753 } else {
19754 self.display_map
19755 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19756 }
19757 }
19758
19759 pub fn set_soft_wrap(&mut self) {
19760 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19761 }
19762
19763 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19764 if self.soft_wrap_mode_override.is_some() {
19765 self.soft_wrap_mode_override.take();
19766 } else {
19767 let soft_wrap = match self.soft_wrap_mode(cx) {
19768 SoftWrap::GitDiff => return,
19769 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19770 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19771 language_settings::SoftWrap::None
19772 }
19773 };
19774 self.soft_wrap_mode_override = Some(soft_wrap);
19775 }
19776 cx.notify();
19777 }
19778
19779 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19780 let Some(workspace) = self.workspace() else {
19781 return;
19782 };
19783 let fs = workspace.read(cx).app_state().fs.clone();
19784 let current_show = TabBarSettings::get_global(cx).show;
19785 update_settings_file(fs, cx, move |setting, _| {
19786 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19787 });
19788 }
19789
19790 pub fn toggle_indent_guides(
19791 &mut self,
19792 _: &ToggleIndentGuides,
19793 _: &mut Window,
19794 cx: &mut Context<Self>,
19795 ) {
19796 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19797 self.buffer
19798 .read(cx)
19799 .language_settings(cx)
19800 .indent_guides
19801 .enabled
19802 });
19803 self.show_indent_guides = Some(!currently_enabled);
19804 cx.notify();
19805 }
19806
19807 fn should_show_indent_guides(&self) -> Option<bool> {
19808 self.show_indent_guides
19809 }
19810
19811 pub fn toggle_line_numbers(
19812 &mut self,
19813 _: &ToggleLineNumbers,
19814 _: &mut Window,
19815 cx: &mut Context<Self>,
19816 ) {
19817 let mut editor_settings = EditorSettings::get_global(cx).clone();
19818 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19819 EditorSettings::override_global(editor_settings, cx);
19820 }
19821
19822 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19823 if let Some(show_line_numbers) = self.show_line_numbers {
19824 return show_line_numbers;
19825 }
19826 EditorSettings::get_global(cx).gutter.line_numbers
19827 }
19828
19829 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19830 match (
19831 self.use_relative_line_numbers,
19832 EditorSettings::get_global(cx).relative_line_numbers,
19833 ) {
19834 (None, setting) => setting,
19835 (Some(false), _) => RelativeLineNumbers::Disabled,
19836 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19837 (Some(true), _) => RelativeLineNumbers::Enabled,
19838 }
19839 }
19840
19841 pub fn toggle_relative_line_numbers(
19842 &mut self,
19843 _: &ToggleRelativeLineNumbers,
19844 _: &mut Window,
19845 cx: &mut Context<Self>,
19846 ) {
19847 let is_relative = self.relative_line_numbers(cx);
19848 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19849 }
19850
19851 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19852 self.use_relative_line_numbers = is_relative;
19853 cx.notify();
19854 }
19855
19856 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19857 self.show_gutter = show_gutter;
19858 cx.notify();
19859 }
19860
19861 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19862 self.show_scrollbars = ScrollbarAxes {
19863 horizontal: show,
19864 vertical: show,
19865 };
19866 cx.notify();
19867 }
19868
19869 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19870 self.show_scrollbars.vertical = show;
19871 cx.notify();
19872 }
19873
19874 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19875 self.show_scrollbars.horizontal = show;
19876 cx.notify();
19877 }
19878
19879 pub fn set_minimap_visibility(
19880 &mut self,
19881 minimap_visibility: MinimapVisibility,
19882 window: &mut Window,
19883 cx: &mut Context<Self>,
19884 ) {
19885 if self.minimap_visibility != minimap_visibility {
19886 if minimap_visibility.visible() && self.minimap.is_none() {
19887 let minimap_settings = EditorSettings::get_global(cx).minimap;
19888 self.minimap =
19889 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19890 }
19891 self.minimap_visibility = minimap_visibility;
19892 cx.notify();
19893 }
19894 }
19895
19896 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19897 self.set_show_scrollbars(false, cx);
19898 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19899 }
19900
19901 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19902 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19903 }
19904
19905 /// Normally the text in full mode and auto height editors is padded on the
19906 /// left side by roughly half a character width for improved hit testing.
19907 ///
19908 /// Use this method to disable this for cases where this is not wanted (e.g.
19909 /// if you want to align the editor text with some other text above or below)
19910 /// or if you want to add this padding to single-line editors.
19911 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19912 self.offset_content = offset_content;
19913 cx.notify();
19914 }
19915
19916 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19917 self.show_line_numbers = Some(show_line_numbers);
19918 cx.notify();
19919 }
19920
19921 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19922 self.disable_expand_excerpt_buttons = true;
19923 cx.notify();
19924 }
19925
19926 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19927 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19928 cx.notify();
19929 }
19930
19931 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19932 self.show_code_actions = Some(show_code_actions);
19933 cx.notify();
19934 }
19935
19936 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19937 self.show_runnables = Some(show_runnables);
19938 cx.notify();
19939 }
19940
19941 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19942 self.show_breakpoints = Some(show_breakpoints);
19943 cx.notify();
19944 }
19945
19946 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19947 if self.display_map.read(cx).masked != masked {
19948 self.display_map.update(cx, |map, _| map.masked = masked);
19949 }
19950 cx.notify()
19951 }
19952
19953 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19954 self.show_wrap_guides = Some(show_wrap_guides);
19955 cx.notify();
19956 }
19957
19958 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19959 self.show_indent_guides = Some(show_indent_guides);
19960 cx.notify();
19961 }
19962
19963 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19964 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19965 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19966 && let Some(dir) = file.abs_path(cx).parent()
19967 {
19968 return Some(dir.to_owned());
19969 }
19970 }
19971
19972 None
19973 }
19974
19975 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19976 self.active_excerpt(cx)?
19977 .1
19978 .read(cx)
19979 .file()
19980 .and_then(|f| f.as_local())
19981 }
19982
19983 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19984 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19985 let buffer = buffer.read(cx);
19986 if let Some(project_path) = buffer.project_path(cx) {
19987 let project = self.project()?.read(cx);
19988 project.absolute_path(&project_path, cx)
19989 } else {
19990 buffer
19991 .file()
19992 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19993 }
19994 })
19995 }
19996
19997 pub fn reveal_in_finder(
19998 &mut self,
19999 _: &RevealInFileManager,
20000 _window: &mut Window,
20001 cx: &mut Context<Self>,
20002 ) {
20003 if let Some(target) = self.target_file(cx) {
20004 cx.reveal_path(&target.abs_path(cx));
20005 }
20006 }
20007
20008 pub fn copy_path(
20009 &mut self,
20010 _: &zed_actions::workspace::CopyPath,
20011 _window: &mut Window,
20012 cx: &mut Context<Self>,
20013 ) {
20014 if let Some(path) = self.target_file_abs_path(cx)
20015 && let Some(path) = path.to_str()
20016 {
20017 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20018 } else {
20019 cx.propagate();
20020 }
20021 }
20022
20023 pub fn copy_relative_path(
20024 &mut self,
20025 _: &zed_actions::workspace::CopyRelativePath,
20026 _window: &mut Window,
20027 cx: &mut Context<Self>,
20028 ) {
20029 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20030 let project = self.project()?.read(cx);
20031 let path = buffer.read(cx).file()?.path();
20032 let path = path.display(project.path_style(cx));
20033 Some(path)
20034 }) {
20035 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20036 } else {
20037 cx.propagate();
20038 }
20039 }
20040
20041 /// Returns the project path for the editor's buffer, if any buffer is
20042 /// opened in the editor.
20043 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20044 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20045 buffer.read(cx).project_path(cx)
20046 } else {
20047 None
20048 }
20049 }
20050
20051 // Returns true if the editor handled a go-to-line request
20052 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20053 maybe!({
20054 let breakpoint_store = self.breakpoint_store.as_ref()?;
20055
20056 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20057 else {
20058 self.clear_row_highlights::<ActiveDebugLine>();
20059 return None;
20060 };
20061
20062 let position = active_stack_frame.position;
20063 let buffer_id = position.buffer_id?;
20064 let snapshot = self
20065 .project
20066 .as_ref()?
20067 .read(cx)
20068 .buffer_for_id(buffer_id, cx)?
20069 .read(cx)
20070 .snapshot();
20071
20072 let mut handled = false;
20073 for (id, ExcerptRange { context, .. }) in
20074 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20075 {
20076 if context.start.cmp(&position, &snapshot).is_ge()
20077 || context.end.cmp(&position, &snapshot).is_lt()
20078 {
20079 continue;
20080 }
20081 let snapshot = self.buffer.read(cx).snapshot(cx);
20082 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20083
20084 handled = true;
20085 self.clear_row_highlights::<ActiveDebugLine>();
20086
20087 self.go_to_line::<ActiveDebugLine>(
20088 multibuffer_anchor,
20089 Some(cx.theme().colors().editor_debugger_active_line_background),
20090 window,
20091 cx,
20092 );
20093
20094 cx.notify();
20095 }
20096
20097 handled.then_some(())
20098 })
20099 .is_some()
20100 }
20101
20102 pub fn copy_file_name_without_extension(
20103 &mut self,
20104 _: &CopyFileNameWithoutExtension,
20105 _: &mut Window,
20106 cx: &mut Context<Self>,
20107 ) {
20108 if let Some(file) = self.target_file(cx)
20109 && let Some(file_stem) = file.path().file_stem()
20110 {
20111 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20112 }
20113 }
20114
20115 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20116 if let Some(file) = self.target_file(cx)
20117 && let Some(name) = file.path().file_name()
20118 {
20119 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
20120 }
20121 }
20122
20123 pub fn toggle_git_blame(
20124 &mut self,
20125 _: &::git::Blame,
20126 window: &mut Window,
20127 cx: &mut Context<Self>,
20128 ) {
20129 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20130
20131 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20132 self.start_git_blame(true, window, cx);
20133 }
20134
20135 cx.notify();
20136 }
20137
20138 pub fn toggle_git_blame_inline(
20139 &mut self,
20140 _: &ToggleGitBlameInline,
20141 window: &mut Window,
20142 cx: &mut Context<Self>,
20143 ) {
20144 self.toggle_git_blame_inline_internal(true, window, cx);
20145 cx.notify();
20146 }
20147
20148 pub fn open_git_blame_commit(
20149 &mut self,
20150 _: &OpenGitBlameCommit,
20151 window: &mut Window,
20152 cx: &mut Context<Self>,
20153 ) {
20154 self.open_git_blame_commit_internal(window, cx);
20155 }
20156
20157 fn open_git_blame_commit_internal(
20158 &mut self,
20159 window: &mut Window,
20160 cx: &mut Context<Self>,
20161 ) -> Option<()> {
20162 let blame = self.blame.as_ref()?;
20163 let snapshot = self.snapshot(window, cx);
20164 let cursor = self
20165 .selections
20166 .newest::<Point>(&snapshot.display_snapshot)
20167 .head();
20168 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20169 let (_, blame_entry) = blame
20170 .update(cx, |blame, cx| {
20171 blame
20172 .blame_for_rows(
20173 &[RowInfo {
20174 buffer_id: Some(buffer.remote_id()),
20175 buffer_row: Some(point.row),
20176 ..Default::default()
20177 }],
20178 cx,
20179 )
20180 .next()
20181 })
20182 .flatten()?;
20183 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20184 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20185 let workspace = self.workspace()?.downgrade();
20186 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20187 None
20188 }
20189
20190 pub fn git_blame_inline_enabled(&self) -> bool {
20191 self.git_blame_inline_enabled
20192 }
20193
20194 pub fn toggle_selection_menu(
20195 &mut self,
20196 _: &ToggleSelectionMenu,
20197 _: &mut Window,
20198 cx: &mut Context<Self>,
20199 ) {
20200 self.show_selection_menu = self
20201 .show_selection_menu
20202 .map(|show_selections_menu| !show_selections_menu)
20203 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20204
20205 cx.notify();
20206 }
20207
20208 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20209 self.show_selection_menu
20210 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20211 }
20212
20213 fn start_git_blame(
20214 &mut self,
20215 user_triggered: bool,
20216 window: &mut Window,
20217 cx: &mut Context<Self>,
20218 ) {
20219 if let Some(project) = self.project() {
20220 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20221 && buffer.read(cx).file().is_none()
20222 {
20223 return;
20224 }
20225
20226 let focused = self.focus_handle(cx).contains_focused(window, cx);
20227
20228 let project = project.clone();
20229 let blame = cx
20230 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20231 self.blame_subscription =
20232 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20233 self.blame = Some(blame);
20234 }
20235 }
20236
20237 fn toggle_git_blame_inline_internal(
20238 &mut self,
20239 user_triggered: bool,
20240 window: &mut Window,
20241 cx: &mut Context<Self>,
20242 ) {
20243 if self.git_blame_inline_enabled {
20244 self.git_blame_inline_enabled = false;
20245 self.show_git_blame_inline = false;
20246 self.show_git_blame_inline_delay_task.take();
20247 } else {
20248 self.git_blame_inline_enabled = true;
20249 self.start_git_blame_inline(user_triggered, window, cx);
20250 }
20251
20252 cx.notify();
20253 }
20254
20255 fn start_git_blame_inline(
20256 &mut self,
20257 user_triggered: bool,
20258 window: &mut Window,
20259 cx: &mut Context<Self>,
20260 ) {
20261 self.start_git_blame(user_triggered, window, cx);
20262
20263 if ProjectSettings::get_global(cx)
20264 .git
20265 .inline_blame_delay()
20266 .is_some()
20267 {
20268 self.start_inline_blame_timer(window, cx);
20269 } else {
20270 self.show_git_blame_inline = true
20271 }
20272 }
20273
20274 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20275 self.blame.as_ref()
20276 }
20277
20278 pub fn show_git_blame_gutter(&self) -> bool {
20279 self.show_git_blame_gutter
20280 }
20281
20282 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20283 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20284 }
20285
20286 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20287 self.show_git_blame_inline
20288 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20289 && !self.newest_selection_head_on_empty_line(cx)
20290 && self.has_blame_entries(cx)
20291 }
20292
20293 fn has_blame_entries(&self, cx: &App) -> bool {
20294 self.blame()
20295 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20296 }
20297
20298 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20299 let cursor_anchor = self.selections.newest_anchor().head();
20300
20301 let snapshot = self.buffer.read(cx).snapshot(cx);
20302 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20303
20304 snapshot.line_len(buffer_row) == 0
20305 }
20306
20307 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20308 let buffer_and_selection = maybe!({
20309 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20310 let selection_range = selection.range();
20311
20312 let multi_buffer = self.buffer().read(cx);
20313 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20314 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20315
20316 let (buffer, range, _) = if selection.reversed {
20317 buffer_ranges.first()
20318 } else {
20319 buffer_ranges.last()
20320 }?;
20321
20322 let selection = text::ToPoint::to_point(&range.start, buffer).row
20323 ..text::ToPoint::to_point(&range.end, buffer).row;
20324 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20325 });
20326
20327 let Some((buffer, selection)) = buffer_and_selection else {
20328 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20329 };
20330
20331 let Some(project) = self.project() else {
20332 return Task::ready(Err(anyhow!("editor does not have project")));
20333 };
20334
20335 project.update(cx, |project, cx| {
20336 project.get_permalink_to_line(&buffer, selection, cx)
20337 })
20338 }
20339
20340 pub fn copy_permalink_to_line(
20341 &mut self,
20342 _: &CopyPermalinkToLine,
20343 window: &mut Window,
20344 cx: &mut Context<Self>,
20345 ) {
20346 let permalink_task = self.get_permalink_to_line(cx);
20347 let workspace = self.workspace();
20348
20349 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20350 Ok(permalink) => {
20351 cx.update(|_, cx| {
20352 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20353 })
20354 .ok();
20355 }
20356 Err(err) => {
20357 let message = format!("Failed to copy permalink: {err}");
20358
20359 anyhow::Result::<()>::Err(err).log_err();
20360
20361 if let Some(workspace) = workspace {
20362 workspace
20363 .update_in(cx, |workspace, _, cx| {
20364 struct CopyPermalinkToLine;
20365
20366 workspace.show_toast(
20367 Toast::new(
20368 NotificationId::unique::<CopyPermalinkToLine>(),
20369 message,
20370 ),
20371 cx,
20372 )
20373 })
20374 .ok();
20375 }
20376 }
20377 })
20378 .detach();
20379 }
20380
20381 pub fn copy_file_location(
20382 &mut self,
20383 _: &CopyFileLocation,
20384 _: &mut Window,
20385 cx: &mut Context<Self>,
20386 ) {
20387 let selection = self
20388 .selections
20389 .newest::<Point>(&self.display_snapshot(cx))
20390 .start
20391 .row
20392 + 1;
20393 if let Some(file) = self.target_file(cx) {
20394 let path = file.path().display(file.path_style(cx));
20395 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20396 }
20397 }
20398
20399 pub fn open_permalink_to_line(
20400 &mut self,
20401 _: &OpenPermalinkToLine,
20402 window: &mut Window,
20403 cx: &mut Context<Self>,
20404 ) {
20405 let permalink_task = self.get_permalink_to_line(cx);
20406 let workspace = self.workspace();
20407
20408 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20409 Ok(permalink) => {
20410 cx.update(|_, cx| {
20411 cx.open_url(permalink.as_ref());
20412 })
20413 .ok();
20414 }
20415 Err(err) => {
20416 let message = format!("Failed to open permalink: {err}");
20417
20418 anyhow::Result::<()>::Err(err).log_err();
20419
20420 if let Some(workspace) = workspace {
20421 workspace
20422 .update(cx, |workspace, cx| {
20423 struct OpenPermalinkToLine;
20424
20425 workspace.show_toast(
20426 Toast::new(
20427 NotificationId::unique::<OpenPermalinkToLine>(),
20428 message,
20429 ),
20430 cx,
20431 )
20432 })
20433 .ok();
20434 }
20435 }
20436 })
20437 .detach();
20438 }
20439
20440 pub fn insert_uuid_v4(
20441 &mut self,
20442 _: &InsertUuidV4,
20443 window: &mut Window,
20444 cx: &mut Context<Self>,
20445 ) {
20446 self.insert_uuid(UuidVersion::V4, window, cx);
20447 }
20448
20449 pub fn insert_uuid_v7(
20450 &mut self,
20451 _: &InsertUuidV7,
20452 window: &mut Window,
20453 cx: &mut Context<Self>,
20454 ) {
20455 self.insert_uuid(UuidVersion::V7, window, cx);
20456 }
20457
20458 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20459 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20460 self.transact(window, cx, |this, window, cx| {
20461 let edits = this
20462 .selections
20463 .all::<Point>(&this.display_snapshot(cx))
20464 .into_iter()
20465 .map(|selection| {
20466 let uuid = match version {
20467 UuidVersion::V4 => uuid::Uuid::new_v4(),
20468 UuidVersion::V7 => uuid::Uuid::now_v7(),
20469 };
20470
20471 (selection.range(), uuid.to_string())
20472 });
20473 this.edit(edits, cx);
20474 this.refresh_edit_prediction(true, false, window, cx);
20475 });
20476 }
20477
20478 pub fn open_selections_in_multibuffer(
20479 &mut self,
20480 _: &OpenSelectionsInMultibuffer,
20481 window: &mut Window,
20482 cx: &mut Context<Self>,
20483 ) {
20484 let multibuffer = self.buffer.read(cx);
20485
20486 let Some(buffer) = multibuffer.as_singleton() else {
20487 return;
20488 };
20489
20490 let Some(workspace) = self.workspace() else {
20491 return;
20492 };
20493
20494 let title = multibuffer.title(cx).to_string();
20495
20496 let locations = self
20497 .selections
20498 .all_anchors(&self.display_snapshot(cx))
20499 .iter()
20500 .map(|selection| {
20501 (
20502 buffer.clone(),
20503 (selection.start.text_anchor..selection.end.text_anchor)
20504 .to_point(buffer.read(cx)),
20505 )
20506 })
20507 .into_group_map();
20508
20509 cx.spawn_in(window, async move |_, cx| {
20510 workspace.update_in(cx, |workspace, window, cx| {
20511 Self::open_locations_in_multibuffer(
20512 workspace,
20513 locations,
20514 format!("Selections for '{title}'"),
20515 false,
20516 MultibufferSelectionMode::All,
20517 window,
20518 cx,
20519 );
20520 })
20521 })
20522 .detach();
20523 }
20524
20525 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20526 /// last highlight added will be used.
20527 ///
20528 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20529 pub fn highlight_rows<T: 'static>(
20530 &mut self,
20531 range: Range<Anchor>,
20532 color: Hsla,
20533 options: RowHighlightOptions,
20534 cx: &mut Context<Self>,
20535 ) {
20536 let snapshot = self.buffer().read(cx).snapshot(cx);
20537 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20538 let ix = row_highlights.binary_search_by(|highlight| {
20539 Ordering::Equal
20540 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20541 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20542 });
20543
20544 if let Err(mut ix) = ix {
20545 let index = post_inc(&mut self.highlight_order);
20546
20547 // If this range intersects with the preceding highlight, then merge it with
20548 // the preceding highlight. Otherwise insert a new highlight.
20549 let mut merged = false;
20550 if ix > 0 {
20551 let prev_highlight = &mut row_highlights[ix - 1];
20552 if prev_highlight
20553 .range
20554 .end
20555 .cmp(&range.start, &snapshot)
20556 .is_ge()
20557 {
20558 ix -= 1;
20559 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20560 prev_highlight.range.end = range.end;
20561 }
20562 merged = true;
20563 prev_highlight.index = index;
20564 prev_highlight.color = color;
20565 prev_highlight.options = options;
20566 }
20567 }
20568
20569 if !merged {
20570 row_highlights.insert(
20571 ix,
20572 RowHighlight {
20573 range,
20574 index,
20575 color,
20576 options,
20577 type_id: TypeId::of::<T>(),
20578 },
20579 );
20580 }
20581
20582 // If any of the following highlights intersect with this one, merge them.
20583 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20584 let highlight = &row_highlights[ix];
20585 if next_highlight
20586 .range
20587 .start
20588 .cmp(&highlight.range.end, &snapshot)
20589 .is_le()
20590 {
20591 if next_highlight
20592 .range
20593 .end
20594 .cmp(&highlight.range.end, &snapshot)
20595 .is_gt()
20596 {
20597 row_highlights[ix].range.end = next_highlight.range.end;
20598 }
20599 row_highlights.remove(ix + 1);
20600 } else {
20601 break;
20602 }
20603 }
20604 }
20605 }
20606
20607 /// Remove any highlighted row ranges of the given type that intersect the
20608 /// given ranges.
20609 pub fn remove_highlighted_rows<T: 'static>(
20610 &mut self,
20611 ranges_to_remove: Vec<Range<Anchor>>,
20612 cx: &mut Context<Self>,
20613 ) {
20614 let snapshot = self.buffer().read(cx).snapshot(cx);
20615 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20616 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20617 row_highlights.retain(|highlight| {
20618 while let Some(range_to_remove) = ranges_to_remove.peek() {
20619 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20620 Ordering::Less | Ordering::Equal => {
20621 ranges_to_remove.next();
20622 }
20623 Ordering::Greater => {
20624 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20625 Ordering::Less | Ordering::Equal => {
20626 return false;
20627 }
20628 Ordering::Greater => break,
20629 }
20630 }
20631 }
20632 }
20633
20634 true
20635 })
20636 }
20637
20638 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20639 pub fn clear_row_highlights<T: 'static>(&mut self) {
20640 self.highlighted_rows.remove(&TypeId::of::<T>());
20641 }
20642
20643 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20644 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20645 self.highlighted_rows
20646 .get(&TypeId::of::<T>())
20647 .map_or(&[] as &[_], |vec| vec.as_slice())
20648 .iter()
20649 .map(|highlight| (highlight.range.clone(), highlight.color))
20650 }
20651
20652 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20653 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20654 /// Allows to ignore certain kinds of highlights.
20655 pub fn highlighted_display_rows(
20656 &self,
20657 window: &mut Window,
20658 cx: &mut App,
20659 ) -> BTreeMap<DisplayRow, LineHighlight> {
20660 let snapshot = self.snapshot(window, cx);
20661 let mut used_highlight_orders = HashMap::default();
20662 self.highlighted_rows
20663 .iter()
20664 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20665 .fold(
20666 BTreeMap::<DisplayRow, LineHighlight>::new(),
20667 |mut unique_rows, highlight| {
20668 let start = highlight.range.start.to_display_point(&snapshot);
20669 let end = highlight.range.end.to_display_point(&snapshot);
20670 let start_row = start.row().0;
20671 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20672 && end.column() == 0
20673 {
20674 end.row().0.saturating_sub(1)
20675 } else {
20676 end.row().0
20677 };
20678 for row in start_row..=end_row {
20679 let used_index =
20680 used_highlight_orders.entry(row).or_insert(highlight.index);
20681 if highlight.index >= *used_index {
20682 *used_index = highlight.index;
20683 unique_rows.insert(
20684 DisplayRow(row),
20685 LineHighlight {
20686 include_gutter: highlight.options.include_gutter,
20687 border: None,
20688 background: highlight.color.into(),
20689 type_id: Some(highlight.type_id),
20690 },
20691 );
20692 }
20693 }
20694 unique_rows
20695 },
20696 )
20697 }
20698
20699 pub fn highlighted_display_row_for_autoscroll(
20700 &self,
20701 snapshot: &DisplaySnapshot,
20702 ) -> Option<DisplayRow> {
20703 self.highlighted_rows
20704 .values()
20705 .flat_map(|highlighted_rows| highlighted_rows.iter())
20706 .filter_map(|highlight| {
20707 if highlight.options.autoscroll {
20708 Some(highlight.range.start.to_display_point(snapshot).row())
20709 } else {
20710 None
20711 }
20712 })
20713 .min()
20714 }
20715
20716 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20717 self.highlight_background::<SearchWithinRange>(
20718 ranges,
20719 |colors| colors.colors().editor_document_highlight_read_background,
20720 cx,
20721 )
20722 }
20723
20724 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20725 self.breadcrumb_header = Some(new_header);
20726 }
20727
20728 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20729 self.clear_background_highlights::<SearchWithinRange>(cx);
20730 }
20731
20732 pub fn highlight_background<T: 'static>(
20733 &mut self,
20734 ranges: &[Range<Anchor>],
20735 color_fetcher: fn(&Theme) -> Hsla,
20736 cx: &mut Context<Self>,
20737 ) {
20738 self.background_highlights.insert(
20739 HighlightKey::Type(TypeId::of::<T>()),
20740 (color_fetcher, Arc::from(ranges)),
20741 );
20742 self.scrollbar_marker_state.dirty = true;
20743 cx.notify();
20744 }
20745
20746 pub fn highlight_background_key<T: 'static>(
20747 &mut self,
20748 key: usize,
20749 ranges: &[Range<Anchor>],
20750 color_fetcher: fn(&Theme) -> Hsla,
20751 cx: &mut Context<Self>,
20752 ) {
20753 self.background_highlights.insert(
20754 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20755 (color_fetcher, Arc::from(ranges)),
20756 );
20757 self.scrollbar_marker_state.dirty = true;
20758 cx.notify();
20759 }
20760
20761 pub fn clear_background_highlights<T: 'static>(
20762 &mut self,
20763 cx: &mut Context<Self>,
20764 ) -> Option<BackgroundHighlight> {
20765 let text_highlights = self
20766 .background_highlights
20767 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20768 if !text_highlights.1.is_empty() {
20769 self.scrollbar_marker_state.dirty = true;
20770 cx.notify();
20771 }
20772 Some(text_highlights)
20773 }
20774
20775 pub fn highlight_gutter<T: 'static>(
20776 &mut self,
20777 ranges: impl Into<Vec<Range<Anchor>>>,
20778 color_fetcher: fn(&App) -> Hsla,
20779 cx: &mut Context<Self>,
20780 ) {
20781 self.gutter_highlights
20782 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20783 cx.notify();
20784 }
20785
20786 pub fn clear_gutter_highlights<T: 'static>(
20787 &mut self,
20788 cx: &mut Context<Self>,
20789 ) -> Option<GutterHighlight> {
20790 cx.notify();
20791 self.gutter_highlights.remove(&TypeId::of::<T>())
20792 }
20793
20794 pub fn insert_gutter_highlight<T: 'static>(
20795 &mut self,
20796 range: Range<Anchor>,
20797 color_fetcher: fn(&App) -> Hsla,
20798 cx: &mut Context<Self>,
20799 ) {
20800 let snapshot = self.buffer().read(cx).snapshot(cx);
20801 let mut highlights = self
20802 .gutter_highlights
20803 .remove(&TypeId::of::<T>())
20804 .map(|(_, highlights)| highlights)
20805 .unwrap_or_default();
20806 let ix = highlights.binary_search_by(|highlight| {
20807 Ordering::Equal
20808 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20809 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20810 });
20811 if let Err(ix) = ix {
20812 highlights.insert(ix, range);
20813 }
20814 self.gutter_highlights
20815 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20816 }
20817
20818 pub fn remove_gutter_highlights<T: 'static>(
20819 &mut self,
20820 ranges_to_remove: Vec<Range<Anchor>>,
20821 cx: &mut Context<Self>,
20822 ) {
20823 let snapshot = self.buffer().read(cx).snapshot(cx);
20824 let Some((color_fetcher, mut gutter_highlights)) =
20825 self.gutter_highlights.remove(&TypeId::of::<T>())
20826 else {
20827 return;
20828 };
20829 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20830 gutter_highlights.retain(|highlight| {
20831 while let Some(range_to_remove) = ranges_to_remove.peek() {
20832 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20833 Ordering::Less | Ordering::Equal => {
20834 ranges_to_remove.next();
20835 }
20836 Ordering::Greater => {
20837 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20838 Ordering::Less | Ordering::Equal => {
20839 return false;
20840 }
20841 Ordering::Greater => break,
20842 }
20843 }
20844 }
20845 }
20846
20847 true
20848 });
20849 self.gutter_highlights
20850 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20851 }
20852
20853 #[cfg(feature = "test-support")]
20854 pub fn all_text_highlights(
20855 &self,
20856 window: &mut Window,
20857 cx: &mut Context<Self>,
20858 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20859 let snapshot = self.snapshot(window, cx);
20860 self.display_map.update(cx, |display_map, _| {
20861 display_map
20862 .all_text_highlights()
20863 .map(|highlight| {
20864 let (style, ranges) = highlight.as_ref();
20865 (
20866 *style,
20867 ranges
20868 .iter()
20869 .map(|range| range.clone().to_display_points(&snapshot))
20870 .collect(),
20871 )
20872 })
20873 .collect()
20874 })
20875 }
20876
20877 #[cfg(feature = "test-support")]
20878 pub fn all_text_background_highlights(
20879 &self,
20880 window: &mut Window,
20881 cx: &mut Context<Self>,
20882 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20883 let snapshot = self.snapshot(window, cx);
20884 let buffer = &snapshot.buffer_snapshot();
20885 let start = buffer.anchor_before(0);
20886 let end = buffer.anchor_after(buffer.len());
20887 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20888 }
20889
20890 #[cfg(any(test, feature = "test-support"))]
20891 pub fn sorted_background_highlights_in_range(
20892 &self,
20893 search_range: Range<Anchor>,
20894 display_snapshot: &DisplaySnapshot,
20895 theme: &Theme,
20896 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20897 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20898 res.sort_by(|a, b| {
20899 a.0.start
20900 .cmp(&b.0.start)
20901 .then_with(|| a.0.end.cmp(&b.0.end))
20902 .then_with(|| a.1.cmp(&b.1))
20903 });
20904 res
20905 }
20906
20907 #[cfg(feature = "test-support")]
20908 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20909 let snapshot = self.buffer().read(cx).snapshot(cx);
20910
20911 let highlights = self
20912 .background_highlights
20913 .get(&HighlightKey::Type(TypeId::of::<
20914 items::BufferSearchHighlights,
20915 >()));
20916
20917 if let Some((_color, ranges)) = highlights {
20918 ranges
20919 .iter()
20920 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20921 .collect_vec()
20922 } else {
20923 vec![]
20924 }
20925 }
20926
20927 fn document_highlights_for_position<'a>(
20928 &'a self,
20929 position: Anchor,
20930 buffer: &'a MultiBufferSnapshot,
20931 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20932 let read_highlights = self
20933 .background_highlights
20934 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20935 .map(|h| &h.1);
20936 let write_highlights = self
20937 .background_highlights
20938 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20939 .map(|h| &h.1);
20940 let left_position = position.bias_left(buffer);
20941 let right_position = position.bias_right(buffer);
20942 read_highlights
20943 .into_iter()
20944 .chain(write_highlights)
20945 .flat_map(move |ranges| {
20946 let start_ix = match ranges.binary_search_by(|probe| {
20947 let cmp = probe.end.cmp(&left_position, buffer);
20948 if cmp.is_ge() {
20949 Ordering::Greater
20950 } else {
20951 Ordering::Less
20952 }
20953 }) {
20954 Ok(i) | Err(i) => i,
20955 };
20956
20957 ranges[start_ix..]
20958 .iter()
20959 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20960 })
20961 }
20962
20963 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20964 self.background_highlights
20965 .get(&HighlightKey::Type(TypeId::of::<T>()))
20966 .is_some_and(|(_, highlights)| !highlights.is_empty())
20967 }
20968
20969 /// Returns all background highlights for a given range.
20970 ///
20971 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20972 pub fn background_highlights_in_range(
20973 &self,
20974 search_range: Range<Anchor>,
20975 display_snapshot: &DisplaySnapshot,
20976 theme: &Theme,
20977 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20978 let mut results = Vec::new();
20979 for (color_fetcher, ranges) in self.background_highlights.values() {
20980 let color = color_fetcher(theme);
20981 let start_ix = match ranges.binary_search_by(|probe| {
20982 let cmp = probe
20983 .end
20984 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20985 if cmp.is_gt() {
20986 Ordering::Greater
20987 } else {
20988 Ordering::Less
20989 }
20990 }) {
20991 Ok(i) | Err(i) => i,
20992 };
20993 for range in &ranges[start_ix..] {
20994 if range
20995 .start
20996 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20997 .is_ge()
20998 {
20999 break;
21000 }
21001
21002 let start = range.start.to_display_point(display_snapshot);
21003 let end = range.end.to_display_point(display_snapshot);
21004 results.push((start..end, color))
21005 }
21006 }
21007 results
21008 }
21009
21010 pub fn gutter_highlights_in_range(
21011 &self,
21012 search_range: Range<Anchor>,
21013 display_snapshot: &DisplaySnapshot,
21014 cx: &App,
21015 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21016 let mut results = Vec::new();
21017 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21018 let color = color_fetcher(cx);
21019 let start_ix = match ranges.binary_search_by(|probe| {
21020 let cmp = probe
21021 .end
21022 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21023 if cmp.is_gt() {
21024 Ordering::Greater
21025 } else {
21026 Ordering::Less
21027 }
21028 }) {
21029 Ok(i) | Err(i) => i,
21030 };
21031 for range in &ranges[start_ix..] {
21032 if range
21033 .start
21034 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21035 .is_ge()
21036 {
21037 break;
21038 }
21039
21040 let start = range.start.to_display_point(display_snapshot);
21041 let end = range.end.to_display_point(display_snapshot);
21042 results.push((start..end, color))
21043 }
21044 }
21045 results
21046 }
21047
21048 /// Get the text ranges corresponding to the redaction query
21049 pub fn redacted_ranges(
21050 &self,
21051 search_range: Range<Anchor>,
21052 display_snapshot: &DisplaySnapshot,
21053 cx: &App,
21054 ) -> Vec<Range<DisplayPoint>> {
21055 display_snapshot
21056 .buffer_snapshot()
21057 .redacted_ranges(search_range, |file| {
21058 if let Some(file) = file {
21059 file.is_private()
21060 && EditorSettings::get(
21061 Some(SettingsLocation {
21062 worktree_id: file.worktree_id(cx),
21063 path: file.path().as_ref(),
21064 }),
21065 cx,
21066 )
21067 .redact_private_values
21068 } else {
21069 false
21070 }
21071 })
21072 .map(|range| {
21073 range.start.to_display_point(display_snapshot)
21074 ..range.end.to_display_point(display_snapshot)
21075 })
21076 .collect()
21077 }
21078
21079 pub fn highlight_text_key<T: 'static>(
21080 &mut self,
21081 key: usize,
21082 ranges: Vec<Range<Anchor>>,
21083 style: HighlightStyle,
21084 cx: &mut Context<Self>,
21085 ) {
21086 self.display_map.update(cx, |map, _| {
21087 map.highlight_text(
21088 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21089 ranges,
21090 style,
21091 );
21092 });
21093 cx.notify();
21094 }
21095
21096 pub fn highlight_text<T: 'static>(
21097 &mut self,
21098 ranges: Vec<Range<Anchor>>,
21099 style: HighlightStyle,
21100 cx: &mut Context<Self>,
21101 ) {
21102 self.display_map.update(cx, |map, _| {
21103 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
21104 });
21105 cx.notify();
21106 }
21107
21108 pub fn text_highlights<'a, T: 'static>(
21109 &'a self,
21110 cx: &'a App,
21111 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21112 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21113 }
21114
21115 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21116 let cleared = self
21117 .display_map
21118 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21119 if cleared {
21120 cx.notify();
21121 }
21122 }
21123
21124 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21125 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21126 && self.focus_handle.is_focused(window)
21127 }
21128
21129 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21130 self.show_cursor_when_unfocused = is_enabled;
21131 cx.notify();
21132 }
21133
21134 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21135 cx.notify();
21136 }
21137
21138 fn on_debug_session_event(
21139 &mut self,
21140 _session: Entity<Session>,
21141 event: &SessionEvent,
21142 cx: &mut Context<Self>,
21143 ) {
21144 if let SessionEvent::InvalidateInlineValue = event {
21145 self.refresh_inline_values(cx);
21146 }
21147 }
21148
21149 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21150 let Some(project) = self.project.clone() else {
21151 return;
21152 };
21153
21154 if !self.inline_value_cache.enabled {
21155 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21156 self.splice_inlays(&inlays, Vec::new(), cx);
21157 return;
21158 }
21159
21160 let current_execution_position = self
21161 .highlighted_rows
21162 .get(&TypeId::of::<ActiveDebugLine>())
21163 .and_then(|lines| lines.last().map(|line| line.range.end));
21164
21165 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21166 let inline_values = editor
21167 .update(cx, |editor, cx| {
21168 let Some(current_execution_position) = current_execution_position else {
21169 return Some(Task::ready(Ok(Vec::new())));
21170 };
21171
21172 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21173 let snapshot = buffer.snapshot(cx);
21174
21175 let excerpt = snapshot.excerpt_containing(
21176 current_execution_position..current_execution_position,
21177 )?;
21178
21179 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21180 })?;
21181
21182 let range =
21183 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21184
21185 project.inline_values(buffer, range, cx)
21186 })
21187 .ok()
21188 .flatten()?
21189 .await
21190 .context("refreshing debugger inlays")
21191 .log_err()?;
21192
21193 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21194
21195 for (buffer_id, inline_value) in inline_values
21196 .into_iter()
21197 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21198 {
21199 buffer_inline_values
21200 .entry(buffer_id)
21201 .or_default()
21202 .push(inline_value);
21203 }
21204
21205 editor
21206 .update(cx, |editor, cx| {
21207 let snapshot = editor.buffer.read(cx).snapshot(cx);
21208 let mut new_inlays = Vec::default();
21209
21210 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21211 let buffer_id = buffer_snapshot.remote_id();
21212 buffer_inline_values
21213 .get(&buffer_id)
21214 .into_iter()
21215 .flatten()
21216 .for_each(|hint| {
21217 let inlay = Inlay::debugger(
21218 post_inc(&mut editor.next_inlay_id),
21219 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
21220 hint.text(),
21221 );
21222 if !inlay.text().chars().contains(&'\n') {
21223 new_inlays.push(inlay);
21224 }
21225 });
21226 }
21227
21228 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21229 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21230
21231 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21232 })
21233 .ok()?;
21234 Some(())
21235 });
21236 }
21237
21238 fn on_buffer_event(
21239 &mut self,
21240 multibuffer: &Entity<MultiBuffer>,
21241 event: &multi_buffer::Event,
21242 window: &mut Window,
21243 cx: &mut Context<Self>,
21244 ) {
21245 match event {
21246 multi_buffer::Event::Edited { edited_buffer } => {
21247 self.scrollbar_marker_state.dirty = true;
21248 self.active_indent_guides_state.dirty = true;
21249 self.refresh_active_diagnostics(cx);
21250 self.refresh_code_actions(window, cx);
21251 self.refresh_selected_text_highlights(true, window, cx);
21252 self.refresh_single_line_folds(window, cx);
21253 self.refresh_matching_bracket_highlights(window, cx);
21254 if self.has_active_edit_prediction() {
21255 self.update_visible_edit_prediction(window, cx);
21256 }
21257
21258 if let Some(buffer) = edited_buffer {
21259 if buffer.read(cx).file().is_none() {
21260 cx.emit(EditorEvent::TitleChanged);
21261 }
21262
21263 if self.project.is_some() {
21264 let buffer_id = buffer.read(cx).remote_id();
21265 self.register_buffer(buffer_id, cx);
21266 self.update_lsp_data(Some(buffer_id), window, cx);
21267 self.refresh_inlay_hints(
21268 InlayHintRefreshReason::BufferEdited(buffer_id),
21269 cx,
21270 );
21271 }
21272 }
21273
21274 cx.emit(EditorEvent::BufferEdited);
21275 cx.emit(SearchEvent::MatchesInvalidated);
21276
21277 let Some(project) = &self.project else { return };
21278 let (telemetry, is_via_ssh) = {
21279 let project = project.read(cx);
21280 let telemetry = project.client().telemetry().clone();
21281 let is_via_ssh = project.is_via_remote_server();
21282 (telemetry, is_via_ssh)
21283 };
21284 telemetry.log_edit_event("editor", is_via_ssh);
21285 }
21286 multi_buffer::Event::ExcerptsAdded {
21287 buffer,
21288 predecessor,
21289 excerpts,
21290 } => {
21291 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21292 let buffer_id = buffer.read(cx).remote_id();
21293 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21294 && let Some(project) = &self.project
21295 {
21296 update_uncommitted_diff_for_buffer(
21297 cx.entity(),
21298 project,
21299 [buffer.clone()],
21300 self.buffer.clone(),
21301 cx,
21302 )
21303 .detach();
21304 }
21305 self.update_lsp_data(Some(buffer_id), window, cx);
21306 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21307 cx.emit(EditorEvent::ExcerptsAdded {
21308 buffer: buffer.clone(),
21309 predecessor: *predecessor,
21310 excerpts: excerpts.clone(),
21311 });
21312 }
21313 multi_buffer::Event::ExcerptsRemoved {
21314 ids,
21315 removed_buffer_ids,
21316 } => {
21317 if let Some(inlay_hints) = &mut self.inlay_hints {
21318 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21319 }
21320 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21321 for buffer_id in removed_buffer_ids {
21322 self.registered_buffers.remove(buffer_id);
21323 }
21324 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21325 cx.emit(EditorEvent::ExcerptsRemoved {
21326 ids: ids.clone(),
21327 removed_buffer_ids: removed_buffer_ids.clone(),
21328 });
21329 }
21330 multi_buffer::Event::ExcerptsEdited {
21331 excerpt_ids,
21332 buffer_ids,
21333 } => {
21334 self.display_map.update(cx, |map, cx| {
21335 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21336 });
21337 cx.emit(EditorEvent::ExcerptsEdited {
21338 ids: excerpt_ids.clone(),
21339 });
21340 }
21341 multi_buffer::Event::ExcerptsExpanded { ids } => {
21342 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21343 self.refresh_document_highlights(cx);
21344 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21345 }
21346 multi_buffer::Event::Reparsed(buffer_id) => {
21347 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21348 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21349
21350 cx.emit(EditorEvent::Reparsed(*buffer_id));
21351 }
21352 multi_buffer::Event::DiffHunksToggled => {
21353 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21354 }
21355 multi_buffer::Event::LanguageChanged(buffer_id) => {
21356 self.registered_buffers.remove(&buffer_id);
21357 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21358 cx.emit(EditorEvent::Reparsed(*buffer_id));
21359 cx.notify();
21360 }
21361 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21362 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21363 multi_buffer::Event::FileHandleChanged
21364 | multi_buffer::Event::Reloaded
21365 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21366 multi_buffer::Event::DiagnosticsUpdated => {
21367 self.update_diagnostics_state(window, cx);
21368 }
21369 _ => {}
21370 };
21371 }
21372
21373 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21374 if !self.diagnostics_enabled() {
21375 return;
21376 }
21377 self.refresh_active_diagnostics(cx);
21378 self.refresh_inline_diagnostics(true, window, cx);
21379 self.scrollbar_marker_state.dirty = true;
21380 cx.notify();
21381 }
21382
21383 pub fn start_temporary_diff_override(&mut self) {
21384 self.load_diff_task.take();
21385 self.temporary_diff_override = true;
21386 }
21387
21388 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21389 self.temporary_diff_override = false;
21390 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21391 self.buffer.update(cx, |buffer, cx| {
21392 buffer.set_all_diff_hunks_collapsed(cx);
21393 });
21394
21395 if let Some(project) = self.project.clone() {
21396 self.load_diff_task = Some(
21397 update_uncommitted_diff_for_buffer(
21398 cx.entity(),
21399 &project,
21400 self.buffer.read(cx).all_buffers(),
21401 self.buffer.clone(),
21402 cx,
21403 )
21404 .shared(),
21405 );
21406 }
21407 }
21408
21409 fn on_display_map_changed(
21410 &mut self,
21411 _: Entity<DisplayMap>,
21412 _: &mut Window,
21413 cx: &mut Context<Self>,
21414 ) {
21415 cx.notify();
21416 }
21417
21418 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21419 if self.diagnostics_enabled() {
21420 let new_severity = EditorSettings::get_global(cx)
21421 .diagnostics_max_severity
21422 .unwrap_or(DiagnosticSeverity::Hint);
21423 self.set_max_diagnostics_severity(new_severity, cx);
21424 }
21425 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21426 self.update_edit_prediction_settings(cx);
21427 self.refresh_edit_prediction(true, false, window, cx);
21428 self.refresh_inline_values(cx);
21429 self.refresh_inlay_hints(
21430 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21431 self.selections.newest_anchor().head(),
21432 &self.buffer.read(cx).snapshot(cx),
21433 cx,
21434 )),
21435 cx,
21436 );
21437
21438 let old_cursor_shape = self.cursor_shape;
21439 let old_show_breadcrumbs = self.show_breadcrumbs;
21440
21441 {
21442 let editor_settings = EditorSettings::get_global(cx);
21443 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21444 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21445 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21446 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21447 }
21448
21449 if old_cursor_shape != self.cursor_shape {
21450 cx.emit(EditorEvent::CursorShapeChanged);
21451 }
21452
21453 if old_show_breadcrumbs != self.show_breadcrumbs {
21454 cx.emit(EditorEvent::BreadcrumbsChanged);
21455 }
21456
21457 let project_settings = ProjectSettings::get_global(cx);
21458 self.buffer_serialization = self
21459 .should_serialize_buffer()
21460 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21461
21462 if self.mode.is_full() {
21463 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21464 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21465 if self.show_inline_diagnostics != show_inline_diagnostics {
21466 self.show_inline_diagnostics = show_inline_diagnostics;
21467 self.refresh_inline_diagnostics(false, window, cx);
21468 }
21469
21470 if self.git_blame_inline_enabled != inline_blame_enabled {
21471 self.toggle_git_blame_inline_internal(false, window, cx);
21472 }
21473
21474 let minimap_settings = EditorSettings::get_global(cx).minimap;
21475 if self.minimap_visibility != MinimapVisibility::Disabled {
21476 if self.minimap_visibility.settings_visibility()
21477 != minimap_settings.minimap_enabled()
21478 {
21479 self.set_minimap_visibility(
21480 MinimapVisibility::for_mode(self.mode(), cx),
21481 window,
21482 cx,
21483 );
21484 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21485 minimap_entity.update(cx, |minimap_editor, cx| {
21486 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21487 })
21488 }
21489 }
21490 }
21491
21492 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21493 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21494 }) {
21495 if !inlay_splice.is_empty() {
21496 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21497 }
21498 self.refresh_colors_for_visible_range(None, window, cx);
21499 }
21500
21501 cx.notify();
21502 }
21503
21504 pub fn set_searchable(&mut self, searchable: bool) {
21505 self.searchable = searchable;
21506 }
21507
21508 pub fn searchable(&self) -> bool {
21509 self.searchable
21510 }
21511
21512 pub fn open_excerpts_in_split(
21513 &mut self,
21514 _: &OpenExcerptsSplit,
21515 window: &mut Window,
21516 cx: &mut Context<Self>,
21517 ) {
21518 self.open_excerpts_common(None, true, window, cx)
21519 }
21520
21521 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21522 self.open_excerpts_common(None, false, window, cx)
21523 }
21524
21525 fn open_excerpts_common(
21526 &mut self,
21527 jump_data: Option<JumpData>,
21528 split: bool,
21529 window: &mut Window,
21530 cx: &mut Context<Self>,
21531 ) {
21532 let Some(workspace) = self.workspace() else {
21533 cx.propagate();
21534 return;
21535 };
21536
21537 if self.buffer.read(cx).is_singleton() {
21538 cx.propagate();
21539 return;
21540 }
21541
21542 let mut new_selections_by_buffer = HashMap::default();
21543 match &jump_data {
21544 Some(JumpData::MultiBufferPoint {
21545 excerpt_id,
21546 position,
21547 anchor,
21548 line_offset_from_top,
21549 }) => {
21550 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21551 if let Some(buffer) = multi_buffer_snapshot
21552 .buffer_id_for_excerpt(*excerpt_id)
21553 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21554 {
21555 let buffer_snapshot = buffer.read(cx).snapshot();
21556 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21557 language::ToPoint::to_point(anchor, &buffer_snapshot)
21558 } else {
21559 buffer_snapshot.clip_point(*position, Bias::Left)
21560 };
21561 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21562 new_selections_by_buffer.insert(
21563 buffer,
21564 (
21565 vec![jump_to_offset..jump_to_offset],
21566 Some(*line_offset_from_top),
21567 ),
21568 );
21569 }
21570 }
21571 Some(JumpData::MultiBufferRow {
21572 row,
21573 line_offset_from_top,
21574 }) => {
21575 let point = MultiBufferPoint::new(row.0, 0);
21576 if let Some((buffer, buffer_point, _)) =
21577 self.buffer.read(cx).point_to_buffer_point(point, cx)
21578 {
21579 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21580 new_selections_by_buffer
21581 .entry(buffer)
21582 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21583 .0
21584 .push(buffer_offset..buffer_offset)
21585 }
21586 }
21587 None => {
21588 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21589 let multi_buffer = self.buffer.read(cx);
21590 for selection in selections {
21591 for (snapshot, range, _, anchor) in multi_buffer
21592 .snapshot(cx)
21593 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21594 {
21595 if let Some(anchor) = anchor {
21596 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21597 else {
21598 continue;
21599 };
21600 let offset = text::ToOffset::to_offset(
21601 &anchor.text_anchor,
21602 &buffer_handle.read(cx).snapshot(),
21603 );
21604 let range = offset..offset;
21605 new_selections_by_buffer
21606 .entry(buffer_handle)
21607 .or_insert((Vec::new(), None))
21608 .0
21609 .push(range)
21610 } else {
21611 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21612 else {
21613 continue;
21614 };
21615 new_selections_by_buffer
21616 .entry(buffer_handle)
21617 .or_insert((Vec::new(), None))
21618 .0
21619 .push(range)
21620 }
21621 }
21622 }
21623 }
21624 }
21625
21626 new_selections_by_buffer
21627 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21628
21629 if new_selections_by_buffer.is_empty() {
21630 return;
21631 }
21632
21633 // We defer the pane interaction because we ourselves are a workspace item
21634 // and activating a new item causes the pane to call a method on us reentrantly,
21635 // which panics if we're on the stack.
21636 window.defer(cx, move |window, cx| {
21637 workspace.update(cx, |workspace, cx| {
21638 let pane = if split {
21639 workspace.adjacent_pane(window, cx)
21640 } else {
21641 workspace.active_pane().clone()
21642 };
21643
21644 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21645 let editor = buffer
21646 .read(cx)
21647 .file()
21648 .is_none()
21649 .then(|| {
21650 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21651 // so `workspace.open_project_item` will never find them, always opening a new editor.
21652 // Instead, we try to activate the existing editor in the pane first.
21653 let (editor, pane_item_index) =
21654 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21655 let editor = item.downcast::<Editor>()?;
21656 let singleton_buffer =
21657 editor.read(cx).buffer().read(cx).as_singleton()?;
21658 if singleton_buffer == buffer {
21659 Some((editor, i))
21660 } else {
21661 None
21662 }
21663 })?;
21664 pane.update(cx, |pane, cx| {
21665 pane.activate_item(pane_item_index, true, true, window, cx)
21666 });
21667 Some(editor)
21668 })
21669 .flatten()
21670 .unwrap_or_else(|| {
21671 workspace.open_project_item::<Self>(
21672 pane.clone(),
21673 buffer,
21674 true,
21675 true,
21676 window,
21677 cx,
21678 )
21679 });
21680
21681 editor.update(cx, |editor, cx| {
21682 let autoscroll = match scroll_offset {
21683 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21684 None => Autoscroll::newest(),
21685 };
21686 let nav_history = editor.nav_history.take();
21687 editor.change_selections(
21688 SelectionEffects::scroll(autoscroll),
21689 window,
21690 cx,
21691 |s| {
21692 s.select_ranges(ranges);
21693 },
21694 );
21695 editor.nav_history = nav_history;
21696 });
21697 }
21698 })
21699 });
21700 }
21701
21702 // For now, don't allow opening excerpts in buffers that aren't backed by
21703 // regular project files.
21704 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21705 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21706 }
21707
21708 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21709 let snapshot = self.buffer.read(cx).read(cx);
21710 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21711 Some(
21712 ranges
21713 .iter()
21714 .map(move |range| {
21715 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21716 })
21717 .collect(),
21718 )
21719 }
21720
21721 fn selection_replacement_ranges(
21722 &self,
21723 range: Range<OffsetUtf16>,
21724 cx: &mut App,
21725 ) -> Vec<Range<OffsetUtf16>> {
21726 let selections = self
21727 .selections
21728 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21729 let newest_selection = selections
21730 .iter()
21731 .max_by_key(|selection| selection.id)
21732 .unwrap();
21733 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21734 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21735 let snapshot = self.buffer.read(cx).read(cx);
21736 selections
21737 .into_iter()
21738 .map(|mut selection| {
21739 selection.start.0 =
21740 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21741 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21742 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21743 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21744 })
21745 .collect()
21746 }
21747
21748 fn report_editor_event(
21749 &self,
21750 reported_event: ReportEditorEvent,
21751 file_extension: Option<String>,
21752 cx: &App,
21753 ) {
21754 if cfg!(any(test, feature = "test-support")) {
21755 return;
21756 }
21757
21758 let Some(project) = &self.project else { return };
21759
21760 // If None, we are in a file without an extension
21761 let file = self
21762 .buffer
21763 .read(cx)
21764 .as_singleton()
21765 .and_then(|b| b.read(cx).file());
21766 let file_extension = file_extension.or(file
21767 .as_ref()
21768 .and_then(|file| Path::new(file.file_name(cx)).extension())
21769 .and_then(|e| e.to_str())
21770 .map(|a| a.to_string()));
21771
21772 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
21773 .map(|vim_mode| vim_mode.0)
21774 .unwrap_or(false);
21775
21776 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21777 let copilot_enabled = edit_predictions_provider
21778 == language::language_settings::EditPredictionProvider::Copilot;
21779 let copilot_enabled_for_language = self
21780 .buffer
21781 .read(cx)
21782 .language_settings(cx)
21783 .show_edit_predictions;
21784
21785 let project = project.read(cx);
21786 let event_type = reported_event.event_type();
21787
21788 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21789 telemetry::event!(
21790 event_type,
21791 type = if auto_saved {"autosave"} else {"manual"},
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 } else {
21800 telemetry::event!(
21801 event_type,
21802 file_extension,
21803 vim_mode,
21804 copilot_enabled,
21805 copilot_enabled_for_language,
21806 edit_predictions_provider,
21807 is_via_ssh = project.is_via_remote_server(),
21808 );
21809 };
21810 }
21811
21812 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21813 /// with each line being an array of {text, highlight} objects.
21814 fn copy_highlight_json(
21815 &mut self,
21816 _: &CopyHighlightJson,
21817 window: &mut Window,
21818 cx: &mut Context<Self>,
21819 ) {
21820 #[derive(Serialize)]
21821 struct Chunk<'a> {
21822 text: String,
21823 highlight: Option<&'a str>,
21824 }
21825
21826 let snapshot = self.buffer.read(cx).snapshot(cx);
21827 let range = self
21828 .selected_text_range(false, window, cx)
21829 .and_then(|selection| {
21830 if selection.range.is_empty() {
21831 None
21832 } else {
21833 Some(
21834 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21835 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21836 )
21837 }
21838 })
21839 .unwrap_or_else(|| 0..snapshot.len());
21840
21841 let chunks = snapshot.chunks(range, true);
21842 let mut lines = Vec::new();
21843 let mut line: VecDeque<Chunk> = VecDeque::new();
21844
21845 let Some(style) = self.style.as_ref() else {
21846 return;
21847 };
21848
21849 for chunk in chunks {
21850 let highlight = chunk
21851 .syntax_highlight_id
21852 .and_then(|id| id.name(&style.syntax));
21853 let mut chunk_lines = chunk.text.split('\n').peekable();
21854 while let Some(text) = chunk_lines.next() {
21855 let mut merged_with_last_token = false;
21856 if let Some(last_token) = line.back_mut()
21857 && last_token.highlight == highlight
21858 {
21859 last_token.text.push_str(text);
21860 merged_with_last_token = true;
21861 }
21862
21863 if !merged_with_last_token {
21864 line.push_back(Chunk {
21865 text: text.into(),
21866 highlight,
21867 });
21868 }
21869
21870 if chunk_lines.peek().is_some() {
21871 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21872 line.pop_front();
21873 }
21874 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21875 line.pop_back();
21876 }
21877
21878 lines.push(mem::take(&mut line));
21879 }
21880 }
21881 }
21882
21883 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21884 return;
21885 };
21886 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21887 }
21888
21889 pub fn open_context_menu(
21890 &mut self,
21891 _: &OpenContextMenu,
21892 window: &mut Window,
21893 cx: &mut Context<Self>,
21894 ) {
21895 self.request_autoscroll(Autoscroll::newest(), cx);
21896 let position = self
21897 .selections
21898 .newest_display(&self.display_snapshot(cx))
21899 .start;
21900 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21901 }
21902
21903 pub fn replay_insert_event(
21904 &mut self,
21905 text: &str,
21906 relative_utf16_range: Option<Range<isize>>,
21907 window: &mut Window,
21908 cx: &mut Context<Self>,
21909 ) {
21910 if !self.input_enabled {
21911 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21912 return;
21913 }
21914 if let Some(relative_utf16_range) = relative_utf16_range {
21915 let selections = self
21916 .selections
21917 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21918 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21919 let new_ranges = selections.into_iter().map(|range| {
21920 let start = OffsetUtf16(
21921 range
21922 .head()
21923 .0
21924 .saturating_add_signed(relative_utf16_range.start),
21925 );
21926 let end = OffsetUtf16(
21927 range
21928 .head()
21929 .0
21930 .saturating_add_signed(relative_utf16_range.end),
21931 );
21932 start..end
21933 });
21934 s.select_ranges(new_ranges);
21935 });
21936 }
21937
21938 self.handle_input(text, window, cx);
21939 }
21940
21941 pub fn is_focused(&self, window: &Window) -> bool {
21942 self.focus_handle.is_focused(window)
21943 }
21944
21945 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21946 cx.emit(EditorEvent::Focused);
21947
21948 if let Some(descendant) = self
21949 .last_focused_descendant
21950 .take()
21951 .and_then(|descendant| descendant.upgrade())
21952 {
21953 window.focus(&descendant);
21954 } else {
21955 if let Some(blame) = self.blame.as_ref() {
21956 blame.update(cx, GitBlame::focus)
21957 }
21958
21959 self.blink_manager.update(cx, BlinkManager::enable);
21960 self.show_cursor_names(window, cx);
21961 self.buffer.update(cx, |buffer, cx| {
21962 buffer.finalize_last_transaction(cx);
21963 if self.leader_id.is_none() {
21964 buffer.set_active_selections(
21965 &self.selections.disjoint_anchors_arc(),
21966 self.selections.line_mode(),
21967 self.cursor_shape,
21968 cx,
21969 );
21970 }
21971 });
21972 }
21973 }
21974
21975 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21976 cx.emit(EditorEvent::FocusedIn)
21977 }
21978
21979 fn handle_focus_out(
21980 &mut self,
21981 event: FocusOutEvent,
21982 _window: &mut Window,
21983 cx: &mut Context<Self>,
21984 ) {
21985 if event.blurred != self.focus_handle {
21986 self.last_focused_descendant = Some(event.blurred);
21987 }
21988 self.selection_drag_state = SelectionDragState::None;
21989 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21990 }
21991
21992 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21993 self.blink_manager.update(cx, BlinkManager::disable);
21994 self.buffer
21995 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21996
21997 if let Some(blame) = self.blame.as_ref() {
21998 blame.update(cx, GitBlame::blur)
21999 }
22000 if !self.hover_state.focused(window, cx) {
22001 hide_hover(self, cx);
22002 }
22003 if !self
22004 .context_menu
22005 .borrow()
22006 .as_ref()
22007 .is_some_and(|context_menu| context_menu.focused(window, cx))
22008 {
22009 self.hide_context_menu(window, cx);
22010 }
22011 self.take_active_edit_prediction(cx);
22012 cx.emit(EditorEvent::Blurred);
22013 cx.notify();
22014 }
22015
22016 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22017 let mut pending: String = window
22018 .pending_input_keystrokes()
22019 .into_iter()
22020 .flatten()
22021 .filter_map(|keystroke| {
22022 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
22023 keystroke.key_char.clone()
22024 } else {
22025 None
22026 }
22027 })
22028 .collect();
22029
22030 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22031 pending = "".to_string();
22032 }
22033
22034 let existing_pending = self
22035 .text_highlights::<PendingInput>(cx)
22036 .map(|(_, ranges)| ranges.to_vec());
22037 if existing_pending.is_none() && pending.is_empty() {
22038 return;
22039 }
22040 let transaction =
22041 self.transact(window, cx, |this, window, cx| {
22042 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
22043 let edits = selections
22044 .iter()
22045 .map(|selection| (selection.end..selection.end, pending.clone()));
22046 this.edit(edits, cx);
22047 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22048 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22049 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22050 }));
22051 });
22052 if let Some(existing_ranges) = existing_pending {
22053 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22054 this.edit(edits, cx);
22055 }
22056 });
22057
22058 let snapshot = self.snapshot(window, cx);
22059 let ranges = self
22060 .selections
22061 .all::<usize>(&snapshot.display_snapshot)
22062 .into_iter()
22063 .map(|selection| {
22064 snapshot.buffer_snapshot().anchor_after(selection.end)
22065 ..snapshot
22066 .buffer_snapshot()
22067 .anchor_before(selection.end + pending.len())
22068 })
22069 .collect();
22070
22071 if pending.is_empty() {
22072 self.clear_highlights::<PendingInput>(cx);
22073 } else {
22074 self.highlight_text::<PendingInput>(
22075 ranges,
22076 HighlightStyle {
22077 underline: Some(UnderlineStyle {
22078 thickness: px(1.),
22079 color: None,
22080 wavy: false,
22081 }),
22082 ..Default::default()
22083 },
22084 cx,
22085 );
22086 }
22087
22088 self.ime_transaction = self.ime_transaction.or(transaction);
22089 if let Some(transaction) = self.ime_transaction {
22090 self.buffer.update(cx, |buffer, cx| {
22091 buffer.group_until_transaction(transaction, cx);
22092 });
22093 }
22094
22095 if self.text_highlights::<PendingInput>(cx).is_none() {
22096 self.ime_transaction.take();
22097 }
22098 }
22099
22100 pub fn register_action_renderer(
22101 &mut self,
22102 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22103 ) -> Subscription {
22104 let id = self.next_editor_action_id.post_inc();
22105 self.editor_actions
22106 .borrow_mut()
22107 .insert(id, Box::new(listener));
22108
22109 let editor_actions = self.editor_actions.clone();
22110 Subscription::new(move || {
22111 editor_actions.borrow_mut().remove(&id);
22112 })
22113 }
22114
22115 pub fn register_action<A: Action>(
22116 &mut self,
22117 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22118 ) -> Subscription {
22119 let id = self.next_editor_action_id.post_inc();
22120 let listener = Arc::new(listener);
22121 self.editor_actions.borrow_mut().insert(
22122 id,
22123 Box::new(move |_, window, _| {
22124 let listener = listener.clone();
22125 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22126 let action = action.downcast_ref().unwrap();
22127 if phase == DispatchPhase::Bubble {
22128 listener(action, window, cx)
22129 }
22130 })
22131 }),
22132 );
22133
22134 let editor_actions = self.editor_actions.clone();
22135 Subscription::new(move || {
22136 editor_actions.borrow_mut().remove(&id);
22137 })
22138 }
22139
22140 pub fn file_header_size(&self) -> u32 {
22141 FILE_HEADER_HEIGHT
22142 }
22143
22144 pub fn restore(
22145 &mut self,
22146 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22147 window: &mut Window,
22148 cx: &mut Context<Self>,
22149 ) {
22150 let workspace = self.workspace();
22151 let project = self.project();
22152 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22153 let mut tasks = Vec::new();
22154 for (buffer_id, changes) in revert_changes {
22155 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22156 buffer.update(cx, |buffer, cx| {
22157 buffer.edit(
22158 changes
22159 .into_iter()
22160 .map(|(range, text)| (range, text.to_string())),
22161 None,
22162 cx,
22163 );
22164 });
22165
22166 if let Some(project) =
22167 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22168 {
22169 project.update(cx, |project, cx| {
22170 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22171 })
22172 }
22173 }
22174 }
22175 tasks
22176 });
22177 cx.spawn_in(window, async move |_, cx| {
22178 for (buffer, task) in save_tasks {
22179 let result = task.await;
22180 if result.is_err() {
22181 let Some(path) = buffer
22182 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22183 .ok()
22184 else {
22185 continue;
22186 };
22187 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22188 let Some(task) = cx
22189 .update_window_entity(workspace, |workspace, window, cx| {
22190 workspace
22191 .open_path_preview(path, None, false, false, false, window, cx)
22192 })
22193 .ok()
22194 else {
22195 continue;
22196 };
22197 task.await.log_err();
22198 }
22199 }
22200 }
22201 })
22202 .detach();
22203 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22204 selections.refresh()
22205 });
22206 }
22207
22208 pub fn to_pixel_point(
22209 &self,
22210 source: multi_buffer::Anchor,
22211 editor_snapshot: &EditorSnapshot,
22212 window: &mut Window,
22213 ) -> Option<gpui::Point<Pixels>> {
22214 let source_point = source.to_display_point(editor_snapshot);
22215 self.display_to_pixel_point(source_point, editor_snapshot, window)
22216 }
22217
22218 pub fn display_to_pixel_point(
22219 &self,
22220 source: DisplayPoint,
22221 editor_snapshot: &EditorSnapshot,
22222 window: &mut Window,
22223 ) -> Option<gpui::Point<Pixels>> {
22224 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22225 let text_layout_details = self.text_layout_details(window);
22226 let scroll_top = text_layout_details
22227 .scroll_anchor
22228 .scroll_position(editor_snapshot)
22229 .y;
22230
22231 if source.row().as_f64() < scroll_top.floor() {
22232 return None;
22233 }
22234 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22235 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22236 Some(gpui::Point::new(source_x, source_y))
22237 }
22238
22239 pub fn has_visible_completions_menu(&self) -> bool {
22240 !self.edit_prediction_preview_is_active()
22241 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22242 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22243 })
22244 }
22245
22246 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22247 if self.mode.is_minimap() {
22248 return;
22249 }
22250 self.addons
22251 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22252 }
22253
22254 pub fn unregister_addon<T: Addon>(&mut self) {
22255 self.addons.remove(&std::any::TypeId::of::<T>());
22256 }
22257
22258 pub fn addon<T: Addon>(&self) -> Option<&T> {
22259 let type_id = std::any::TypeId::of::<T>();
22260 self.addons
22261 .get(&type_id)
22262 .and_then(|item| item.to_any().downcast_ref::<T>())
22263 }
22264
22265 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22266 let type_id = std::any::TypeId::of::<T>();
22267 self.addons
22268 .get_mut(&type_id)
22269 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22270 }
22271
22272 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22273 let text_layout_details = self.text_layout_details(window);
22274 let style = &text_layout_details.editor_style;
22275 let font_id = window.text_system().resolve_font(&style.text.font());
22276 let font_size = style.text.font_size.to_pixels(window.rem_size());
22277 let line_height = style.text.line_height_in_pixels(window.rem_size());
22278 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22279 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22280
22281 CharacterDimensions {
22282 em_width,
22283 em_advance,
22284 line_height,
22285 }
22286 }
22287
22288 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22289 self.load_diff_task.clone()
22290 }
22291
22292 fn read_metadata_from_db(
22293 &mut self,
22294 item_id: u64,
22295 workspace_id: WorkspaceId,
22296 window: &mut Window,
22297 cx: &mut Context<Editor>,
22298 ) {
22299 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22300 && !self.mode.is_minimap()
22301 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22302 {
22303 let buffer_snapshot = OnceCell::new();
22304
22305 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22306 && !folds.is_empty()
22307 {
22308 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22309 self.fold_ranges(
22310 folds
22311 .into_iter()
22312 .map(|(start, end)| {
22313 snapshot.clip_offset(start, Bias::Left)
22314 ..snapshot.clip_offset(end, Bias::Right)
22315 })
22316 .collect(),
22317 false,
22318 window,
22319 cx,
22320 );
22321 }
22322
22323 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22324 && !selections.is_empty()
22325 {
22326 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22327 // skip adding the initial selection to selection history
22328 self.selection_history.mode = SelectionHistoryMode::Skipping;
22329 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22330 s.select_ranges(selections.into_iter().map(|(start, end)| {
22331 snapshot.clip_offset(start, Bias::Left)
22332 ..snapshot.clip_offset(end, Bias::Right)
22333 }));
22334 });
22335 self.selection_history.mode = SelectionHistoryMode::Normal;
22336 };
22337 }
22338
22339 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22340 }
22341
22342 fn update_lsp_data(
22343 &mut self,
22344 for_buffer: Option<BufferId>,
22345 window: &mut Window,
22346 cx: &mut Context<'_, Self>,
22347 ) {
22348 self.pull_diagnostics(for_buffer, window, cx);
22349 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22350 }
22351
22352 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22353 if self.ignore_lsp_data() {
22354 return;
22355 }
22356 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22357 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22358 }
22359 }
22360
22361 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22362 if self.ignore_lsp_data() {
22363 return;
22364 }
22365
22366 if !self.registered_buffers.contains_key(&buffer_id)
22367 && let Some(project) = self.project.as_ref()
22368 {
22369 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22370 project.update(cx, |project, cx| {
22371 self.registered_buffers.insert(
22372 buffer_id,
22373 project.register_buffer_with_language_servers(&buffer, cx),
22374 );
22375 });
22376 } else {
22377 self.registered_buffers.remove(&buffer_id);
22378 }
22379 }
22380 }
22381
22382 fn ignore_lsp_data(&self) -> bool {
22383 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22384 // skip any LSP updates for it.
22385 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22386 }
22387}
22388
22389fn edit_for_markdown_paste<'a>(
22390 buffer: &MultiBufferSnapshot,
22391 range: Range<usize>,
22392 to_insert: &'a str,
22393 url: Option<url::Url>,
22394) -> (Range<usize>, Cow<'a, str>) {
22395 if url.is_none() {
22396 return (range, Cow::Borrowed(to_insert));
22397 };
22398
22399 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22400
22401 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22402 Cow::Borrowed(to_insert)
22403 } else {
22404 Cow::Owned(format!("[{old_text}]({to_insert})"))
22405 };
22406 (range, new_text)
22407}
22408
22409fn process_completion_for_edit(
22410 completion: &Completion,
22411 intent: CompletionIntent,
22412 buffer: &Entity<Buffer>,
22413 cursor_position: &text::Anchor,
22414 cx: &mut Context<Editor>,
22415) -> CompletionEdit {
22416 let buffer = buffer.read(cx);
22417 let buffer_snapshot = buffer.snapshot();
22418 let (snippet, new_text) = if completion.is_snippet() {
22419 let mut snippet_source = completion.new_text.clone();
22420 // Workaround for typescript language server issues so that methods don't expand within
22421 // strings and functions with type expressions. The previous point is used because the query
22422 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22423 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22424 let previous_point = if previous_point.column > 0 {
22425 cursor_position.to_previous_offset(&buffer_snapshot)
22426 } else {
22427 cursor_position.to_offset(&buffer_snapshot)
22428 };
22429 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22430 && scope.prefers_label_for_snippet_in_completion()
22431 && let Some(label) = completion.label()
22432 && matches!(
22433 completion.kind(),
22434 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22435 )
22436 {
22437 snippet_source = label;
22438 }
22439 match Snippet::parse(&snippet_source).log_err() {
22440 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22441 None => (None, completion.new_text.clone()),
22442 }
22443 } else {
22444 (None, completion.new_text.clone())
22445 };
22446
22447 let mut range_to_replace = {
22448 let replace_range = &completion.replace_range;
22449 if let CompletionSource::Lsp {
22450 insert_range: Some(insert_range),
22451 ..
22452 } = &completion.source
22453 {
22454 debug_assert_eq!(
22455 insert_range.start, replace_range.start,
22456 "insert_range and replace_range should start at the same position"
22457 );
22458 debug_assert!(
22459 insert_range
22460 .start
22461 .cmp(cursor_position, &buffer_snapshot)
22462 .is_le(),
22463 "insert_range should start before or at cursor position"
22464 );
22465 debug_assert!(
22466 replace_range
22467 .start
22468 .cmp(cursor_position, &buffer_snapshot)
22469 .is_le(),
22470 "replace_range should start before or at cursor position"
22471 );
22472
22473 let should_replace = match intent {
22474 CompletionIntent::CompleteWithInsert => false,
22475 CompletionIntent::CompleteWithReplace => true,
22476 CompletionIntent::Complete | CompletionIntent::Compose => {
22477 let insert_mode =
22478 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22479 .completions
22480 .lsp_insert_mode;
22481 match insert_mode {
22482 LspInsertMode::Insert => false,
22483 LspInsertMode::Replace => true,
22484 LspInsertMode::ReplaceSubsequence => {
22485 let mut text_to_replace = buffer.chars_for_range(
22486 buffer.anchor_before(replace_range.start)
22487 ..buffer.anchor_after(replace_range.end),
22488 );
22489 let mut current_needle = text_to_replace.next();
22490 for haystack_ch in completion.label.text.chars() {
22491 if let Some(needle_ch) = current_needle
22492 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22493 {
22494 current_needle = text_to_replace.next();
22495 }
22496 }
22497 current_needle.is_none()
22498 }
22499 LspInsertMode::ReplaceSuffix => {
22500 if replace_range
22501 .end
22502 .cmp(cursor_position, &buffer_snapshot)
22503 .is_gt()
22504 {
22505 let range_after_cursor = *cursor_position..replace_range.end;
22506 let text_after_cursor = buffer
22507 .text_for_range(
22508 buffer.anchor_before(range_after_cursor.start)
22509 ..buffer.anchor_after(range_after_cursor.end),
22510 )
22511 .collect::<String>()
22512 .to_ascii_lowercase();
22513 completion
22514 .label
22515 .text
22516 .to_ascii_lowercase()
22517 .ends_with(&text_after_cursor)
22518 } else {
22519 true
22520 }
22521 }
22522 }
22523 }
22524 };
22525
22526 if should_replace {
22527 replace_range.clone()
22528 } else {
22529 insert_range.clone()
22530 }
22531 } else {
22532 replace_range.clone()
22533 }
22534 };
22535
22536 if range_to_replace
22537 .end
22538 .cmp(cursor_position, &buffer_snapshot)
22539 .is_lt()
22540 {
22541 range_to_replace.end = *cursor_position;
22542 }
22543
22544 CompletionEdit {
22545 new_text,
22546 replace_range: range_to_replace.to_offset(buffer),
22547 snippet,
22548 }
22549}
22550
22551struct CompletionEdit {
22552 new_text: String,
22553 replace_range: Range<usize>,
22554 snippet: Option<Snippet>,
22555}
22556
22557fn insert_extra_newline_brackets(
22558 buffer: &MultiBufferSnapshot,
22559 range: Range<usize>,
22560 language: &language::LanguageScope,
22561) -> bool {
22562 let leading_whitespace_len = buffer
22563 .reversed_chars_at(range.start)
22564 .take_while(|c| c.is_whitespace() && *c != '\n')
22565 .map(|c| c.len_utf8())
22566 .sum::<usize>();
22567 let trailing_whitespace_len = buffer
22568 .chars_at(range.end)
22569 .take_while(|c| c.is_whitespace() && *c != '\n')
22570 .map(|c| c.len_utf8())
22571 .sum::<usize>();
22572 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22573
22574 language.brackets().any(|(pair, enabled)| {
22575 let pair_start = pair.start.trim_end();
22576 let pair_end = pair.end.trim_start();
22577
22578 enabled
22579 && pair.newline
22580 && buffer.contains_str_at(range.end, pair_end)
22581 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22582 })
22583}
22584
22585fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22586 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22587 [(buffer, range, _)] => (*buffer, range.clone()),
22588 _ => return false,
22589 };
22590 let pair = {
22591 let mut result: Option<BracketMatch> = None;
22592
22593 for pair in buffer
22594 .all_bracket_ranges(range.clone())
22595 .filter(move |pair| {
22596 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22597 })
22598 {
22599 let len = pair.close_range.end - pair.open_range.start;
22600
22601 if let Some(existing) = &result {
22602 let existing_len = existing.close_range.end - existing.open_range.start;
22603 if len > existing_len {
22604 continue;
22605 }
22606 }
22607
22608 result = Some(pair);
22609 }
22610
22611 result
22612 };
22613 let Some(pair) = pair else {
22614 return false;
22615 };
22616 pair.newline_only
22617 && buffer
22618 .chars_for_range(pair.open_range.end..range.start)
22619 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22620 .all(|c| c.is_whitespace() && c != '\n')
22621}
22622
22623fn update_uncommitted_diff_for_buffer(
22624 editor: Entity<Editor>,
22625 project: &Entity<Project>,
22626 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22627 buffer: Entity<MultiBuffer>,
22628 cx: &mut App,
22629) -> Task<()> {
22630 let mut tasks = Vec::new();
22631 project.update(cx, |project, cx| {
22632 for buffer in buffers {
22633 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22634 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22635 }
22636 }
22637 });
22638 cx.spawn(async move |cx| {
22639 let diffs = future::join_all(tasks).await;
22640 if editor
22641 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22642 .unwrap_or(false)
22643 {
22644 return;
22645 }
22646
22647 buffer
22648 .update(cx, |buffer, cx| {
22649 for diff in diffs.into_iter().flatten() {
22650 buffer.add_diff(diff, cx);
22651 }
22652 })
22653 .ok();
22654 })
22655}
22656
22657fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22658 let tab_size = tab_size.get() as usize;
22659 let mut width = offset;
22660
22661 for ch in text.chars() {
22662 width += if ch == '\t' {
22663 tab_size - (width % tab_size)
22664 } else {
22665 1
22666 };
22667 }
22668
22669 width - offset
22670}
22671
22672#[cfg(test)]
22673mod tests {
22674 use super::*;
22675
22676 #[test]
22677 fn test_string_size_with_expanded_tabs() {
22678 let nz = |val| NonZeroU32::new(val).unwrap();
22679 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22680 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22681 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22682 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22683 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22684 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22685 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22686 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22687 }
22688}
22689
22690/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22691struct WordBreakingTokenizer<'a> {
22692 input: &'a str,
22693}
22694
22695impl<'a> WordBreakingTokenizer<'a> {
22696 fn new(input: &'a str) -> Self {
22697 Self { input }
22698 }
22699}
22700
22701fn is_char_ideographic(ch: char) -> bool {
22702 use unicode_script::Script::*;
22703 use unicode_script::UnicodeScript;
22704 matches!(ch.script(), Han | Tangut | Yi)
22705}
22706
22707fn is_grapheme_ideographic(text: &str) -> bool {
22708 text.chars().any(is_char_ideographic)
22709}
22710
22711fn is_grapheme_whitespace(text: &str) -> bool {
22712 text.chars().any(|x| x.is_whitespace())
22713}
22714
22715fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22716 text.chars()
22717 .next()
22718 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22719}
22720
22721#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22722enum WordBreakToken<'a> {
22723 Word { token: &'a str, grapheme_len: usize },
22724 InlineWhitespace { token: &'a str, grapheme_len: usize },
22725 Newline,
22726}
22727
22728impl<'a> Iterator for WordBreakingTokenizer<'a> {
22729 /// Yields a span, the count of graphemes in the token, and whether it was
22730 /// whitespace. Note that it also breaks at word boundaries.
22731 type Item = WordBreakToken<'a>;
22732
22733 fn next(&mut self) -> Option<Self::Item> {
22734 use unicode_segmentation::UnicodeSegmentation;
22735 if self.input.is_empty() {
22736 return None;
22737 }
22738
22739 let mut iter = self.input.graphemes(true).peekable();
22740 let mut offset = 0;
22741 let mut grapheme_len = 0;
22742 if let Some(first_grapheme) = iter.next() {
22743 let is_newline = first_grapheme == "\n";
22744 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22745 offset += first_grapheme.len();
22746 grapheme_len += 1;
22747 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22748 if let Some(grapheme) = iter.peek().copied()
22749 && should_stay_with_preceding_ideograph(grapheme)
22750 {
22751 offset += grapheme.len();
22752 grapheme_len += 1;
22753 }
22754 } else {
22755 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22756 let mut next_word_bound = words.peek().copied();
22757 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22758 next_word_bound = words.next();
22759 }
22760 while let Some(grapheme) = iter.peek().copied() {
22761 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22762 break;
22763 };
22764 if is_grapheme_whitespace(grapheme) != is_whitespace
22765 || (grapheme == "\n") != is_newline
22766 {
22767 break;
22768 };
22769 offset += grapheme.len();
22770 grapheme_len += 1;
22771 iter.next();
22772 }
22773 }
22774 let token = &self.input[..offset];
22775 self.input = &self.input[offset..];
22776 if token == "\n" {
22777 Some(WordBreakToken::Newline)
22778 } else if is_whitespace {
22779 Some(WordBreakToken::InlineWhitespace {
22780 token,
22781 grapheme_len,
22782 })
22783 } else {
22784 Some(WordBreakToken::Word {
22785 token,
22786 grapheme_len,
22787 })
22788 }
22789 } else {
22790 None
22791 }
22792 }
22793}
22794
22795#[test]
22796fn test_word_breaking_tokenizer() {
22797 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22798 ("", &[]),
22799 (" ", &[whitespace(" ", 2)]),
22800 ("Ʒ", &[word("Ʒ", 1)]),
22801 ("Ǽ", &[word("Ǽ", 1)]),
22802 ("⋑", &[word("⋑", 1)]),
22803 ("⋑⋑", &[word("⋑⋑", 2)]),
22804 (
22805 "原理,进而",
22806 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22807 ),
22808 (
22809 "hello world",
22810 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22811 ),
22812 (
22813 "hello, world",
22814 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22815 ),
22816 (
22817 " hello world",
22818 &[
22819 whitespace(" ", 2),
22820 word("hello", 5),
22821 whitespace(" ", 1),
22822 word("world", 5),
22823 ],
22824 ),
22825 (
22826 "这是什么 \n 钢笔",
22827 &[
22828 word("这", 1),
22829 word("是", 1),
22830 word("什", 1),
22831 word("么", 1),
22832 whitespace(" ", 1),
22833 newline(),
22834 whitespace(" ", 1),
22835 word("钢", 1),
22836 word("笔", 1),
22837 ],
22838 ),
22839 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22840 ];
22841
22842 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22843 WordBreakToken::Word {
22844 token,
22845 grapheme_len,
22846 }
22847 }
22848
22849 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22850 WordBreakToken::InlineWhitespace {
22851 token,
22852 grapheme_len,
22853 }
22854 }
22855
22856 fn newline() -> WordBreakToken<'static> {
22857 WordBreakToken::Newline
22858 }
22859
22860 for (input, result) in tests {
22861 assert_eq!(
22862 WordBreakingTokenizer::new(input)
22863 .collect::<Vec<_>>()
22864 .as_slice(),
22865 *result,
22866 );
22867 }
22868}
22869
22870fn wrap_with_prefix(
22871 first_line_prefix: String,
22872 subsequent_lines_prefix: String,
22873 unwrapped_text: String,
22874 wrap_column: usize,
22875 tab_size: NonZeroU32,
22876 preserve_existing_whitespace: bool,
22877) -> String {
22878 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22879 let subsequent_lines_prefix_len =
22880 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22881 let mut wrapped_text = String::new();
22882 let mut current_line = first_line_prefix;
22883 let mut is_first_line = true;
22884
22885 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22886 let mut current_line_len = first_line_prefix_len;
22887 let mut in_whitespace = false;
22888 for token in tokenizer {
22889 let have_preceding_whitespace = in_whitespace;
22890 match token {
22891 WordBreakToken::Word {
22892 token,
22893 grapheme_len,
22894 } => {
22895 in_whitespace = false;
22896 let current_prefix_len = if is_first_line {
22897 first_line_prefix_len
22898 } else {
22899 subsequent_lines_prefix_len
22900 };
22901 if current_line_len + grapheme_len > wrap_column
22902 && current_line_len != current_prefix_len
22903 {
22904 wrapped_text.push_str(current_line.trim_end());
22905 wrapped_text.push('\n');
22906 is_first_line = false;
22907 current_line = subsequent_lines_prefix.clone();
22908 current_line_len = subsequent_lines_prefix_len;
22909 }
22910 current_line.push_str(token);
22911 current_line_len += grapheme_len;
22912 }
22913 WordBreakToken::InlineWhitespace {
22914 mut token,
22915 mut grapheme_len,
22916 } => {
22917 in_whitespace = true;
22918 if have_preceding_whitespace && !preserve_existing_whitespace {
22919 continue;
22920 }
22921 if !preserve_existing_whitespace {
22922 // Keep a single whitespace grapheme as-is
22923 if let Some(first) =
22924 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22925 {
22926 token = first;
22927 } else {
22928 token = " ";
22929 }
22930 grapheme_len = 1;
22931 }
22932 let current_prefix_len = if is_first_line {
22933 first_line_prefix_len
22934 } else {
22935 subsequent_lines_prefix_len
22936 };
22937 if current_line_len + grapheme_len > wrap_column {
22938 wrapped_text.push_str(current_line.trim_end());
22939 wrapped_text.push('\n');
22940 is_first_line = false;
22941 current_line = subsequent_lines_prefix.clone();
22942 current_line_len = subsequent_lines_prefix_len;
22943 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22944 current_line.push_str(token);
22945 current_line_len += grapheme_len;
22946 }
22947 }
22948 WordBreakToken::Newline => {
22949 in_whitespace = true;
22950 let current_prefix_len = if is_first_line {
22951 first_line_prefix_len
22952 } else {
22953 subsequent_lines_prefix_len
22954 };
22955 if preserve_existing_whitespace {
22956 wrapped_text.push_str(current_line.trim_end());
22957 wrapped_text.push('\n');
22958 is_first_line = false;
22959 current_line = subsequent_lines_prefix.clone();
22960 current_line_len = subsequent_lines_prefix_len;
22961 } else if have_preceding_whitespace {
22962 continue;
22963 } else if current_line_len + 1 > wrap_column
22964 && current_line_len != current_prefix_len
22965 {
22966 wrapped_text.push_str(current_line.trim_end());
22967 wrapped_text.push('\n');
22968 is_first_line = false;
22969 current_line = subsequent_lines_prefix.clone();
22970 current_line_len = subsequent_lines_prefix_len;
22971 } else if current_line_len != current_prefix_len {
22972 current_line.push(' ');
22973 current_line_len += 1;
22974 }
22975 }
22976 }
22977 }
22978
22979 if !current_line.is_empty() {
22980 wrapped_text.push_str(¤t_line);
22981 }
22982 wrapped_text
22983}
22984
22985#[test]
22986fn test_wrap_with_prefix() {
22987 assert_eq!(
22988 wrap_with_prefix(
22989 "# ".to_string(),
22990 "# ".to_string(),
22991 "abcdefg".to_string(),
22992 4,
22993 NonZeroU32::new(4).unwrap(),
22994 false,
22995 ),
22996 "# abcdefg"
22997 );
22998 assert_eq!(
22999 wrap_with_prefix(
23000 "".to_string(),
23001 "".to_string(),
23002 "\thello world".to_string(),
23003 8,
23004 NonZeroU32::new(4).unwrap(),
23005 false,
23006 ),
23007 "hello\nworld"
23008 );
23009 assert_eq!(
23010 wrap_with_prefix(
23011 "// ".to_string(),
23012 "// ".to_string(),
23013 "xx \nyy zz aa bb cc".to_string(),
23014 12,
23015 NonZeroU32::new(4).unwrap(),
23016 false,
23017 ),
23018 "// xx yy zz\n// aa bb cc"
23019 );
23020 assert_eq!(
23021 wrap_with_prefix(
23022 String::new(),
23023 String::new(),
23024 "这是什么 \n 钢笔".to_string(),
23025 3,
23026 NonZeroU32::new(4).unwrap(),
23027 false,
23028 ),
23029 "这是什\n么 钢\n笔"
23030 );
23031 assert_eq!(
23032 wrap_with_prefix(
23033 String::new(),
23034 String::new(),
23035 format!("foo{}bar", '\u{2009}'), // thin space
23036 80,
23037 NonZeroU32::new(4).unwrap(),
23038 false,
23039 ),
23040 format!("foo{}bar", '\u{2009}')
23041 );
23042}
23043
23044pub trait CollaborationHub {
23045 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23046 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23047 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23048}
23049
23050impl CollaborationHub for Entity<Project> {
23051 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23052 self.read(cx).collaborators()
23053 }
23054
23055 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23056 self.read(cx).user_store().read(cx).participant_indices()
23057 }
23058
23059 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23060 let this = self.read(cx);
23061 let user_ids = this.collaborators().values().map(|c| c.user_id);
23062 this.user_store().read(cx).participant_names(user_ids, cx)
23063 }
23064}
23065
23066pub trait SemanticsProvider {
23067 fn hover(
23068 &self,
23069 buffer: &Entity<Buffer>,
23070 position: text::Anchor,
23071 cx: &mut App,
23072 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23073
23074 fn inline_values(
23075 &self,
23076 buffer_handle: Entity<Buffer>,
23077 range: Range<text::Anchor>,
23078 cx: &mut App,
23079 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23080
23081 fn applicable_inlay_chunks(
23082 &self,
23083 buffer: &Entity<Buffer>,
23084 ranges: &[Range<text::Anchor>],
23085 cx: &mut App,
23086 ) -> Vec<Range<BufferRow>>;
23087
23088 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23089
23090 fn inlay_hints(
23091 &self,
23092 invalidate: InvalidationStrategy,
23093 buffer: Entity<Buffer>,
23094 ranges: Vec<Range<text::Anchor>>,
23095 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23096 cx: &mut App,
23097 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23098
23099 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23100
23101 fn document_highlights(
23102 &self,
23103 buffer: &Entity<Buffer>,
23104 position: text::Anchor,
23105 cx: &mut App,
23106 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23107
23108 fn definitions(
23109 &self,
23110 buffer: &Entity<Buffer>,
23111 position: text::Anchor,
23112 kind: GotoDefinitionKind,
23113 cx: &mut App,
23114 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23115
23116 fn range_for_rename(
23117 &self,
23118 buffer: &Entity<Buffer>,
23119 position: text::Anchor,
23120 cx: &mut App,
23121 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23122
23123 fn perform_rename(
23124 &self,
23125 buffer: &Entity<Buffer>,
23126 position: text::Anchor,
23127 new_name: String,
23128 cx: &mut App,
23129 ) -> Option<Task<Result<ProjectTransaction>>>;
23130}
23131
23132pub trait CompletionProvider {
23133 fn completions(
23134 &self,
23135 excerpt_id: ExcerptId,
23136 buffer: &Entity<Buffer>,
23137 buffer_position: text::Anchor,
23138 trigger: CompletionContext,
23139 window: &mut Window,
23140 cx: &mut Context<Editor>,
23141 ) -> Task<Result<Vec<CompletionResponse>>>;
23142
23143 fn resolve_completions(
23144 &self,
23145 _buffer: Entity<Buffer>,
23146 _completion_indices: Vec<usize>,
23147 _completions: Rc<RefCell<Box<[Completion]>>>,
23148 _cx: &mut Context<Editor>,
23149 ) -> Task<Result<bool>> {
23150 Task::ready(Ok(false))
23151 }
23152
23153 fn apply_additional_edits_for_completion(
23154 &self,
23155 _buffer: Entity<Buffer>,
23156 _completions: Rc<RefCell<Box<[Completion]>>>,
23157 _completion_index: usize,
23158 _push_to_history: bool,
23159 _cx: &mut Context<Editor>,
23160 ) -> Task<Result<Option<language::Transaction>>> {
23161 Task::ready(Ok(None))
23162 }
23163
23164 fn is_completion_trigger(
23165 &self,
23166 buffer: &Entity<Buffer>,
23167 position: language::Anchor,
23168 text: &str,
23169 trigger_in_words: bool,
23170 menu_is_open: bool,
23171 cx: &mut Context<Editor>,
23172 ) -> bool;
23173
23174 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23175
23176 fn sort_completions(&self) -> bool {
23177 true
23178 }
23179
23180 fn filter_completions(&self) -> bool {
23181 true
23182 }
23183
23184 fn show_snippets(&self) -> bool {
23185 false
23186 }
23187}
23188
23189pub trait CodeActionProvider {
23190 fn id(&self) -> Arc<str>;
23191
23192 fn code_actions(
23193 &self,
23194 buffer: &Entity<Buffer>,
23195 range: Range<text::Anchor>,
23196 window: &mut Window,
23197 cx: &mut App,
23198 ) -> Task<Result<Vec<CodeAction>>>;
23199
23200 fn apply_code_action(
23201 &self,
23202 buffer_handle: Entity<Buffer>,
23203 action: CodeAction,
23204 excerpt_id: ExcerptId,
23205 push_to_history: bool,
23206 window: &mut Window,
23207 cx: &mut App,
23208 ) -> Task<Result<ProjectTransaction>>;
23209}
23210
23211impl CodeActionProvider for Entity<Project> {
23212 fn id(&self) -> Arc<str> {
23213 "project".into()
23214 }
23215
23216 fn code_actions(
23217 &self,
23218 buffer: &Entity<Buffer>,
23219 range: Range<text::Anchor>,
23220 _window: &mut Window,
23221 cx: &mut App,
23222 ) -> Task<Result<Vec<CodeAction>>> {
23223 self.update(cx, |project, cx| {
23224 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23225 let code_actions = project.code_actions(buffer, range, None, cx);
23226 cx.background_spawn(async move {
23227 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23228 Ok(code_lens_actions
23229 .context("code lens fetch")?
23230 .into_iter()
23231 .flatten()
23232 .chain(
23233 code_actions
23234 .context("code action fetch")?
23235 .into_iter()
23236 .flatten(),
23237 )
23238 .collect())
23239 })
23240 })
23241 }
23242
23243 fn apply_code_action(
23244 &self,
23245 buffer_handle: Entity<Buffer>,
23246 action: CodeAction,
23247 _excerpt_id: ExcerptId,
23248 push_to_history: bool,
23249 _window: &mut Window,
23250 cx: &mut App,
23251 ) -> Task<Result<ProjectTransaction>> {
23252 self.update(cx, |project, cx| {
23253 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23254 })
23255 }
23256}
23257
23258fn snippet_completions(
23259 project: &Project,
23260 buffer: &Entity<Buffer>,
23261 buffer_anchor: text::Anchor,
23262 classifier: CharClassifier,
23263 cx: &mut App,
23264) -> Task<Result<CompletionResponse>> {
23265 let languages = buffer.read(cx).languages_at(buffer_anchor);
23266 let snippet_store = project.snippets().read(cx);
23267
23268 let scopes: Vec<_> = languages
23269 .iter()
23270 .filter_map(|language| {
23271 let language_name = language.lsp_id();
23272 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23273
23274 if snippets.is_empty() {
23275 None
23276 } else {
23277 Some((language.default_scope(), snippets))
23278 }
23279 })
23280 .collect();
23281
23282 if scopes.is_empty() {
23283 return Task::ready(Ok(CompletionResponse {
23284 completions: vec![],
23285 display_options: CompletionDisplayOptions::default(),
23286 is_incomplete: false,
23287 }));
23288 }
23289
23290 let snapshot = buffer.read(cx).text_snapshot();
23291 let executor = cx.background_executor().clone();
23292
23293 cx.background_spawn(async move {
23294 let is_word_char = |c| classifier.is_word(c);
23295
23296 let mut is_incomplete = false;
23297 let mut completions: Vec<Completion> = Vec::new();
23298
23299 const MAX_PREFIX_LEN: usize = 128;
23300 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
23301 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
23302 let window_start = snapshot.clip_offset(window_start, Bias::Left);
23303
23304 let max_buffer_window: String = snapshot
23305 .text_for_range(window_start..buffer_offset)
23306 .collect();
23307
23308 if max_buffer_window.is_empty() {
23309 return Ok(CompletionResponse {
23310 completions: vec![],
23311 display_options: CompletionDisplayOptions::default(),
23312 is_incomplete: true,
23313 });
23314 }
23315
23316 for (_scope, snippets) in scopes.into_iter() {
23317 // Sort snippets by word count to match longer snippet prefixes first.
23318 let mut sorted_snippet_candidates = snippets
23319 .iter()
23320 .enumerate()
23321 .flat_map(|(snippet_ix, snippet)| {
23322 snippet
23323 .prefix
23324 .iter()
23325 .enumerate()
23326 .map(move |(prefix_ix, prefix)| {
23327 let word_count =
23328 snippet_candidate_suffixes(prefix, is_word_char).count();
23329 ((snippet_ix, prefix_ix), prefix, word_count)
23330 })
23331 })
23332 .collect_vec();
23333 sorted_snippet_candidates
23334 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
23335
23336 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
23337
23338 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
23339 .take(
23340 sorted_snippet_candidates
23341 .first()
23342 .map(|(_, _, word_count)| *word_count)
23343 .unwrap_or_default(),
23344 )
23345 .collect_vec();
23346
23347 const MAX_RESULTS: usize = 100;
23348 // Each match also remembers how many characters from the buffer it consumed
23349 let mut matches: Vec<(StringMatch, usize)> = vec![];
23350
23351 let mut snippet_list_cutoff_index = 0;
23352 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
23353 let word_count = buffer_index + 1;
23354 // Increase `snippet_list_cutoff_index` until we have all of the
23355 // snippets with sufficiently many words.
23356 while sorted_snippet_candidates
23357 .get(snippet_list_cutoff_index)
23358 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
23359 *snippet_word_count >= word_count
23360 })
23361 {
23362 snippet_list_cutoff_index += 1;
23363 }
23364
23365 // Take only the candidates with at least `word_count` many words
23366 let snippet_candidates_at_word_len =
23367 &sorted_snippet_candidates[..snippet_list_cutoff_index];
23368
23369 let candidates = snippet_candidates_at_word_len
23370 .iter()
23371 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
23372 .enumerate() // index in `sorted_snippet_candidates`
23373 // First char must match
23374 .filter(|(_ix, prefix)| {
23375 itertools::equal(
23376 prefix
23377 .chars()
23378 .next()
23379 .into_iter()
23380 .flat_map(|c| c.to_lowercase()),
23381 buffer_window
23382 .chars()
23383 .next()
23384 .into_iter()
23385 .flat_map(|c| c.to_lowercase()),
23386 )
23387 })
23388 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
23389 .collect::<Vec<StringMatchCandidate>>();
23390
23391 matches.extend(
23392 fuzzy::match_strings(
23393 &candidates,
23394 &buffer_window,
23395 buffer_window.chars().any(|c| c.is_uppercase()),
23396 true,
23397 MAX_RESULTS - matches.len(), // always prioritize longer snippets
23398 &Default::default(),
23399 executor.clone(),
23400 )
23401 .await
23402 .into_iter()
23403 .map(|string_match| (string_match, buffer_window.len())),
23404 );
23405
23406 if matches.len() >= MAX_RESULTS {
23407 break;
23408 }
23409 }
23410
23411 let to_lsp = |point: &text::Anchor| {
23412 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23413 point_to_lsp(end)
23414 };
23415 let lsp_end = to_lsp(&buffer_anchor);
23416
23417 if matches.len() >= MAX_RESULTS {
23418 is_incomplete = true;
23419 }
23420
23421 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
23422 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
23423 sorted_snippet_candidates[string_match.candidate_id];
23424 let snippet = &snippets[snippet_index];
23425 let start = buffer_offset - buffer_window_len;
23426 let start = snapshot.anchor_before(start);
23427 let range = start..buffer_anchor;
23428 let lsp_start = to_lsp(&start);
23429 let lsp_range = lsp::Range {
23430 start: lsp_start,
23431 end: lsp_end,
23432 };
23433 Completion {
23434 replace_range: range,
23435 new_text: snippet.body.clone(),
23436 source: CompletionSource::Lsp {
23437 insert_range: None,
23438 server_id: LanguageServerId(usize::MAX),
23439 resolved: true,
23440 lsp_completion: Box::new(lsp::CompletionItem {
23441 label: snippet.prefix.first().unwrap().clone(),
23442 kind: Some(CompletionItemKind::SNIPPET),
23443 label_details: snippet.description.as_ref().map(|description| {
23444 lsp::CompletionItemLabelDetails {
23445 detail: Some(description.clone()),
23446 description: None,
23447 }
23448 }),
23449 insert_text_format: Some(InsertTextFormat::SNIPPET),
23450 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23451 lsp::InsertReplaceEdit {
23452 new_text: snippet.body.clone(),
23453 insert: lsp_range,
23454 replace: lsp_range,
23455 },
23456 )),
23457 filter_text: Some(snippet.body.clone()),
23458 sort_text: Some(char::MAX.to_string()),
23459 ..lsp::CompletionItem::default()
23460 }),
23461 lsp_defaults: None,
23462 },
23463 label: CodeLabel {
23464 text: matching_prefix.clone(),
23465 runs: Vec::new(),
23466 filter_range: 0..matching_prefix.len(),
23467 },
23468 icon_path: None,
23469 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23470 single_line: snippet.name.clone().into(),
23471 plain_text: snippet
23472 .description
23473 .clone()
23474 .map(|description| description.into()),
23475 }),
23476 insert_text_mode: None,
23477 confirm: None,
23478 match_start: Some(start),
23479 snippet_deduplication_key: Some((snippet_index, prefix_index)),
23480 }
23481 }));
23482 }
23483
23484 Ok(CompletionResponse {
23485 completions,
23486 display_options: CompletionDisplayOptions::default(),
23487 is_incomplete,
23488 })
23489 })
23490}
23491
23492impl CompletionProvider for Entity<Project> {
23493 fn completions(
23494 &self,
23495 _excerpt_id: ExcerptId,
23496 buffer: &Entity<Buffer>,
23497 buffer_position: text::Anchor,
23498 options: CompletionContext,
23499 _window: &mut Window,
23500 cx: &mut Context<Editor>,
23501 ) -> Task<Result<Vec<CompletionResponse>>> {
23502 self.update(cx, |project, cx| {
23503 let task = project.completions(buffer, buffer_position, options, cx);
23504 cx.background_spawn(task)
23505 })
23506 }
23507
23508 fn resolve_completions(
23509 &self,
23510 buffer: Entity<Buffer>,
23511 completion_indices: Vec<usize>,
23512 completions: Rc<RefCell<Box<[Completion]>>>,
23513 cx: &mut Context<Editor>,
23514 ) -> Task<Result<bool>> {
23515 self.update(cx, |project, cx| {
23516 project.lsp_store().update(cx, |lsp_store, cx| {
23517 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23518 })
23519 })
23520 }
23521
23522 fn apply_additional_edits_for_completion(
23523 &self,
23524 buffer: Entity<Buffer>,
23525 completions: Rc<RefCell<Box<[Completion]>>>,
23526 completion_index: usize,
23527 push_to_history: bool,
23528 cx: &mut Context<Editor>,
23529 ) -> Task<Result<Option<language::Transaction>>> {
23530 self.update(cx, |project, cx| {
23531 project.lsp_store().update(cx, |lsp_store, cx| {
23532 lsp_store.apply_additional_edits_for_completion(
23533 buffer,
23534 completions,
23535 completion_index,
23536 push_to_history,
23537 cx,
23538 )
23539 })
23540 })
23541 }
23542
23543 fn is_completion_trigger(
23544 &self,
23545 buffer: &Entity<Buffer>,
23546 position: language::Anchor,
23547 text: &str,
23548 trigger_in_words: bool,
23549 menu_is_open: bool,
23550 cx: &mut Context<Editor>,
23551 ) -> bool {
23552 let mut chars = text.chars();
23553 let char = if let Some(char) = chars.next() {
23554 char
23555 } else {
23556 return false;
23557 };
23558 if chars.next().is_some() {
23559 return false;
23560 }
23561
23562 let buffer = buffer.read(cx);
23563 let snapshot = buffer.snapshot();
23564 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23565 return false;
23566 }
23567 let classifier = snapshot
23568 .char_classifier_at(position)
23569 .scope_context(Some(CharScopeContext::Completion));
23570 if trigger_in_words && classifier.is_word(char) {
23571 return true;
23572 }
23573
23574 buffer.completion_triggers().contains(text)
23575 }
23576
23577 fn show_snippets(&self) -> bool {
23578 true
23579 }
23580}
23581
23582impl SemanticsProvider for Entity<Project> {
23583 fn hover(
23584 &self,
23585 buffer: &Entity<Buffer>,
23586 position: text::Anchor,
23587 cx: &mut App,
23588 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23589 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23590 }
23591
23592 fn document_highlights(
23593 &self,
23594 buffer: &Entity<Buffer>,
23595 position: text::Anchor,
23596 cx: &mut App,
23597 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23598 Some(self.update(cx, |project, cx| {
23599 project.document_highlights(buffer, position, cx)
23600 }))
23601 }
23602
23603 fn definitions(
23604 &self,
23605 buffer: &Entity<Buffer>,
23606 position: text::Anchor,
23607 kind: GotoDefinitionKind,
23608 cx: &mut App,
23609 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23610 Some(self.update(cx, |project, cx| match kind {
23611 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23612 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23613 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23614 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23615 }))
23616 }
23617
23618 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23619 self.update(cx, |project, cx| {
23620 if project
23621 .active_debug_session(cx)
23622 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23623 {
23624 return true;
23625 }
23626
23627 buffer.update(cx, |buffer, cx| {
23628 project.any_language_server_supports_inlay_hints(buffer, cx)
23629 })
23630 })
23631 }
23632
23633 fn inline_values(
23634 &self,
23635 buffer_handle: Entity<Buffer>,
23636 range: Range<text::Anchor>,
23637 cx: &mut App,
23638 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23639 self.update(cx, |project, cx| {
23640 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23641
23642 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23643 })
23644 }
23645
23646 fn applicable_inlay_chunks(
23647 &self,
23648 buffer: &Entity<Buffer>,
23649 ranges: &[Range<text::Anchor>],
23650 cx: &mut App,
23651 ) -> Vec<Range<BufferRow>> {
23652 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23653 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23654 })
23655 }
23656
23657 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23658 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23659 lsp_store.invalidate_inlay_hints(for_buffers)
23660 });
23661 }
23662
23663 fn inlay_hints(
23664 &self,
23665 invalidate: InvalidationStrategy,
23666 buffer: Entity<Buffer>,
23667 ranges: Vec<Range<text::Anchor>>,
23668 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23669 cx: &mut App,
23670 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23671 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23672 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23673 }))
23674 }
23675
23676 fn range_for_rename(
23677 &self,
23678 buffer: &Entity<Buffer>,
23679 position: text::Anchor,
23680 cx: &mut App,
23681 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23682 Some(self.update(cx, |project, cx| {
23683 let buffer = buffer.clone();
23684 let task = project.prepare_rename(buffer.clone(), position, cx);
23685 cx.spawn(async move |_, cx| {
23686 Ok(match task.await? {
23687 PrepareRenameResponse::Success(range) => Some(range),
23688 PrepareRenameResponse::InvalidPosition => None,
23689 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23690 // Fallback on using TreeSitter info to determine identifier range
23691 buffer.read_with(cx, |buffer, _| {
23692 let snapshot = buffer.snapshot();
23693 let (range, kind) = snapshot.surrounding_word(position, None);
23694 if kind != Some(CharKind::Word) {
23695 return None;
23696 }
23697 Some(
23698 snapshot.anchor_before(range.start)
23699 ..snapshot.anchor_after(range.end),
23700 )
23701 })?
23702 }
23703 })
23704 })
23705 }))
23706 }
23707
23708 fn perform_rename(
23709 &self,
23710 buffer: &Entity<Buffer>,
23711 position: text::Anchor,
23712 new_name: String,
23713 cx: &mut App,
23714 ) -> Option<Task<Result<ProjectTransaction>>> {
23715 Some(self.update(cx, |project, cx| {
23716 project.perform_rename(buffer.clone(), position, new_name, cx)
23717 }))
23718 }
23719}
23720
23721fn consume_contiguous_rows(
23722 contiguous_row_selections: &mut Vec<Selection<Point>>,
23723 selection: &Selection<Point>,
23724 display_map: &DisplaySnapshot,
23725 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23726) -> (MultiBufferRow, MultiBufferRow) {
23727 contiguous_row_selections.push(selection.clone());
23728 let start_row = starting_row(selection, display_map);
23729 let mut end_row = ending_row(selection, display_map);
23730
23731 while let Some(next_selection) = selections.peek() {
23732 if next_selection.start.row <= end_row.0 {
23733 end_row = ending_row(next_selection, display_map);
23734 contiguous_row_selections.push(selections.next().unwrap().clone());
23735 } else {
23736 break;
23737 }
23738 }
23739 (start_row, end_row)
23740}
23741
23742fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23743 if selection.start.column > 0 {
23744 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23745 } else {
23746 MultiBufferRow(selection.start.row)
23747 }
23748}
23749
23750fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23751 if next_selection.end.column > 0 || next_selection.is_empty() {
23752 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23753 } else {
23754 MultiBufferRow(next_selection.end.row)
23755 }
23756}
23757
23758impl EditorSnapshot {
23759 pub fn remote_selections_in_range<'a>(
23760 &'a self,
23761 range: &'a Range<Anchor>,
23762 collaboration_hub: &dyn CollaborationHub,
23763 cx: &'a App,
23764 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23765 let participant_names = collaboration_hub.user_names(cx);
23766 let participant_indices = collaboration_hub.user_participant_indices(cx);
23767 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23768 let collaborators_by_replica_id = collaborators_by_peer_id
23769 .values()
23770 .map(|collaborator| (collaborator.replica_id, collaborator))
23771 .collect::<HashMap<_, _>>();
23772 self.buffer_snapshot()
23773 .selections_in_range(range, false)
23774 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23775 if replica_id == ReplicaId::AGENT {
23776 Some(RemoteSelection {
23777 replica_id,
23778 selection,
23779 cursor_shape,
23780 line_mode,
23781 collaborator_id: CollaboratorId::Agent,
23782 user_name: Some("Agent".into()),
23783 color: cx.theme().players().agent(),
23784 })
23785 } else {
23786 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23787 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23788 let user_name = participant_names.get(&collaborator.user_id).cloned();
23789 Some(RemoteSelection {
23790 replica_id,
23791 selection,
23792 cursor_shape,
23793 line_mode,
23794 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23795 user_name,
23796 color: if let Some(index) = participant_index {
23797 cx.theme().players().color_for_participant(index.0)
23798 } else {
23799 cx.theme().players().absent()
23800 },
23801 })
23802 }
23803 })
23804 }
23805
23806 pub fn hunks_for_ranges(
23807 &self,
23808 ranges: impl IntoIterator<Item = Range<Point>>,
23809 ) -> Vec<MultiBufferDiffHunk> {
23810 let mut hunks = Vec::new();
23811 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23812 HashMap::default();
23813 for query_range in ranges {
23814 let query_rows =
23815 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23816 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23817 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23818 ) {
23819 // Include deleted hunks that are adjacent to the query range, because
23820 // otherwise they would be missed.
23821 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23822 if hunk.status().is_deleted() {
23823 intersects_range |= hunk.row_range.start == query_rows.end;
23824 intersects_range |= hunk.row_range.end == query_rows.start;
23825 }
23826 if intersects_range {
23827 if !processed_buffer_rows
23828 .entry(hunk.buffer_id)
23829 .or_default()
23830 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23831 {
23832 continue;
23833 }
23834 hunks.push(hunk);
23835 }
23836 }
23837 }
23838
23839 hunks
23840 }
23841
23842 fn display_diff_hunks_for_rows<'a>(
23843 &'a self,
23844 display_rows: Range<DisplayRow>,
23845 folded_buffers: &'a HashSet<BufferId>,
23846 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23847 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23848 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23849
23850 self.buffer_snapshot()
23851 .diff_hunks_in_range(buffer_start..buffer_end)
23852 .filter_map(|hunk| {
23853 if folded_buffers.contains(&hunk.buffer_id) {
23854 return None;
23855 }
23856
23857 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23858 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23859
23860 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23861 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23862
23863 let display_hunk = if hunk_display_start.column() != 0 {
23864 DisplayDiffHunk::Folded {
23865 display_row: hunk_display_start.row(),
23866 }
23867 } else {
23868 let mut end_row = hunk_display_end.row();
23869 if hunk_display_end.column() > 0 {
23870 end_row.0 += 1;
23871 }
23872 let is_created_file = hunk.is_created_file();
23873 DisplayDiffHunk::Unfolded {
23874 status: hunk.status(),
23875 diff_base_byte_range: hunk.diff_base_byte_range,
23876 display_row_range: hunk_display_start.row()..end_row,
23877 multi_buffer_range: Anchor::range_in_buffer(
23878 hunk.excerpt_id,
23879 hunk.buffer_id,
23880 hunk.buffer_range,
23881 ),
23882 is_created_file,
23883 }
23884 };
23885
23886 Some(display_hunk)
23887 })
23888 }
23889
23890 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23891 self.display_snapshot
23892 .buffer_snapshot()
23893 .language_at(position)
23894 }
23895
23896 pub fn is_focused(&self) -> bool {
23897 self.is_focused
23898 }
23899
23900 pub fn placeholder_text(&self) -> Option<String> {
23901 self.placeholder_display_snapshot
23902 .as_ref()
23903 .map(|display_map| display_map.text())
23904 }
23905
23906 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23907 self.scroll_anchor.scroll_position(&self.display_snapshot)
23908 }
23909
23910 fn gutter_dimensions(
23911 &self,
23912 font_id: FontId,
23913 font_size: Pixels,
23914 max_line_number_width: Pixels,
23915 cx: &App,
23916 ) -> Option<GutterDimensions> {
23917 if !self.show_gutter {
23918 return None;
23919 }
23920
23921 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23922 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23923
23924 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23925 matches!(
23926 ProjectSettings::get_global(cx).git.git_gutter,
23927 GitGutterSetting::TrackedFiles
23928 )
23929 });
23930 let gutter_settings = EditorSettings::get_global(cx).gutter;
23931 let show_line_numbers = self
23932 .show_line_numbers
23933 .unwrap_or(gutter_settings.line_numbers);
23934 let line_gutter_width = if show_line_numbers {
23935 // Avoid flicker-like gutter resizes when the line number gains another digit by
23936 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23937 let min_width_for_number_on_gutter =
23938 ch_advance * gutter_settings.min_line_number_digits as f32;
23939 max_line_number_width.max(min_width_for_number_on_gutter)
23940 } else {
23941 0.0.into()
23942 };
23943
23944 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23945 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23946
23947 let git_blame_entries_width =
23948 self.git_blame_gutter_max_author_length
23949 .map(|max_author_length| {
23950 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23951 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23952
23953 /// The number of characters to dedicate to gaps and margins.
23954 const SPACING_WIDTH: usize = 4;
23955
23956 let max_char_count = max_author_length.min(renderer.max_author_length())
23957 + ::git::SHORT_SHA_LENGTH
23958 + MAX_RELATIVE_TIMESTAMP.len()
23959 + SPACING_WIDTH;
23960
23961 ch_advance * max_char_count
23962 });
23963
23964 let is_singleton = self.buffer_snapshot().is_singleton();
23965
23966 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23967 left_padding += if !is_singleton {
23968 ch_width * 4.0
23969 } else if show_runnables || show_breakpoints {
23970 ch_width * 3.0
23971 } else if show_git_gutter && show_line_numbers {
23972 ch_width * 2.0
23973 } else if show_git_gutter || show_line_numbers {
23974 ch_width
23975 } else {
23976 px(0.)
23977 };
23978
23979 let shows_folds = is_singleton && gutter_settings.folds;
23980
23981 let right_padding = if shows_folds && show_line_numbers {
23982 ch_width * 4.0
23983 } else if shows_folds || (!is_singleton && show_line_numbers) {
23984 ch_width * 3.0
23985 } else if show_line_numbers {
23986 ch_width
23987 } else {
23988 px(0.)
23989 };
23990
23991 Some(GutterDimensions {
23992 left_padding,
23993 right_padding,
23994 width: line_gutter_width + left_padding + right_padding,
23995 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23996 git_blame_entries_width,
23997 })
23998 }
23999
24000 pub fn render_crease_toggle(
24001 &self,
24002 buffer_row: MultiBufferRow,
24003 row_contains_cursor: bool,
24004 editor: Entity<Editor>,
24005 window: &mut Window,
24006 cx: &mut App,
24007 ) -> Option<AnyElement> {
24008 let folded = self.is_line_folded(buffer_row);
24009 let mut is_foldable = false;
24010
24011 if let Some(crease) = self
24012 .crease_snapshot
24013 .query_row(buffer_row, self.buffer_snapshot())
24014 {
24015 is_foldable = true;
24016 match crease {
24017 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24018 if let Some(render_toggle) = render_toggle {
24019 let toggle_callback =
24020 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24021 if folded {
24022 editor.update(cx, |editor, cx| {
24023 editor.fold_at(buffer_row, window, cx)
24024 });
24025 } else {
24026 editor.update(cx, |editor, cx| {
24027 editor.unfold_at(buffer_row, window, cx)
24028 });
24029 }
24030 });
24031 return Some((render_toggle)(
24032 buffer_row,
24033 folded,
24034 toggle_callback,
24035 window,
24036 cx,
24037 ));
24038 }
24039 }
24040 }
24041 }
24042
24043 is_foldable |= self.starts_indent(buffer_row);
24044
24045 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24046 Some(
24047 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24048 .toggle_state(folded)
24049 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24050 if folded {
24051 this.unfold_at(buffer_row, window, cx);
24052 } else {
24053 this.fold_at(buffer_row, window, cx);
24054 }
24055 }))
24056 .into_any_element(),
24057 )
24058 } else {
24059 None
24060 }
24061 }
24062
24063 pub fn render_crease_trailer(
24064 &self,
24065 buffer_row: MultiBufferRow,
24066 window: &mut Window,
24067 cx: &mut App,
24068 ) -> Option<AnyElement> {
24069 let folded = self.is_line_folded(buffer_row);
24070 if let Crease::Inline { render_trailer, .. } = self
24071 .crease_snapshot
24072 .query_row(buffer_row, self.buffer_snapshot())?
24073 {
24074 let render_trailer = render_trailer.as_ref()?;
24075 Some(render_trailer(buffer_row, folded, window, cx))
24076 } else {
24077 None
24078 }
24079 }
24080}
24081
24082impl Deref for EditorSnapshot {
24083 type Target = DisplaySnapshot;
24084
24085 fn deref(&self) -> &Self::Target {
24086 &self.display_snapshot
24087 }
24088}
24089
24090#[derive(Clone, Debug, PartialEq, Eq)]
24091pub enum EditorEvent {
24092 InputIgnored {
24093 text: Arc<str>,
24094 },
24095 InputHandled {
24096 utf16_range_to_replace: Option<Range<isize>>,
24097 text: Arc<str>,
24098 },
24099 ExcerptsAdded {
24100 buffer: Entity<Buffer>,
24101 predecessor: ExcerptId,
24102 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
24103 },
24104 ExcerptsRemoved {
24105 ids: Vec<ExcerptId>,
24106 removed_buffer_ids: Vec<BufferId>,
24107 },
24108 BufferFoldToggled {
24109 ids: Vec<ExcerptId>,
24110 folded: bool,
24111 },
24112 ExcerptsEdited {
24113 ids: Vec<ExcerptId>,
24114 },
24115 ExcerptsExpanded {
24116 ids: Vec<ExcerptId>,
24117 },
24118 BufferEdited,
24119 Edited {
24120 transaction_id: clock::Lamport,
24121 },
24122 Reparsed(BufferId),
24123 Focused,
24124 FocusedIn,
24125 Blurred,
24126 DirtyChanged,
24127 Saved,
24128 TitleChanged,
24129 SelectionsChanged {
24130 local: bool,
24131 },
24132 ScrollPositionChanged {
24133 local: bool,
24134 autoscroll: bool,
24135 },
24136 TransactionUndone {
24137 transaction_id: clock::Lamport,
24138 },
24139 TransactionBegun {
24140 transaction_id: clock::Lamport,
24141 },
24142 CursorShapeChanged,
24143 BreadcrumbsChanged,
24144 PushedToNavHistory {
24145 anchor: Anchor,
24146 is_deactivate: bool,
24147 },
24148}
24149
24150impl EventEmitter<EditorEvent> for Editor {}
24151
24152impl Focusable for Editor {
24153 fn focus_handle(&self, _cx: &App) -> FocusHandle {
24154 self.focus_handle.clone()
24155 }
24156}
24157
24158impl Render for Editor {
24159 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24160 let settings = ThemeSettings::get_global(cx);
24161
24162 let mut text_style = match self.mode {
24163 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24164 color: cx.theme().colors().editor_foreground,
24165 font_family: settings.ui_font.family.clone(),
24166 font_features: settings.ui_font.features.clone(),
24167 font_fallbacks: settings.ui_font.fallbacks.clone(),
24168 font_size: rems(0.875).into(),
24169 font_weight: settings.ui_font.weight,
24170 line_height: relative(settings.buffer_line_height.value()),
24171 ..Default::default()
24172 },
24173 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24174 color: cx.theme().colors().editor_foreground,
24175 font_family: settings.buffer_font.family.clone(),
24176 font_features: settings.buffer_font.features.clone(),
24177 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24178 font_size: settings.buffer_font_size(cx).into(),
24179 font_weight: settings.buffer_font.weight,
24180 line_height: relative(settings.buffer_line_height.value()),
24181 ..Default::default()
24182 },
24183 };
24184 if let Some(text_style_refinement) = &self.text_style_refinement {
24185 text_style.refine(text_style_refinement)
24186 }
24187
24188 let background = match self.mode {
24189 EditorMode::SingleLine => cx.theme().system().transparent,
24190 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24191 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24192 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24193 };
24194
24195 EditorElement::new(
24196 &cx.entity(),
24197 EditorStyle {
24198 background,
24199 border: cx.theme().colors().border,
24200 local_player: cx.theme().players().local(),
24201 text: text_style,
24202 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24203 syntax: cx.theme().syntax().clone(),
24204 status: cx.theme().status().clone(),
24205 inlay_hints_style: make_inlay_hints_style(cx),
24206 edit_prediction_styles: make_suggestion_styles(cx),
24207 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
24208 show_underlines: self.diagnostics_enabled(),
24209 },
24210 )
24211 }
24212}
24213
24214impl EntityInputHandler for Editor {
24215 fn text_for_range(
24216 &mut self,
24217 range_utf16: Range<usize>,
24218 adjusted_range: &mut Option<Range<usize>>,
24219 _: &mut Window,
24220 cx: &mut Context<Self>,
24221 ) -> Option<String> {
24222 let snapshot = self.buffer.read(cx).read(cx);
24223 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
24224 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
24225 if (start.0..end.0) != range_utf16 {
24226 adjusted_range.replace(start.0..end.0);
24227 }
24228 Some(snapshot.text_for_range(start..end).collect())
24229 }
24230
24231 fn selected_text_range(
24232 &mut self,
24233 ignore_disabled_input: bool,
24234 _: &mut Window,
24235 cx: &mut Context<Self>,
24236 ) -> Option<UTF16Selection> {
24237 // Prevent the IME menu from appearing when holding down an alphabetic key
24238 // while input is disabled.
24239 if !ignore_disabled_input && !self.input_enabled {
24240 return None;
24241 }
24242
24243 let selection = self
24244 .selections
24245 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
24246 let range = selection.range();
24247
24248 Some(UTF16Selection {
24249 range: range.start.0..range.end.0,
24250 reversed: selection.reversed,
24251 })
24252 }
24253
24254 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24255 let snapshot = self.buffer.read(cx).read(cx);
24256 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24257 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
24258 }
24259
24260 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24261 self.clear_highlights::<InputComposition>(cx);
24262 self.ime_transaction.take();
24263 }
24264
24265 fn replace_text_in_range(
24266 &mut self,
24267 range_utf16: Option<Range<usize>>,
24268 text: &str,
24269 window: &mut Window,
24270 cx: &mut Context<Self>,
24271 ) {
24272 if !self.input_enabled {
24273 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24274 return;
24275 }
24276
24277 self.transact(window, cx, |this, window, cx| {
24278 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24279 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24280 Some(this.selection_replacement_ranges(range_utf16, cx))
24281 } else {
24282 this.marked_text_ranges(cx)
24283 };
24284
24285 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24286 let newest_selection_id = this.selections.newest_anchor().id;
24287 this.selections
24288 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24289 .iter()
24290 .zip(ranges_to_replace.iter())
24291 .find_map(|(selection, range)| {
24292 if selection.id == newest_selection_id {
24293 Some(
24294 (range.start.0 as isize - selection.head().0 as isize)
24295 ..(range.end.0 as isize - selection.head().0 as isize),
24296 )
24297 } else {
24298 None
24299 }
24300 })
24301 });
24302
24303 cx.emit(EditorEvent::InputHandled {
24304 utf16_range_to_replace: range_to_replace,
24305 text: text.into(),
24306 });
24307
24308 if let Some(new_selected_ranges) = new_selected_ranges {
24309 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24310 selections.select_ranges(new_selected_ranges)
24311 });
24312 this.backspace(&Default::default(), window, cx);
24313 }
24314
24315 this.handle_input(text, window, cx);
24316 });
24317
24318 if let Some(transaction) = self.ime_transaction {
24319 self.buffer.update(cx, |buffer, cx| {
24320 buffer.group_until_transaction(transaction, cx);
24321 });
24322 }
24323
24324 self.unmark_text(window, cx);
24325 }
24326
24327 fn replace_and_mark_text_in_range(
24328 &mut self,
24329 range_utf16: Option<Range<usize>>,
24330 text: &str,
24331 new_selected_range_utf16: Option<Range<usize>>,
24332 window: &mut Window,
24333 cx: &mut Context<Self>,
24334 ) {
24335 if !self.input_enabled {
24336 return;
24337 }
24338
24339 let transaction = self.transact(window, cx, |this, window, cx| {
24340 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24341 let snapshot = this.buffer.read(cx).read(cx);
24342 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24343 for marked_range in &mut marked_ranges {
24344 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24345 marked_range.start.0 += relative_range_utf16.start;
24346 marked_range.start =
24347 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24348 marked_range.end =
24349 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24350 }
24351 }
24352 Some(marked_ranges)
24353 } else if let Some(range_utf16) = range_utf16 {
24354 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24355 Some(this.selection_replacement_ranges(range_utf16, cx))
24356 } else {
24357 None
24358 };
24359
24360 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24361 let newest_selection_id = this.selections.newest_anchor().id;
24362 this.selections
24363 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24364 .iter()
24365 .zip(ranges_to_replace.iter())
24366 .find_map(|(selection, range)| {
24367 if selection.id == newest_selection_id {
24368 Some(
24369 (range.start.0 as isize - selection.head().0 as isize)
24370 ..(range.end.0 as isize - selection.head().0 as isize),
24371 )
24372 } else {
24373 None
24374 }
24375 })
24376 });
24377
24378 cx.emit(EditorEvent::InputHandled {
24379 utf16_range_to_replace: range_to_replace,
24380 text: text.into(),
24381 });
24382
24383 if let Some(ranges) = ranges_to_replace {
24384 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24385 s.select_ranges(ranges)
24386 });
24387 }
24388
24389 let marked_ranges = {
24390 let snapshot = this.buffer.read(cx).read(cx);
24391 this.selections
24392 .disjoint_anchors_arc()
24393 .iter()
24394 .map(|selection| {
24395 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24396 })
24397 .collect::<Vec<_>>()
24398 };
24399
24400 if text.is_empty() {
24401 this.unmark_text(window, cx);
24402 } else {
24403 this.highlight_text::<InputComposition>(
24404 marked_ranges.clone(),
24405 HighlightStyle {
24406 underline: Some(UnderlineStyle {
24407 thickness: px(1.),
24408 color: None,
24409 wavy: false,
24410 }),
24411 ..Default::default()
24412 },
24413 cx,
24414 );
24415 }
24416
24417 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24418 let use_autoclose = this.use_autoclose;
24419 let use_auto_surround = this.use_auto_surround;
24420 this.set_use_autoclose(false);
24421 this.set_use_auto_surround(false);
24422 this.handle_input(text, window, cx);
24423 this.set_use_autoclose(use_autoclose);
24424 this.set_use_auto_surround(use_auto_surround);
24425
24426 if let Some(new_selected_range) = new_selected_range_utf16 {
24427 let snapshot = this.buffer.read(cx).read(cx);
24428 let new_selected_ranges = marked_ranges
24429 .into_iter()
24430 .map(|marked_range| {
24431 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24432 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24433 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24434 snapshot.clip_offset_utf16(new_start, Bias::Left)
24435 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24436 })
24437 .collect::<Vec<_>>();
24438
24439 drop(snapshot);
24440 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24441 selections.select_ranges(new_selected_ranges)
24442 });
24443 }
24444 });
24445
24446 self.ime_transaction = self.ime_transaction.or(transaction);
24447 if let Some(transaction) = self.ime_transaction {
24448 self.buffer.update(cx, |buffer, cx| {
24449 buffer.group_until_transaction(transaction, cx);
24450 });
24451 }
24452
24453 if self.text_highlights::<InputComposition>(cx).is_none() {
24454 self.ime_transaction.take();
24455 }
24456 }
24457
24458 fn bounds_for_range(
24459 &mut self,
24460 range_utf16: Range<usize>,
24461 element_bounds: gpui::Bounds<Pixels>,
24462 window: &mut Window,
24463 cx: &mut Context<Self>,
24464 ) -> Option<gpui::Bounds<Pixels>> {
24465 let text_layout_details = self.text_layout_details(window);
24466 let CharacterDimensions {
24467 em_width,
24468 em_advance,
24469 line_height,
24470 } = self.character_dimensions(window);
24471
24472 let snapshot = self.snapshot(window, cx);
24473 let scroll_position = snapshot.scroll_position();
24474 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24475
24476 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24477 let x = Pixels::from(
24478 ScrollOffset::from(
24479 snapshot.x_for_display_point(start, &text_layout_details)
24480 + self.gutter_dimensions.full_width(),
24481 ) - scroll_left,
24482 );
24483 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24484
24485 Some(Bounds {
24486 origin: element_bounds.origin + point(x, y),
24487 size: size(em_width, line_height),
24488 })
24489 }
24490
24491 fn character_index_for_point(
24492 &mut self,
24493 point: gpui::Point<Pixels>,
24494 _window: &mut Window,
24495 _cx: &mut Context<Self>,
24496 ) -> Option<usize> {
24497 let position_map = self.last_position_map.as_ref()?;
24498 if !position_map.text_hitbox.contains(&point) {
24499 return None;
24500 }
24501 let display_point = position_map.point_for_position(point).previous_valid;
24502 let anchor = position_map
24503 .snapshot
24504 .display_point_to_anchor(display_point, Bias::Left);
24505 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24506 Some(utf16_offset.0)
24507 }
24508
24509 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24510 self.input_enabled
24511 }
24512}
24513
24514trait SelectionExt {
24515 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24516 fn spanned_rows(
24517 &self,
24518 include_end_if_at_line_start: bool,
24519 map: &DisplaySnapshot,
24520 ) -> Range<MultiBufferRow>;
24521}
24522
24523impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24524 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24525 let start = self
24526 .start
24527 .to_point(map.buffer_snapshot())
24528 .to_display_point(map);
24529 let end = self
24530 .end
24531 .to_point(map.buffer_snapshot())
24532 .to_display_point(map);
24533 if self.reversed {
24534 end..start
24535 } else {
24536 start..end
24537 }
24538 }
24539
24540 fn spanned_rows(
24541 &self,
24542 include_end_if_at_line_start: bool,
24543 map: &DisplaySnapshot,
24544 ) -> Range<MultiBufferRow> {
24545 let start = self.start.to_point(map.buffer_snapshot());
24546 let mut end = self.end.to_point(map.buffer_snapshot());
24547 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24548 end.row -= 1;
24549 }
24550
24551 let buffer_start = map.prev_line_boundary(start).0;
24552 let buffer_end = map.next_line_boundary(end).0;
24553 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24554 }
24555}
24556
24557impl<T: InvalidationRegion> InvalidationStack<T> {
24558 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24559 where
24560 S: Clone + ToOffset,
24561 {
24562 while let Some(region) = self.last() {
24563 let all_selections_inside_invalidation_ranges =
24564 if selections.len() == region.ranges().len() {
24565 selections
24566 .iter()
24567 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24568 .all(|(selection, invalidation_range)| {
24569 let head = selection.head().to_offset(buffer);
24570 invalidation_range.start <= head && invalidation_range.end >= head
24571 })
24572 } else {
24573 false
24574 };
24575
24576 if all_selections_inside_invalidation_ranges {
24577 break;
24578 } else {
24579 self.pop();
24580 }
24581 }
24582 }
24583}
24584
24585impl<T> Default for InvalidationStack<T> {
24586 fn default() -> Self {
24587 Self(Default::default())
24588 }
24589}
24590
24591impl<T> Deref for InvalidationStack<T> {
24592 type Target = Vec<T>;
24593
24594 fn deref(&self) -> &Self::Target {
24595 &self.0
24596 }
24597}
24598
24599impl<T> DerefMut for InvalidationStack<T> {
24600 fn deref_mut(&mut self) -> &mut Self::Target {
24601 &mut self.0
24602 }
24603}
24604
24605impl InvalidationRegion for SnippetState {
24606 fn ranges(&self) -> &[Range<Anchor>] {
24607 &self.ranges[self.active_index]
24608 }
24609}
24610
24611fn edit_prediction_edit_text(
24612 current_snapshot: &BufferSnapshot,
24613 edits: &[(Range<Anchor>, impl AsRef<str>)],
24614 edit_preview: &EditPreview,
24615 include_deletions: bool,
24616 cx: &App,
24617) -> HighlightedText {
24618 let edits = edits
24619 .iter()
24620 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24621 .collect::<Vec<_>>();
24622
24623 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24624}
24625
24626fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24627 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24628 // Just show the raw edit text with basic styling
24629 let mut text = String::new();
24630 let mut highlights = Vec::new();
24631
24632 let insertion_highlight_style = HighlightStyle {
24633 color: Some(cx.theme().colors().text),
24634 ..Default::default()
24635 };
24636
24637 for (_, edit_text) in edits {
24638 let start_offset = text.len();
24639 text.push_str(edit_text);
24640 let end_offset = text.len();
24641
24642 if start_offset < end_offset {
24643 highlights.push((start_offset..end_offset, insertion_highlight_style));
24644 }
24645 }
24646
24647 HighlightedText {
24648 text: text.into(),
24649 highlights,
24650 }
24651}
24652
24653pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24654 match severity {
24655 lsp::DiagnosticSeverity::ERROR => colors.error,
24656 lsp::DiagnosticSeverity::WARNING => colors.warning,
24657 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24658 lsp::DiagnosticSeverity::HINT => colors.info,
24659 _ => colors.ignored,
24660 }
24661}
24662
24663pub fn styled_runs_for_code_label<'a>(
24664 label: &'a CodeLabel,
24665 syntax_theme: &'a theme::SyntaxTheme,
24666) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24667 let fade_out = HighlightStyle {
24668 fade_out: Some(0.35),
24669 ..Default::default()
24670 };
24671
24672 let mut prev_end = label.filter_range.end;
24673 label
24674 .runs
24675 .iter()
24676 .enumerate()
24677 .flat_map(move |(ix, (range, highlight_id))| {
24678 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24679 style
24680 } else {
24681 return Default::default();
24682 };
24683 let muted_style = style.highlight(fade_out);
24684
24685 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24686 if range.start >= label.filter_range.end {
24687 if range.start > prev_end {
24688 runs.push((prev_end..range.start, fade_out));
24689 }
24690 runs.push((range.clone(), muted_style));
24691 } else if range.end <= label.filter_range.end {
24692 runs.push((range.clone(), style));
24693 } else {
24694 runs.push((range.start..label.filter_range.end, style));
24695 runs.push((label.filter_range.end..range.end, muted_style));
24696 }
24697 prev_end = cmp::max(prev_end, range.end);
24698
24699 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24700 runs.push((prev_end..label.text.len(), fade_out));
24701 }
24702
24703 runs
24704 })
24705}
24706
24707pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24708 let mut prev_index = 0;
24709 let mut prev_codepoint: Option<char> = None;
24710 text.char_indices()
24711 .chain([(text.len(), '\0')])
24712 .filter_map(move |(index, codepoint)| {
24713 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24714 let is_boundary = index == text.len()
24715 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24716 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24717 if is_boundary {
24718 let chunk = &text[prev_index..index];
24719 prev_index = index;
24720 Some(chunk)
24721 } else {
24722 None
24723 }
24724 })
24725}
24726
24727/// Given a string of text immediately before the cursor, iterates over possible
24728/// strings a snippet could match to. More precisely: returns an iterator over
24729/// suffixes of `text` created by splitting at word boundaries (before & after
24730/// every non-word character).
24731///
24732/// Shorter suffixes are returned first.
24733pub(crate) fn snippet_candidate_suffixes(
24734 text: &str,
24735 is_word_char: impl Fn(char) -> bool,
24736) -> impl std::iter::Iterator<Item = &str> {
24737 let mut prev_index = text.len();
24738 let mut prev_codepoint = None;
24739 text.char_indices()
24740 .rev()
24741 .chain([(0, '\0')])
24742 .filter_map(move |(index, codepoint)| {
24743 let prev_index = std::mem::replace(&mut prev_index, index);
24744 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24745 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
24746 None
24747 } else {
24748 let chunk = &text[prev_index..]; // go to end of string
24749 Some(chunk)
24750 }
24751 })
24752}
24753
24754pub trait RangeToAnchorExt: Sized {
24755 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24756
24757 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24758 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24759 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24760 }
24761}
24762
24763impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24764 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24765 let start_offset = self.start.to_offset(snapshot);
24766 let end_offset = self.end.to_offset(snapshot);
24767 if start_offset == end_offset {
24768 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24769 } else {
24770 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24771 }
24772 }
24773}
24774
24775pub trait RowExt {
24776 fn as_f64(&self) -> f64;
24777
24778 fn next_row(&self) -> Self;
24779
24780 fn previous_row(&self) -> Self;
24781
24782 fn minus(&self, other: Self) -> u32;
24783}
24784
24785impl RowExt for DisplayRow {
24786 fn as_f64(&self) -> f64 {
24787 self.0 as _
24788 }
24789
24790 fn next_row(&self) -> Self {
24791 Self(self.0 + 1)
24792 }
24793
24794 fn previous_row(&self) -> Self {
24795 Self(self.0.saturating_sub(1))
24796 }
24797
24798 fn minus(&self, other: Self) -> u32 {
24799 self.0 - other.0
24800 }
24801}
24802
24803impl RowExt for MultiBufferRow {
24804 fn as_f64(&self) -> f64 {
24805 self.0 as _
24806 }
24807
24808 fn next_row(&self) -> Self {
24809 Self(self.0 + 1)
24810 }
24811
24812 fn previous_row(&self) -> Self {
24813 Self(self.0.saturating_sub(1))
24814 }
24815
24816 fn minus(&self, other: Self) -> u32 {
24817 self.0 - other.0
24818 }
24819}
24820
24821trait RowRangeExt {
24822 type Row;
24823
24824 fn len(&self) -> usize;
24825
24826 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24827}
24828
24829impl RowRangeExt for Range<MultiBufferRow> {
24830 type Row = MultiBufferRow;
24831
24832 fn len(&self) -> usize {
24833 (self.end.0 - self.start.0) as usize
24834 }
24835
24836 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24837 (self.start.0..self.end.0).map(MultiBufferRow)
24838 }
24839}
24840
24841impl RowRangeExt for Range<DisplayRow> {
24842 type Row = DisplayRow;
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 = DisplayRow> {
24849 (self.start.0..self.end.0).map(DisplayRow)
24850 }
24851}
24852
24853/// If select range has more than one line, we
24854/// just point the cursor to range.start.
24855fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24856 if range.start.row == range.end.row {
24857 range
24858 } else {
24859 range.start..range.start
24860 }
24861}
24862pub struct KillRing(ClipboardItem);
24863impl Global for KillRing {}
24864
24865const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24866
24867enum BreakpointPromptEditAction {
24868 Log,
24869 Condition,
24870 HitCondition,
24871}
24872
24873struct BreakpointPromptEditor {
24874 pub(crate) prompt: Entity<Editor>,
24875 editor: WeakEntity<Editor>,
24876 breakpoint_anchor: Anchor,
24877 breakpoint: Breakpoint,
24878 edit_action: BreakpointPromptEditAction,
24879 block_ids: HashSet<CustomBlockId>,
24880 editor_margins: Arc<Mutex<EditorMargins>>,
24881 _subscriptions: Vec<Subscription>,
24882}
24883
24884impl BreakpointPromptEditor {
24885 const MAX_LINES: u8 = 4;
24886
24887 fn new(
24888 editor: WeakEntity<Editor>,
24889 breakpoint_anchor: Anchor,
24890 breakpoint: Breakpoint,
24891 edit_action: BreakpointPromptEditAction,
24892 window: &mut Window,
24893 cx: &mut Context<Self>,
24894 ) -> Self {
24895 let base_text = match edit_action {
24896 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24897 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24898 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24899 }
24900 .map(|msg| msg.to_string())
24901 .unwrap_or_default();
24902
24903 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24904 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24905
24906 let prompt = cx.new(|cx| {
24907 let mut prompt = Editor::new(
24908 EditorMode::AutoHeight {
24909 min_lines: 1,
24910 max_lines: Some(Self::MAX_LINES as usize),
24911 },
24912 buffer,
24913 None,
24914 window,
24915 cx,
24916 );
24917 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24918 prompt.set_show_cursor_when_unfocused(false, cx);
24919 prompt.set_placeholder_text(
24920 match edit_action {
24921 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24922 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24923 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24924 },
24925 window,
24926 cx,
24927 );
24928
24929 prompt
24930 });
24931
24932 Self {
24933 prompt,
24934 editor,
24935 breakpoint_anchor,
24936 breakpoint,
24937 edit_action,
24938 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24939 block_ids: Default::default(),
24940 _subscriptions: vec![],
24941 }
24942 }
24943
24944 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24945 self.block_ids.extend(block_ids)
24946 }
24947
24948 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24949 if let Some(editor) = self.editor.upgrade() {
24950 let message = self
24951 .prompt
24952 .read(cx)
24953 .buffer
24954 .read(cx)
24955 .as_singleton()
24956 .expect("A multi buffer in breakpoint prompt isn't possible")
24957 .read(cx)
24958 .as_rope()
24959 .to_string();
24960
24961 editor.update(cx, |editor, cx| {
24962 editor.edit_breakpoint_at_anchor(
24963 self.breakpoint_anchor,
24964 self.breakpoint.clone(),
24965 match self.edit_action {
24966 BreakpointPromptEditAction::Log => {
24967 BreakpointEditAction::EditLogMessage(message.into())
24968 }
24969 BreakpointPromptEditAction::Condition => {
24970 BreakpointEditAction::EditCondition(message.into())
24971 }
24972 BreakpointPromptEditAction::HitCondition => {
24973 BreakpointEditAction::EditHitCondition(message.into())
24974 }
24975 },
24976 cx,
24977 );
24978
24979 editor.remove_blocks(self.block_ids.clone(), None, cx);
24980 cx.focus_self(window);
24981 });
24982 }
24983 }
24984
24985 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24986 self.editor
24987 .update(cx, |editor, cx| {
24988 editor.remove_blocks(self.block_ids.clone(), None, cx);
24989 window.focus(&editor.focus_handle);
24990 })
24991 .log_err();
24992 }
24993
24994 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24995 let settings = ThemeSettings::get_global(cx);
24996 let text_style = TextStyle {
24997 color: if self.prompt.read(cx).read_only(cx) {
24998 cx.theme().colors().text_disabled
24999 } else {
25000 cx.theme().colors().text
25001 },
25002 font_family: settings.buffer_font.family.clone(),
25003 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25004 font_size: settings.buffer_font_size(cx).into(),
25005 font_weight: settings.buffer_font.weight,
25006 line_height: relative(settings.buffer_line_height.value()),
25007 ..Default::default()
25008 };
25009 EditorElement::new(
25010 &self.prompt,
25011 EditorStyle {
25012 background: cx.theme().colors().editor_background,
25013 local_player: cx.theme().players().local(),
25014 text: text_style,
25015 ..Default::default()
25016 },
25017 )
25018 }
25019}
25020
25021impl Render for BreakpointPromptEditor {
25022 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25023 let editor_margins = *self.editor_margins.lock();
25024 let gutter_dimensions = editor_margins.gutter;
25025 h_flex()
25026 .key_context("Editor")
25027 .bg(cx.theme().colors().editor_background)
25028 .border_y_1()
25029 .border_color(cx.theme().status().info_border)
25030 .size_full()
25031 .py(window.line_height() / 2.5)
25032 .on_action(cx.listener(Self::confirm))
25033 .on_action(cx.listener(Self::cancel))
25034 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25035 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25036 }
25037}
25038
25039impl Focusable for BreakpointPromptEditor {
25040 fn focus_handle(&self, cx: &App) -> FocusHandle {
25041 self.prompt.focus_handle(cx)
25042 }
25043}
25044
25045fn all_edits_insertions_or_deletions(
25046 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25047 snapshot: &MultiBufferSnapshot,
25048) -> bool {
25049 let mut all_insertions = true;
25050 let mut all_deletions = true;
25051
25052 for (range, new_text) in edits.iter() {
25053 let range_is_empty = range.to_offset(snapshot).is_empty();
25054 let text_is_empty = new_text.is_empty();
25055
25056 if range_is_empty != text_is_empty {
25057 if range_is_empty {
25058 all_deletions = false;
25059 } else {
25060 all_insertions = false;
25061 }
25062 } else {
25063 return false;
25064 }
25065
25066 if !all_insertions && !all_deletions {
25067 return false;
25068 }
25069 }
25070 all_insertions || all_deletions
25071}
25072
25073struct MissingEditPredictionKeybindingTooltip;
25074
25075impl Render for MissingEditPredictionKeybindingTooltip {
25076 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25077 ui::tooltip_container(cx, |container, cx| {
25078 container
25079 .flex_shrink_0()
25080 .max_w_80()
25081 .min_h(rems_from_px(124.))
25082 .justify_between()
25083 .child(
25084 v_flex()
25085 .flex_1()
25086 .text_ui_sm(cx)
25087 .child(Label::new("Conflict with Accept Keybinding"))
25088 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25089 )
25090 .child(
25091 h_flex()
25092 .pb_1()
25093 .gap_1()
25094 .items_end()
25095 .w_full()
25096 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
25097 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
25098 }))
25099 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
25100 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
25101 })),
25102 )
25103 })
25104 }
25105}
25106
25107#[derive(Debug, Clone, Copy, PartialEq)]
25108pub struct LineHighlight {
25109 pub background: Background,
25110 pub border: Option<gpui::Hsla>,
25111 pub include_gutter: bool,
25112 pub type_id: Option<TypeId>,
25113}
25114
25115struct LineManipulationResult {
25116 pub new_text: String,
25117 pub line_count_before: usize,
25118 pub line_count_after: usize,
25119}
25120
25121fn render_diff_hunk_controls(
25122 row: u32,
25123 status: &DiffHunkStatus,
25124 hunk_range: Range<Anchor>,
25125 is_created_file: bool,
25126 line_height: Pixels,
25127 editor: &Entity<Editor>,
25128 _window: &mut Window,
25129 cx: &mut App,
25130) -> AnyElement {
25131 h_flex()
25132 .h(line_height)
25133 .mr_1()
25134 .gap_1()
25135 .px_0p5()
25136 .pb_1()
25137 .border_x_1()
25138 .border_b_1()
25139 .border_color(cx.theme().colors().border_variant)
25140 .rounded_b_lg()
25141 .bg(cx.theme().colors().editor_background)
25142 .gap_1()
25143 .block_mouse_except_scroll()
25144 .shadow_md()
25145 .child(if status.has_secondary_hunk() {
25146 Button::new(("stage", row as u64), "Stage")
25147 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25148 .tooltip({
25149 let focus_handle = editor.focus_handle(cx);
25150 move |_window, cx| {
25151 Tooltip::for_action_in(
25152 "Stage Hunk",
25153 &::git::ToggleStaged,
25154 &focus_handle,
25155 cx,
25156 )
25157 }
25158 })
25159 .on_click({
25160 let editor = editor.clone();
25161 move |_event, _window, cx| {
25162 editor.update(cx, |editor, cx| {
25163 editor.stage_or_unstage_diff_hunks(
25164 true,
25165 vec![hunk_range.start..hunk_range.start],
25166 cx,
25167 );
25168 });
25169 }
25170 })
25171 } else {
25172 Button::new(("unstage", row as u64), "Unstage")
25173 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25174 .tooltip({
25175 let focus_handle = editor.focus_handle(cx);
25176 move |_window, cx| {
25177 Tooltip::for_action_in(
25178 "Unstage Hunk",
25179 &::git::ToggleStaged,
25180 &focus_handle,
25181 cx,
25182 )
25183 }
25184 })
25185 .on_click({
25186 let editor = editor.clone();
25187 move |_event, _window, cx| {
25188 editor.update(cx, |editor, cx| {
25189 editor.stage_or_unstage_diff_hunks(
25190 false,
25191 vec![hunk_range.start..hunk_range.start],
25192 cx,
25193 );
25194 });
25195 }
25196 })
25197 })
25198 .child(
25199 Button::new(("restore", row as u64), "Restore")
25200 .tooltip({
25201 let focus_handle = editor.focus_handle(cx);
25202 move |_window, cx| {
25203 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
25204 }
25205 })
25206 .on_click({
25207 let editor = editor.clone();
25208 move |_event, window, cx| {
25209 editor.update(cx, |editor, cx| {
25210 let snapshot = editor.snapshot(window, cx);
25211 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
25212 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
25213 });
25214 }
25215 })
25216 .disabled(is_created_file),
25217 )
25218 .when(
25219 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
25220 |el| {
25221 el.child(
25222 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
25223 .shape(IconButtonShape::Square)
25224 .icon_size(IconSize::Small)
25225 // .disabled(!has_multiple_hunks)
25226 .tooltip({
25227 let focus_handle = editor.focus_handle(cx);
25228 move |_window, cx| {
25229 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
25230 }
25231 })
25232 .on_click({
25233 let editor = editor.clone();
25234 move |_event, window, cx| {
25235 editor.update(cx, |editor, cx| {
25236 let snapshot = editor.snapshot(window, cx);
25237 let position =
25238 hunk_range.end.to_point(&snapshot.buffer_snapshot());
25239 editor.go_to_hunk_before_or_after_position(
25240 &snapshot,
25241 position,
25242 Direction::Next,
25243 window,
25244 cx,
25245 );
25246 editor.expand_selected_diff_hunks(cx);
25247 });
25248 }
25249 }),
25250 )
25251 .child(
25252 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25253 .shape(IconButtonShape::Square)
25254 .icon_size(IconSize::Small)
25255 // .disabled(!has_multiple_hunks)
25256 .tooltip({
25257 let focus_handle = editor.focus_handle(cx);
25258 move |_window, cx| {
25259 Tooltip::for_action_in(
25260 "Previous Hunk",
25261 &GoToPreviousHunk,
25262 &focus_handle,
25263 cx,
25264 )
25265 }
25266 })
25267 .on_click({
25268 let editor = editor.clone();
25269 move |_event, window, cx| {
25270 editor.update(cx, |editor, cx| {
25271 let snapshot = editor.snapshot(window, cx);
25272 let point =
25273 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25274 editor.go_to_hunk_before_or_after_position(
25275 &snapshot,
25276 point,
25277 Direction::Prev,
25278 window,
25279 cx,
25280 );
25281 editor.expand_selected_diff_hunks(cx);
25282 });
25283 }
25284 }),
25285 )
25286 },
25287 )
25288 .into_any_element()
25289}
25290
25291pub fn multibuffer_context_lines(cx: &App) -> u32 {
25292 EditorSettings::try_get(cx)
25293 .map(|settings| settings.excerpt_context_lines)
25294 .unwrap_or(2)
25295 .min(32)
25296}