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, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
68 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
69 ToPoint,
70};
71pub use text::Bias;
72
73use ::git::{
74 Restore,
75 blame::{BlameEntry, ParsedCommitMessage},
76 status::FileStatus,
77};
78use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
79use anyhow::{Context as _, Result, anyhow};
80use blink_manager::BlinkManager;
81use buffer_diff::DiffHunkStatus;
82use client::{Collaborator, ParticipantIndex, parse_zed_link};
83use clock::ReplicaId;
84use code_context_menus::{
85 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
86 CompletionsMenu, ContextMenuOrigin,
87};
88use collections::{BTreeMap, HashMap, HashSet, VecDeque};
89use convert_case::{Case, Casing};
90use dap::TelemetrySpawnLocation;
91use display_map::*;
92use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
93use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
94use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
95use futures::{
96 FutureExt, StreamExt as _,
97 future::{self, Shared, join},
98 stream::FuturesUnordered,
99};
100use fuzzy::{StringMatch, StringMatchCandidate};
101use git::blame::{GitBlame, GlobalBlameRenderer};
102use gpui::{
103 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
104 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
105 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
106 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
107 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
108 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
109 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
110 div, point, prelude::*, pulsating_between, px, relative, size,
111};
112use hover_links::{HoverLink, HoveredLinkState, find_file};
113use hover_popover::{HoverState, hide_hover};
114use indent_guides::ActiveIndentGuidesState;
115use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
116use itertools::{Either, Itertools};
117use language::{
118 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
119 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
120 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
121 IndentSize, Language, LanguageRegistry, OffsetRangeExt, OutlineItem, Point, Runnable,
122 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, 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/// Addons allow storing per-editor state in other crates (e.g. Vim)
860pub trait Addon: 'static {
861 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
862
863 fn render_buffer_header_controls(
864 &self,
865 _: &ExcerptInfo,
866 _: &Window,
867 _: &App,
868 ) -> Option<AnyElement> {
869 None
870 }
871
872 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
873 None
874 }
875
876 fn to_any(&self) -> &dyn std::any::Any;
877
878 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
879 None
880 }
881}
882
883struct ChangeLocation {
884 current: Option<Vec<Anchor>>,
885 original: Vec<Anchor>,
886}
887impl ChangeLocation {
888 fn locations(&self) -> &[Anchor] {
889 self.current.as_ref().unwrap_or(&self.original)
890 }
891}
892
893/// A set of caret positions, registered when the editor was edited.
894pub struct ChangeList {
895 changes: Vec<ChangeLocation>,
896 /// Currently "selected" change.
897 position: Option<usize>,
898}
899
900impl ChangeList {
901 pub fn new() -> Self {
902 Self {
903 changes: Vec::new(),
904 position: None,
905 }
906 }
907
908 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
909 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
910 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
911 if self.changes.is_empty() {
912 return None;
913 }
914
915 let prev = self.position.unwrap_or(self.changes.len());
916 let next = if direction == Direction::Prev {
917 prev.saturating_sub(count)
918 } else {
919 (prev + count).min(self.changes.len() - 1)
920 };
921 self.position = Some(next);
922 self.changes.get(next).map(|change| change.locations())
923 }
924
925 /// Adds a new change to the list, resetting the change list position.
926 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
927 self.position.take();
928 if let Some(last) = self.changes.last_mut()
929 && group
930 {
931 last.current = Some(new_positions)
932 } else {
933 self.changes.push(ChangeLocation {
934 original: new_positions,
935 current: None,
936 });
937 }
938 }
939
940 pub fn last(&self) -> Option<&[Anchor]> {
941 self.changes.last().map(|change| change.locations())
942 }
943
944 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
945 self.changes.last().map(|change| change.original.as_slice())
946 }
947
948 pub fn invert_last_group(&mut self) {
949 if let Some(last) = self.changes.last_mut()
950 && let Some(current) = last.current.as_mut()
951 {
952 mem::swap(&mut last.original, current);
953 }
954 }
955}
956
957#[derive(Clone)]
958struct InlineBlamePopoverState {
959 scroll_handle: ScrollHandle,
960 commit_message: Option<ParsedCommitMessage>,
961 markdown: Entity<Markdown>,
962}
963
964struct InlineBlamePopover {
965 position: gpui::Point<Pixels>,
966 hide_task: Option<Task<()>>,
967 popover_bounds: Option<Bounds<Pixels>>,
968 popover_state: InlineBlamePopoverState,
969 keyboard_grace: bool,
970}
971
972enum SelectionDragState {
973 /// State when no drag related activity is detected.
974 None,
975 /// State when the mouse is down on a selection that is about to be dragged.
976 ReadyToDrag {
977 selection: Selection<Anchor>,
978 click_position: gpui::Point<Pixels>,
979 mouse_down_time: Instant,
980 },
981 /// State when the mouse is dragging the selection in the editor.
982 Dragging {
983 selection: Selection<Anchor>,
984 drop_cursor: Selection<Anchor>,
985 hide_drop_cursor: bool,
986 },
987}
988
989enum ColumnarSelectionState {
990 FromMouse {
991 selection_tail: Anchor,
992 display_point: Option<DisplayPoint>,
993 },
994 FromSelection {
995 selection_tail: Anchor,
996 },
997}
998
999/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1000/// a breakpoint on them.
1001#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1002struct PhantomBreakpointIndicator {
1003 display_row: DisplayRow,
1004 /// There's a small debounce between hovering over the line and showing the indicator.
1005 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1006 is_active: bool,
1007 collides_with_existing_breakpoint: bool,
1008}
1009
1010/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1011///
1012/// See the [module level documentation](self) for more information.
1013pub struct Editor {
1014 focus_handle: FocusHandle,
1015 last_focused_descendant: Option<WeakFocusHandle>,
1016 /// The text buffer being edited
1017 buffer: Entity<MultiBuffer>,
1018 /// Map of how text in the buffer should be displayed.
1019 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1020 pub display_map: Entity<DisplayMap>,
1021 placeholder_display_map: Option<Entity<DisplayMap>>,
1022 pub selections: SelectionsCollection,
1023 pub scroll_manager: ScrollManager,
1024 /// When inline assist editors are linked, they all render cursors because
1025 /// typing enters text into each of them, even the ones that aren't focused.
1026 pub(crate) show_cursor_when_unfocused: bool,
1027 columnar_selection_state: Option<ColumnarSelectionState>,
1028 add_selections_state: Option<AddSelectionsState>,
1029 select_next_state: Option<SelectNextState>,
1030 select_prev_state: Option<SelectNextState>,
1031 selection_history: SelectionHistory,
1032 defer_selection_effects: bool,
1033 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1034 autoclose_regions: Vec<AutocloseRegion>,
1035 snippet_stack: InvalidationStack<SnippetState>,
1036 select_syntax_node_history: SelectSyntaxNodeHistory,
1037 ime_transaction: Option<TransactionId>,
1038 pub diagnostics_max_severity: DiagnosticSeverity,
1039 active_diagnostics: ActiveDiagnostic,
1040 show_inline_diagnostics: bool,
1041 inline_diagnostics_update: Task<()>,
1042 inline_diagnostics_enabled: bool,
1043 diagnostics_enabled: bool,
1044 word_completions_enabled: bool,
1045 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1046 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1047 hard_wrap: Option<usize>,
1048 project: Option<Entity<Project>>,
1049 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1050 completion_provider: Option<Rc<dyn CompletionProvider>>,
1051 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1052 blink_manager: Entity<BlinkManager>,
1053 show_cursor_names: bool,
1054 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1055 pub show_local_selections: bool,
1056 mode: EditorMode,
1057 show_breadcrumbs: bool,
1058 show_gutter: bool,
1059 show_scrollbars: ScrollbarAxes,
1060 minimap_visibility: MinimapVisibility,
1061 offset_content: bool,
1062 disable_expand_excerpt_buttons: bool,
1063 show_line_numbers: Option<bool>,
1064 use_relative_line_numbers: Option<bool>,
1065 show_git_diff_gutter: Option<bool>,
1066 show_code_actions: Option<bool>,
1067 show_runnables: Option<bool>,
1068 show_breakpoints: Option<bool>,
1069 show_wrap_guides: Option<bool>,
1070 show_indent_guides: Option<bool>,
1071 highlight_order: usize,
1072 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1073 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1074 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1075 scrollbar_marker_state: ScrollbarMarkerState,
1076 active_indent_guides_state: ActiveIndentGuidesState,
1077 nav_history: Option<ItemNavHistory>,
1078 context_menu: RefCell<Option<CodeContextMenu>>,
1079 context_menu_options: Option<ContextMenuOptions>,
1080 mouse_context_menu: Option<MouseContextMenu>,
1081 completion_tasks: Vec<(CompletionId, Task<()>)>,
1082 inline_blame_popover: Option<InlineBlamePopover>,
1083 inline_blame_popover_show_task: Option<Task<()>>,
1084 signature_help_state: SignatureHelpState,
1085 auto_signature_help: Option<bool>,
1086 find_all_references_task_sources: Vec<Anchor>,
1087 next_completion_id: CompletionId,
1088 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1089 code_actions_task: Option<Task<Result<()>>>,
1090 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1091 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1092 document_highlights_task: Option<Task<()>>,
1093 linked_editing_range_task: Option<Task<Option<()>>>,
1094 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1095 pending_rename: Option<RenameState>,
1096 searchable: bool,
1097 cursor_shape: CursorShape,
1098 current_line_highlight: Option<CurrentLineHighlight>,
1099 pub collapse_matches: bool,
1100 autoindent_mode: Option<AutoindentMode>,
1101 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1102 input_enabled: bool,
1103 use_modal_editing: bool,
1104 read_only: bool,
1105 leader_id: Option<CollaboratorId>,
1106 remote_id: Option<ViewId>,
1107 pub hover_state: HoverState,
1108 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1109 gutter_hovered: bool,
1110 hovered_link_state: Option<HoveredLinkState>,
1111 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1112 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1113 active_edit_prediction: Option<EditPredictionState>,
1114 /// Used to prevent flickering as the user types while the menu is open
1115 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1116 edit_prediction_settings: EditPredictionSettings,
1117 edit_predictions_hidden_for_vim_mode: bool,
1118 show_edit_predictions_override: Option<bool>,
1119 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1120 edit_prediction_preview: EditPredictionPreview,
1121 edit_prediction_indent_conflict: bool,
1122 edit_prediction_requires_modifier_in_indent_conflict: bool,
1123 next_inlay_id: usize,
1124 next_color_inlay_id: usize,
1125 _subscriptions: Vec<Subscription>,
1126 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1127 gutter_dimensions: GutterDimensions,
1128 style: Option<EditorStyle>,
1129 text_style_refinement: Option<TextStyleRefinement>,
1130 next_editor_action_id: EditorActionId,
1131 editor_actions: Rc<
1132 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1133 >,
1134 use_autoclose: bool,
1135 use_auto_surround: bool,
1136 auto_replace_emoji_shortcode: bool,
1137 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1138 show_git_blame_gutter: bool,
1139 show_git_blame_inline: bool,
1140 show_git_blame_inline_delay_task: Option<Task<()>>,
1141 git_blame_inline_enabled: bool,
1142 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1143 buffer_serialization: Option<BufferSerialization>,
1144 show_selection_menu: Option<bool>,
1145 blame: Option<Entity<GitBlame>>,
1146 blame_subscription: Option<Subscription>,
1147 custom_context_menu: Option<
1148 Box<
1149 dyn 'static
1150 + Fn(
1151 &mut Self,
1152 DisplayPoint,
1153 &mut Window,
1154 &mut Context<Self>,
1155 ) -> Option<Entity<ui::ContextMenu>>,
1156 >,
1157 >,
1158 last_bounds: Option<Bounds<Pixels>>,
1159 last_position_map: Option<Rc<PositionMap>>,
1160 expect_bounds_change: Option<Bounds<Pixels>>,
1161 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1162 tasks_update_task: Option<Task<()>>,
1163 breakpoint_store: Option<Entity<BreakpointStore>>,
1164 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1165 hovered_diff_hunk_row: Option<DisplayRow>,
1166 pull_diagnostics_task: Task<()>,
1167 in_project_search: bool,
1168 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1169 breadcrumb_header: Option<String>,
1170 focused_block: Option<FocusedBlock>,
1171 next_scroll_position: NextScrollCursorCenterTopBottom,
1172 addons: HashMap<TypeId, Box<dyn Addon>>,
1173 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1174 load_diff_task: Option<Shared<Task<()>>>,
1175 /// Whether we are temporarily displaying a diff other than git's
1176 temporary_diff_override: bool,
1177 selection_mark_mode: bool,
1178 toggle_fold_multiple_buffers: Task<()>,
1179 _scroll_cursor_center_top_bottom_task: Task<()>,
1180 serialize_selections: Task<()>,
1181 serialize_folds: Task<()>,
1182 mouse_cursor_hidden: bool,
1183 minimap: Option<Entity<Self>>,
1184 hide_mouse_mode: HideMouseMode,
1185 pub change_list: ChangeList,
1186 inline_value_cache: InlineValueCache,
1187
1188 selection_drag_state: SelectionDragState,
1189 colors: Option<LspColorData>,
1190 post_scroll_update: Task<()>,
1191 refresh_colors_task: Task<()>,
1192 inlay_hints: Option<LspInlayHintData>,
1193 folding_newlines: Task<()>,
1194 select_next_is_case_sensitive: Option<bool>,
1195 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1196}
1197
1198fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1199 if debounce_ms > 0 {
1200 Some(Duration::from_millis(debounce_ms))
1201 } else {
1202 None
1203 }
1204}
1205
1206#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1207enum NextScrollCursorCenterTopBottom {
1208 #[default]
1209 Center,
1210 Top,
1211 Bottom,
1212}
1213
1214impl NextScrollCursorCenterTopBottom {
1215 fn next(&self) -> Self {
1216 match self {
1217 Self::Center => Self::Top,
1218 Self::Top => Self::Bottom,
1219 Self::Bottom => Self::Center,
1220 }
1221 }
1222}
1223
1224#[derive(Clone)]
1225pub struct EditorSnapshot {
1226 pub mode: EditorMode,
1227 show_gutter: bool,
1228 show_line_numbers: Option<bool>,
1229 show_git_diff_gutter: Option<bool>,
1230 show_code_actions: Option<bool>,
1231 show_runnables: Option<bool>,
1232 show_breakpoints: Option<bool>,
1233 git_blame_gutter_max_author_length: Option<usize>,
1234 pub display_snapshot: DisplaySnapshot,
1235 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1236 is_focused: bool,
1237 scroll_anchor: ScrollAnchor,
1238 ongoing_scroll: OngoingScroll,
1239 current_line_highlight: CurrentLineHighlight,
1240 gutter_hovered: bool,
1241}
1242
1243#[derive(Default, Debug, Clone, Copy)]
1244pub struct GutterDimensions {
1245 pub left_padding: Pixels,
1246 pub right_padding: Pixels,
1247 pub width: Pixels,
1248 pub margin: Pixels,
1249 pub git_blame_entries_width: Option<Pixels>,
1250}
1251
1252impl GutterDimensions {
1253 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1254 Self {
1255 margin: Self::default_gutter_margin(font_id, font_size, cx),
1256 ..Default::default()
1257 }
1258 }
1259
1260 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1261 -cx.text_system().descent(font_id, font_size)
1262 }
1263 /// The full width of the space taken up by the gutter.
1264 pub fn full_width(&self) -> Pixels {
1265 self.margin + self.width
1266 }
1267
1268 /// The width of the space reserved for the fold indicators,
1269 /// use alongside 'justify_end' and `gutter_width` to
1270 /// right align content with the line numbers
1271 pub fn fold_area_width(&self) -> Pixels {
1272 self.margin + self.right_padding
1273 }
1274}
1275
1276struct CharacterDimensions {
1277 em_width: Pixels,
1278 em_advance: Pixels,
1279 line_height: Pixels,
1280}
1281
1282#[derive(Debug)]
1283pub struct RemoteSelection {
1284 pub replica_id: ReplicaId,
1285 pub selection: Selection<Anchor>,
1286 pub cursor_shape: CursorShape,
1287 pub collaborator_id: CollaboratorId,
1288 pub line_mode: bool,
1289 pub user_name: Option<SharedString>,
1290 pub color: PlayerColor,
1291}
1292
1293#[derive(Clone, Debug)]
1294struct SelectionHistoryEntry {
1295 selections: Arc<[Selection<Anchor>]>,
1296 select_next_state: Option<SelectNextState>,
1297 select_prev_state: Option<SelectNextState>,
1298 add_selections_state: Option<AddSelectionsState>,
1299}
1300
1301#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1302enum SelectionHistoryMode {
1303 #[default]
1304 Normal,
1305 Undoing,
1306 Redoing,
1307 Skipping,
1308}
1309
1310#[derive(Clone, PartialEq, Eq, Hash)]
1311struct HoveredCursor {
1312 replica_id: ReplicaId,
1313 selection_id: usize,
1314}
1315
1316#[derive(Debug)]
1317/// SelectionEffects controls the side-effects of updating the selection.
1318///
1319/// The default behaviour does "what you mostly want":
1320/// - it pushes to the nav history if the cursor moved by >10 lines
1321/// - it re-triggers completion requests
1322/// - it scrolls to fit
1323///
1324/// You might want to modify these behaviours. For example when doing a "jump"
1325/// like go to definition, we always want to add to nav history; but when scrolling
1326/// in vim mode we never do.
1327///
1328/// Similarly, you might want to disable scrolling if you don't want the viewport to
1329/// move.
1330#[derive(Clone)]
1331pub struct SelectionEffects {
1332 nav_history: Option<bool>,
1333 completions: bool,
1334 scroll: Option<Autoscroll>,
1335}
1336
1337impl Default for SelectionEffects {
1338 fn default() -> Self {
1339 Self {
1340 nav_history: None,
1341 completions: true,
1342 scroll: Some(Autoscroll::fit()),
1343 }
1344 }
1345}
1346impl SelectionEffects {
1347 pub fn scroll(scroll: Autoscroll) -> Self {
1348 Self {
1349 scroll: Some(scroll),
1350 ..Default::default()
1351 }
1352 }
1353
1354 pub fn no_scroll() -> Self {
1355 Self {
1356 scroll: None,
1357 ..Default::default()
1358 }
1359 }
1360
1361 pub fn completions(self, completions: bool) -> Self {
1362 Self {
1363 completions,
1364 ..self
1365 }
1366 }
1367
1368 pub fn nav_history(self, nav_history: bool) -> Self {
1369 Self {
1370 nav_history: Some(nav_history),
1371 ..self
1372 }
1373 }
1374}
1375
1376struct DeferredSelectionEffectsState {
1377 changed: bool,
1378 effects: SelectionEffects,
1379 old_cursor_position: Anchor,
1380 history_entry: SelectionHistoryEntry,
1381}
1382
1383#[derive(Default)]
1384struct SelectionHistory {
1385 #[allow(clippy::type_complexity)]
1386 selections_by_transaction:
1387 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1388 mode: SelectionHistoryMode,
1389 undo_stack: VecDeque<SelectionHistoryEntry>,
1390 redo_stack: VecDeque<SelectionHistoryEntry>,
1391}
1392
1393impl SelectionHistory {
1394 #[track_caller]
1395 fn insert_transaction(
1396 &mut self,
1397 transaction_id: TransactionId,
1398 selections: Arc<[Selection<Anchor>]>,
1399 ) {
1400 if selections.is_empty() {
1401 log::error!(
1402 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1403 std::panic::Location::caller()
1404 );
1405 return;
1406 }
1407 self.selections_by_transaction
1408 .insert(transaction_id, (selections, None));
1409 }
1410
1411 #[allow(clippy::type_complexity)]
1412 fn transaction(
1413 &self,
1414 transaction_id: TransactionId,
1415 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1416 self.selections_by_transaction.get(&transaction_id)
1417 }
1418
1419 #[allow(clippy::type_complexity)]
1420 fn transaction_mut(
1421 &mut self,
1422 transaction_id: TransactionId,
1423 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1424 self.selections_by_transaction.get_mut(&transaction_id)
1425 }
1426
1427 fn push(&mut self, entry: SelectionHistoryEntry) {
1428 if !entry.selections.is_empty() {
1429 match self.mode {
1430 SelectionHistoryMode::Normal => {
1431 self.push_undo(entry);
1432 self.redo_stack.clear();
1433 }
1434 SelectionHistoryMode::Undoing => self.push_redo(entry),
1435 SelectionHistoryMode::Redoing => self.push_undo(entry),
1436 SelectionHistoryMode::Skipping => {}
1437 }
1438 }
1439 }
1440
1441 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1442 if self
1443 .undo_stack
1444 .back()
1445 .is_none_or(|e| e.selections != entry.selections)
1446 {
1447 self.undo_stack.push_back(entry);
1448 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1449 self.undo_stack.pop_front();
1450 }
1451 }
1452 }
1453
1454 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1455 if self
1456 .redo_stack
1457 .back()
1458 .is_none_or(|e| e.selections != entry.selections)
1459 {
1460 self.redo_stack.push_back(entry);
1461 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1462 self.redo_stack.pop_front();
1463 }
1464 }
1465 }
1466}
1467
1468#[derive(Clone, Copy)]
1469pub struct RowHighlightOptions {
1470 pub autoscroll: bool,
1471 pub include_gutter: bool,
1472}
1473
1474impl Default for RowHighlightOptions {
1475 fn default() -> Self {
1476 Self {
1477 autoscroll: Default::default(),
1478 include_gutter: true,
1479 }
1480 }
1481}
1482
1483struct RowHighlight {
1484 index: usize,
1485 range: Range<Anchor>,
1486 color: Hsla,
1487 options: RowHighlightOptions,
1488 type_id: TypeId,
1489}
1490
1491#[derive(Clone, Debug)]
1492struct AddSelectionsState {
1493 groups: Vec<AddSelectionsGroup>,
1494}
1495
1496#[derive(Clone, Debug)]
1497struct AddSelectionsGroup {
1498 above: bool,
1499 stack: Vec<usize>,
1500}
1501
1502#[derive(Clone)]
1503struct SelectNextState {
1504 query: AhoCorasick,
1505 wordwise: bool,
1506 done: bool,
1507}
1508
1509impl std::fmt::Debug for SelectNextState {
1510 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1511 f.debug_struct(std::any::type_name::<Self>())
1512 .field("wordwise", &self.wordwise)
1513 .field("done", &self.done)
1514 .finish()
1515 }
1516}
1517
1518#[derive(Debug)]
1519struct AutocloseRegion {
1520 selection_id: usize,
1521 range: Range<Anchor>,
1522 pair: BracketPair,
1523}
1524
1525#[derive(Debug)]
1526struct SnippetState {
1527 ranges: Vec<Vec<Range<Anchor>>>,
1528 active_index: usize,
1529 choices: Vec<Option<Vec<String>>>,
1530}
1531
1532#[doc(hidden)]
1533pub struct RenameState {
1534 pub range: Range<Anchor>,
1535 pub old_name: Arc<str>,
1536 pub editor: Entity<Editor>,
1537 block_id: CustomBlockId,
1538}
1539
1540struct InvalidationStack<T>(Vec<T>);
1541
1542struct RegisteredEditPredictionProvider {
1543 provider: Arc<dyn EditPredictionProviderHandle>,
1544 _subscription: Subscription,
1545}
1546
1547#[derive(Debug, PartialEq, Eq)]
1548pub struct ActiveDiagnosticGroup {
1549 pub active_range: Range<Anchor>,
1550 pub active_message: String,
1551 pub group_id: usize,
1552 pub blocks: HashSet<CustomBlockId>,
1553}
1554
1555#[derive(Debug, PartialEq, Eq)]
1556
1557pub(crate) enum ActiveDiagnostic {
1558 None,
1559 All,
1560 Group(ActiveDiagnosticGroup),
1561}
1562
1563#[derive(Serialize, Deserialize, Clone, Debug)]
1564pub struct ClipboardSelection {
1565 /// The number of bytes in this selection.
1566 pub len: usize,
1567 /// Whether this was a full-line selection.
1568 pub is_entire_line: bool,
1569 /// The indentation of the first line when this content was originally copied.
1570 pub first_line_indent: u32,
1571}
1572
1573// selections, scroll behavior, was newest selection reversed
1574type SelectSyntaxNodeHistoryState = (
1575 Box<[Selection<MultiBufferOffset>]>,
1576 SelectSyntaxNodeScrollBehavior,
1577 bool,
1578);
1579
1580#[derive(Default)]
1581struct SelectSyntaxNodeHistory {
1582 stack: Vec<SelectSyntaxNodeHistoryState>,
1583 // disable temporarily to allow changing selections without losing the stack
1584 pub disable_clearing: bool,
1585}
1586
1587impl SelectSyntaxNodeHistory {
1588 pub fn try_clear(&mut self) {
1589 if !self.disable_clearing {
1590 self.stack.clear();
1591 }
1592 }
1593
1594 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1595 self.stack.push(selection);
1596 }
1597
1598 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1599 self.stack.pop()
1600 }
1601}
1602
1603enum SelectSyntaxNodeScrollBehavior {
1604 CursorTop,
1605 FitSelection,
1606 CursorBottom,
1607}
1608
1609#[derive(Debug)]
1610pub(crate) struct NavigationData {
1611 cursor_anchor: Anchor,
1612 cursor_position: Point,
1613 scroll_anchor: ScrollAnchor,
1614 scroll_top_row: u32,
1615}
1616
1617#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1618pub enum GotoDefinitionKind {
1619 Symbol,
1620 Declaration,
1621 Type,
1622 Implementation,
1623}
1624
1625pub enum FormatTarget {
1626 Buffers(HashSet<Entity<Buffer>>),
1627 Ranges(Vec<Range<MultiBufferPoint>>),
1628}
1629
1630pub(crate) struct FocusedBlock {
1631 id: BlockId,
1632 focus_handle: WeakFocusHandle,
1633}
1634
1635#[derive(Clone)]
1636enum JumpData {
1637 MultiBufferRow {
1638 row: MultiBufferRow,
1639 line_offset_from_top: u32,
1640 },
1641 MultiBufferPoint {
1642 excerpt_id: ExcerptId,
1643 position: Point,
1644 anchor: text::Anchor,
1645 line_offset_from_top: u32,
1646 },
1647}
1648
1649pub enum MultibufferSelectionMode {
1650 First,
1651 All,
1652}
1653
1654#[derive(Clone, Copy, Debug, Default)]
1655pub struct RewrapOptions {
1656 pub override_language_settings: bool,
1657 pub preserve_existing_whitespace: bool,
1658}
1659
1660impl Editor {
1661 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1662 let buffer = cx.new(|cx| Buffer::local("", cx));
1663 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1664 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1665 }
1666
1667 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1668 let buffer = cx.new(|cx| Buffer::local("", cx));
1669 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1670 Self::new(EditorMode::full(), buffer, None, window, cx)
1671 }
1672
1673 pub fn auto_height(
1674 min_lines: usize,
1675 max_lines: usize,
1676 window: &mut Window,
1677 cx: &mut Context<Self>,
1678 ) -> Self {
1679 let buffer = cx.new(|cx| Buffer::local("", cx));
1680 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1681 Self::new(
1682 EditorMode::AutoHeight {
1683 min_lines,
1684 max_lines: Some(max_lines),
1685 },
1686 buffer,
1687 None,
1688 window,
1689 cx,
1690 )
1691 }
1692
1693 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1694 /// The editor grows as tall as needed to fit its content.
1695 pub fn auto_height_unbounded(
1696 min_lines: usize,
1697 window: &mut Window,
1698 cx: &mut Context<Self>,
1699 ) -> Self {
1700 let buffer = cx.new(|cx| Buffer::local("", cx));
1701 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1702 Self::new(
1703 EditorMode::AutoHeight {
1704 min_lines,
1705 max_lines: None,
1706 },
1707 buffer,
1708 None,
1709 window,
1710 cx,
1711 )
1712 }
1713
1714 pub fn for_buffer(
1715 buffer: Entity<Buffer>,
1716 project: Option<Entity<Project>>,
1717 window: &mut Window,
1718 cx: &mut Context<Self>,
1719 ) -> Self {
1720 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1721 Self::new(EditorMode::full(), buffer, project, window, cx)
1722 }
1723
1724 pub fn for_multibuffer(
1725 buffer: Entity<MultiBuffer>,
1726 project: Option<Entity<Project>>,
1727 window: &mut Window,
1728 cx: &mut Context<Self>,
1729 ) -> Self {
1730 Self::new(EditorMode::full(), buffer, project, window, cx)
1731 }
1732
1733 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1734 let mut clone = Self::new(
1735 self.mode.clone(),
1736 self.buffer.clone(),
1737 self.project.clone(),
1738 window,
1739 cx,
1740 );
1741 self.display_map.update(cx, |display_map, cx| {
1742 let snapshot = display_map.snapshot(cx);
1743 clone.display_map.update(cx, |display_map, cx| {
1744 display_map.set_state(&snapshot, cx);
1745 });
1746 });
1747 clone.folds_did_change(cx);
1748 clone.selections.clone_state(&self.selections);
1749 clone.scroll_manager.clone_state(&self.scroll_manager);
1750 clone.searchable = self.searchable;
1751 clone.read_only = self.read_only;
1752 clone
1753 }
1754
1755 pub fn new(
1756 mode: EditorMode,
1757 buffer: Entity<MultiBuffer>,
1758 project: Option<Entity<Project>>,
1759 window: &mut Window,
1760 cx: &mut Context<Self>,
1761 ) -> Self {
1762 Editor::new_internal(mode, buffer, project, None, window, cx)
1763 }
1764
1765 pub fn sticky_headers(&self, cx: &App) -> Option<Vec<OutlineItem<Anchor>>> {
1766 let multi_buffer = self.buffer().read(cx);
1767 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1768 let multi_buffer_visible_start = self
1769 .scroll_manager
1770 .anchor()
1771 .anchor
1772 .to_point(&multi_buffer_snapshot);
1773 let max_row = multi_buffer_snapshot.max_point().row;
1774
1775 let start_row = (multi_buffer_visible_start.row).min(max_row);
1776 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1777
1778 if let Some((excerpt_id, buffer_id, buffer)) = multi_buffer.read(cx).as_singleton() {
1779 let outline_items = buffer
1780 .outline_items_containing(
1781 Point::new(start_row, 0)..Point::new(end_row, 0),
1782 true,
1783 self.style().map(|style| style.syntax.as_ref()),
1784 )
1785 .into_iter()
1786 .map(|outline_item| OutlineItem {
1787 depth: outline_item.depth,
1788 range: Anchor::range_in_buffer(*excerpt_id, buffer_id, outline_item.range),
1789 source_range_for_text: Anchor::range_in_buffer(
1790 *excerpt_id,
1791 buffer_id,
1792 outline_item.source_range_for_text,
1793 ),
1794 text: outline_item.text,
1795 highlight_ranges: outline_item.highlight_ranges,
1796 name_ranges: outline_item.name_ranges,
1797 body_range: outline_item
1798 .body_range
1799 .map(|range| Anchor::range_in_buffer(*excerpt_id, buffer_id, range)),
1800 annotation_range: outline_item
1801 .annotation_range
1802 .map(|range| Anchor::range_in_buffer(*excerpt_id, buffer_id, range)),
1803 });
1804 return Some(outline_items.collect());
1805 }
1806
1807 None
1808 }
1809
1810 fn new_internal(
1811 mode: EditorMode,
1812 multi_buffer: Entity<MultiBuffer>,
1813 project: Option<Entity<Project>>,
1814 display_map: Option<Entity<DisplayMap>>,
1815 window: &mut Window,
1816 cx: &mut Context<Self>,
1817 ) -> Self {
1818 debug_assert!(
1819 display_map.is_none() || mode.is_minimap(),
1820 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1821 );
1822
1823 let full_mode = mode.is_full();
1824 let is_minimap = mode.is_minimap();
1825 let diagnostics_max_severity = if full_mode {
1826 EditorSettings::get_global(cx)
1827 .diagnostics_max_severity
1828 .unwrap_or(DiagnosticSeverity::Hint)
1829 } else {
1830 DiagnosticSeverity::Off
1831 };
1832 let style = window.text_style();
1833 let font_size = style.font_size.to_pixels(window.rem_size());
1834 let editor = cx.entity().downgrade();
1835 let fold_placeholder = FoldPlaceholder {
1836 constrain_width: false,
1837 render: Arc::new(move |fold_id, fold_range, cx| {
1838 let editor = editor.clone();
1839 div()
1840 .id(fold_id)
1841 .bg(cx.theme().colors().ghost_element_background)
1842 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1843 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1844 .rounded_xs()
1845 .size_full()
1846 .cursor_pointer()
1847 .child("⋯")
1848 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1849 .on_click(move |_, _window, cx| {
1850 editor
1851 .update(cx, |editor, cx| {
1852 editor.unfold_ranges(
1853 &[fold_range.start..fold_range.end],
1854 true,
1855 false,
1856 cx,
1857 );
1858 cx.stop_propagation();
1859 })
1860 .ok();
1861 })
1862 .into_any()
1863 }),
1864 merge_adjacent: true,
1865 ..FoldPlaceholder::default()
1866 };
1867 let display_map = display_map.unwrap_or_else(|| {
1868 cx.new(|cx| {
1869 DisplayMap::new(
1870 multi_buffer.clone(),
1871 style.font(),
1872 font_size,
1873 None,
1874 FILE_HEADER_HEIGHT,
1875 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1876 fold_placeholder,
1877 diagnostics_max_severity,
1878 cx,
1879 )
1880 })
1881 });
1882
1883 let selections = SelectionsCollection::new();
1884
1885 let blink_manager = cx.new(|cx| {
1886 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1887 if is_minimap {
1888 blink_manager.disable(cx);
1889 }
1890 blink_manager
1891 });
1892
1893 let soft_wrap_mode_override =
1894 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1895
1896 let mut project_subscriptions = Vec::new();
1897 if full_mode && let Some(project) = project.as_ref() {
1898 project_subscriptions.push(cx.subscribe_in(
1899 project,
1900 window,
1901 |editor, _, event, window, cx| match event {
1902 project::Event::RefreshCodeLens => {
1903 // we always query lens with actions, without storing them, always refreshing them
1904 }
1905 project::Event::RefreshInlayHints {
1906 server_id,
1907 request_id,
1908 } => {
1909 editor.refresh_inlay_hints(
1910 InlayHintRefreshReason::RefreshRequested {
1911 server_id: *server_id,
1912 request_id: *request_id,
1913 },
1914 cx,
1915 );
1916 }
1917 project::Event::LanguageServerRemoved(..) => {
1918 if editor.tasks_update_task.is_none() {
1919 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1920 }
1921 editor.registered_buffers.clear();
1922 editor.register_visible_buffers(cx);
1923 }
1924 project::Event::LanguageServerAdded(..) => {
1925 if editor.tasks_update_task.is_none() {
1926 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1927 }
1928 }
1929 project::Event::SnippetEdit(id, snippet_edits) => {
1930 // todo(lw): Non singletons
1931 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
1932 let snapshot = buffer.read(cx).snapshot();
1933 let focus_handle = editor.focus_handle(cx);
1934 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
1935 for (range, snippet) in snippet_edits {
1936 let buffer_range =
1937 language::range_from_lsp(*range).to_offset(&snapshot);
1938 editor
1939 .insert_snippet(
1940 &[MultiBufferOffset(buffer_range.start)
1941 ..MultiBufferOffset(buffer_range.end)],
1942 snippet.clone(),
1943 window,
1944 cx,
1945 )
1946 .ok();
1947 }
1948 }
1949 }
1950 }
1951 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1952 let buffer_id = *buffer_id;
1953 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1954 editor.register_buffer(buffer_id, cx);
1955 editor.update_lsp_data(Some(buffer_id), window, cx);
1956 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1957 refresh_linked_ranges(editor, window, cx);
1958 editor.refresh_code_actions(window, cx);
1959 editor.refresh_document_highlights(cx);
1960 }
1961 }
1962
1963 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
1964 let Some(workspace) = editor.workspace() else {
1965 return;
1966 };
1967 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1968 else {
1969 return;
1970 };
1971
1972 if active_editor.entity_id() == cx.entity_id() {
1973 let entity_id = cx.entity_id();
1974 workspace.update(cx, |this, cx| {
1975 this.panes_mut()
1976 .iter_mut()
1977 .filter(|pane| pane.entity_id() != entity_id)
1978 .for_each(|p| {
1979 p.update(cx, |pane, _| {
1980 pane.nav_history_mut().rename_item(
1981 entity_id,
1982 project_path.clone(),
1983 abs_path.clone().into(),
1984 );
1985 })
1986 });
1987 });
1988 let edited_buffers_already_open = {
1989 let other_editors: Vec<Entity<Editor>> = workspace
1990 .read(cx)
1991 .panes()
1992 .iter()
1993 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1994 .filter(|editor| editor.entity_id() != cx.entity_id())
1995 .collect();
1996
1997 transaction.0.keys().all(|buffer| {
1998 other_editors.iter().any(|editor| {
1999 let multi_buffer = editor.read(cx).buffer();
2000 multi_buffer.read(cx).is_singleton()
2001 && multi_buffer.read(cx).as_singleton().map_or(
2002 false,
2003 |singleton| {
2004 singleton.entity_id() == buffer.entity_id()
2005 },
2006 )
2007 })
2008 })
2009 };
2010 if !edited_buffers_already_open {
2011 let workspace = workspace.downgrade();
2012 let transaction = transaction.clone();
2013 cx.defer_in(window, move |_, window, cx| {
2014 cx.spawn_in(window, async move |editor, cx| {
2015 Self::open_project_transaction(
2016 &editor,
2017 workspace,
2018 transaction,
2019 "Rename".to_string(),
2020 cx,
2021 )
2022 .await
2023 .ok()
2024 })
2025 .detach();
2026 });
2027 }
2028 }
2029 }
2030
2031 _ => {}
2032 },
2033 ));
2034 if let Some(task_inventory) = project
2035 .read(cx)
2036 .task_store()
2037 .read(cx)
2038 .task_inventory()
2039 .cloned()
2040 {
2041 project_subscriptions.push(cx.observe_in(
2042 &task_inventory,
2043 window,
2044 |editor, _, window, cx| {
2045 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2046 },
2047 ));
2048 };
2049
2050 project_subscriptions.push(cx.subscribe_in(
2051 &project.read(cx).breakpoint_store(),
2052 window,
2053 |editor, _, event, window, cx| match event {
2054 BreakpointStoreEvent::ClearDebugLines => {
2055 editor.clear_row_highlights::<ActiveDebugLine>();
2056 editor.refresh_inline_values(cx);
2057 }
2058 BreakpointStoreEvent::SetDebugLine => {
2059 if editor.go_to_active_debug_line(window, cx) {
2060 cx.stop_propagation();
2061 }
2062
2063 editor.refresh_inline_values(cx);
2064 }
2065 _ => {}
2066 },
2067 ));
2068 let git_store = project.read(cx).git_store().clone();
2069 let project = project.clone();
2070 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2071 if let GitStoreEvent::RepositoryAdded = event {
2072 this.load_diff_task = Some(
2073 update_uncommitted_diff_for_buffer(
2074 cx.entity(),
2075 &project,
2076 this.buffer.read(cx).all_buffers(),
2077 this.buffer.clone(),
2078 cx,
2079 )
2080 .shared(),
2081 );
2082 }
2083 }));
2084 }
2085
2086 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2087
2088 let inlay_hint_settings =
2089 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2090 let focus_handle = cx.focus_handle();
2091 if !is_minimap {
2092 cx.on_focus(&focus_handle, window, Self::handle_focus)
2093 .detach();
2094 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2095 .detach();
2096 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2097 .detach();
2098 cx.on_blur(&focus_handle, window, Self::handle_blur)
2099 .detach();
2100 cx.observe_pending_input(window, Self::observe_pending_input)
2101 .detach();
2102 }
2103
2104 let show_indent_guides =
2105 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2106 Some(false)
2107 } else {
2108 None
2109 };
2110
2111 let breakpoint_store = match (&mode, project.as_ref()) {
2112 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2113 _ => None,
2114 };
2115
2116 let mut code_action_providers = Vec::new();
2117 let mut load_uncommitted_diff = None;
2118 if let Some(project) = project.clone() {
2119 load_uncommitted_diff = Some(
2120 update_uncommitted_diff_for_buffer(
2121 cx.entity(),
2122 &project,
2123 multi_buffer.read(cx).all_buffers(),
2124 multi_buffer.clone(),
2125 cx,
2126 )
2127 .shared(),
2128 );
2129 code_action_providers.push(Rc::new(project) as Rc<_>);
2130 }
2131
2132 let mut editor = Self {
2133 focus_handle,
2134 show_cursor_when_unfocused: false,
2135 last_focused_descendant: None,
2136 buffer: multi_buffer.clone(),
2137 display_map: display_map.clone(),
2138 placeholder_display_map: None,
2139 selections,
2140 scroll_manager: ScrollManager::new(cx),
2141 columnar_selection_state: None,
2142 add_selections_state: None,
2143 select_next_state: None,
2144 select_prev_state: None,
2145 selection_history: SelectionHistory::default(),
2146 defer_selection_effects: false,
2147 deferred_selection_effects_state: None,
2148 autoclose_regions: Vec::new(),
2149 snippet_stack: InvalidationStack::default(),
2150 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2151 ime_transaction: None,
2152 active_diagnostics: ActiveDiagnostic::None,
2153 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2154 inline_diagnostics_update: Task::ready(()),
2155 inline_diagnostics: Vec::new(),
2156 soft_wrap_mode_override,
2157 diagnostics_max_severity,
2158 hard_wrap: None,
2159 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2160 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2161 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2162 project,
2163 blink_manager: blink_manager.clone(),
2164 show_local_selections: true,
2165 show_scrollbars: ScrollbarAxes {
2166 horizontal: full_mode,
2167 vertical: full_mode,
2168 },
2169 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2170 offset_content: !matches!(mode, EditorMode::SingleLine),
2171 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2172 show_gutter: full_mode,
2173 show_line_numbers: (!full_mode).then_some(false),
2174 use_relative_line_numbers: None,
2175 disable_expand_excerpt_buttons: !full_mode,
2176 show_git_diff_gutter: None,
2177 show_code_actions: None,
2178 show_runnables: None,
2179 show_breakpoints: None,
2180 show_wrap_guides: None,
2181 show_indent_guides,
2182 highlight_order: 0,
2183 highlighted_rows: HashMap::default(),
2184 background_highlights: HashMap::default(),
2185 gutter_highlights: HashMap::default(),
2186 scrollbar_marker_state: ScrollbarMarkerState::default(),
2187 active_indent_guides_state: ActiveIndentGuidesState::default(),
2188 nav_history: None,
2189 context_menu: RefCell::new(None),
2190 context_menu_options: None,
2191 mouse_context_menu: None,
2192 completion_tasks: Vec::new(),
2193 inline_blame_popover: None,
2194 inline_blame_popover_show_task: None,
2195 signature_help_state: SignatureHelpState::default(),
2196 auto_signature_help: None,
2197 find_all_references_task_sources: Vec::new(),
2198 next_completion_id: 0,
2199 next_inlay_id: 0,
2200 code_action_providers,
2201 available_code_actions: None,
2202 code_actions_task: None,
2203 quick_selection_highlight_task: None,
2204 debounced_selection_highlight_task: None,
2205 document_highlights_task: None,
2206 linked_editing_range_task: None,
2207 pending_rename: None,
2208 searchable: !is_minimap,
2209 cursor_shape: EditorSettings::get_global(cx)
2210 .cursor_shape
2211 .unwrap_or_default(),
2212 current_line_highlight: None,
2213 autoindent_mode: Some(AutoindentMode::EachLine),
2214 collapse_matches: false,
2215 workspace: None,
2216 input_enabled: !is_minimap,
2217 use_modal_editing: full_mode,
2218 read_only: is_minimap,
2219 use_autoclose: true,
2220 use_auto_surround: true,
2221 auto_replace_emoji_shortcode: false,
2222 jsx_tag_auto_close_enabled_in_any_buffer: false,
2223 leader_id: None,
2224 remote_id: None,
2225 hover_state: HoverState::default(),
2226 pending_mouse_down: None,
2227 hovered_link_state: None,
2228 edit_prediction_provider: None,
2229 active_edit_prediction: None,
2230 stale_edit_prediction_in_menu: None,
2231 edit_prediction_preview: EditPredictionPreview::Inactive {
2232 released_too_fast: false,
2233 },
2234 inline_diagnostics_enabled: full_mode,
2235 diagnostics_enabled: full_mode,
2236 word_completions_enabled: full_mode,
2237 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2238 gutter_hovered: false,
2239 pixel_position_of_newest_cursor: None,
2240 last_bounds: None,
2241 last_position_map: None,
2242 expect_bounds_change: None,
2243 gutter_dimensions: GutterDimensions::default(),
2244 style: None,
2245 show_cursor_names: false,
2246 hovered_cursors: HashMap::default(),
2247 next_editor_action_id: EditorActionId::default(),
2248 editor_actions: Rc::default(),
2249 edit_predictions_hidden_for_vim_mode: false,
2250 show_edit_predictions_override: None,
2251 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2252 edit_prediction_settings: EditPredictionSettings::Disabled,
2253 edit_prediction_indent_conflict: false,
2254 edit_prediction_requires_modifier_in_indent_conflict: true,
2255 custom_context_menu: None,
2256 show_git_blame_gutter: false,
2257 show_git_blame_inline: false,
2258 show_selection_menu: None,
2259 show_git_blame_inline_delay_task: None,
2260 git_blame_inline_enabled: full_mode
2261 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2262 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2263 buffer_serialization: is_minimap.not().then(|| {
2264 BufferSerialization::new(
2265 ProjectSettings::get_global(cx)
2266 .session
2267 .restore_unsaved_buffers,
2268 )
2269 }),
2270 blame: None,
2271 blame_subscription: None,
2272 tasks: BTreeMap::default(),
2273
2274 breakpoint_store,
2275 gutter_breakpoint_indicator: (None, None),
2276 hovered_diff_hunk_row: None,
2277 _subscriptions: (!is_minimap)
2278 .then(|| {
2279 vec![
2280 cx.observe(&multi_buffer, Self::on_buffer_changed),
2281 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2282 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2283 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2284 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2285 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2286 cx.observe_window_activation(window, |editor, window, cx| {
2287 let active = window.is_window_active();
2288 editor.blink_manager.update(cx, |blink_manager, cx| {
2289 if active {
2290 blink_manager.enable(cx);
2291 } else {
2292 blink_manager.disable(cx);
2293 }
2294 });
2295 if active {
2296 editor.show_mouse_cursor(cx);
2297 }
2298 }),
2299 ]
2300 })
2301 .unwrap_or_default(),
2302 tasks_update_task: None,
2303 pull_diagnostics_task: Task::ready(()),
2304 colors: None,
2305 refresh_colors_task: Task::ready(()),
2306 inlay_hints: None,
2307 next_color_inlay_id: 0,
2308 post_scroll_update: Task::ready(()),
2309 linked_edit_ranges: Default::default(),
2310 in_project_search: false,
2311 previous_search_ranges: None,
2312 breadcrumb_header: None,
2313 focused_block: None,
2314 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2315 addons: HashMap::default(),
2316 registered_buffers: HashMap::default(),
2317 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2318 selection_mark_mode: false,
2319 toggle_fold_multiple_buffers: Task::ready(()),
2320 serialize_selections: Task::ready(()),
2321 serialize_folds: Task::ready(()),
2322 text_style_refinement: None,
2323 load_diff_task: load_uncommitted_diff,
2324 temporary_diff_override: false,
2325 mouse_cursor_hidden: false,
2326 minimap: None,
2327 hide_mouse_mode: EditorSettings::get_global(cx)
2328 .hide_mouse
2329 .unwrap_or_default(),
2330 change_list: ChangeList::new(),
2331 mode,
2332 selection_drag_state: SelectionDragState::None,
2333 folding_newlines: Task::ready(()),
2334 lookup_key: None,
2335 select_next_is_case_sensitive: None,
2336 };
2337
2338 if is_minimap {
2339 return editor;
2340 }
2341
2342 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2343 editor
2344 ._subscriptions
2345 .push(cx.observe(breakpoints, |_, _, cx| {
2346 cx.notify();
2347 }));
2348 }
2349 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2350 editor._subscriptions.extend(project_subscriptions);
2351
2352 editor._subscriptions.push(cx.subscribe_in(
2353 &cx.entity(),
2354 window,
2355 |editor, _, e: &EditorEvent, window, cx| match e {
2356 EditorEvent::ScrollPositionChanged { local, .. } => {
2357 if *local {
2358 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2359 editor.inline_blame_popover.take();
2360 let new_anchor = editor.scroll_manager.anchor();
2361 let snapshot = editor.snapshot(window, cx);
2362 editor.update_restoration_data(cx, move |data| {
2363 data.scroll_position = (
2364 new_anchor.top_row(snapshot.buffer_snapshot()),
2365 new_anchor.offset,
2366 );
2367 });
2368
2369 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2370 cx.background_executor()
2371 .timer(Duration::from_millis(50))
2372 .await;
2373 editor
2374 .update_in(cx, |editor, window, cx| {
2375 editor.register_visible_buffers(cx);
2376 editor.refresh_colors_for_visible_range(None, window, cx);
2377 editor.refresh_inlay_hints(
2378 InlayHintRefreshReason::NewLinesShown,
2379 cx,
2380 );
2381 })
2382 .ok();
2383 });
2384 }
2385 }
2386 EditorEvent::Edited { .. } => {
2387 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2388 .map(|vim_mode| vim_mode.0)
2389 .unwrap_or(false);
2390 if !vim_mode {
2391 let display_map = editor.display_snapshot(cx);
2392 let selections = editor.selections.all_adjusted_display(&display_map);
2393 let pop_state = editor
2394 .change_list
2395 .last()
2396 .map(|previous| {
2397 previous.len() == selections.len()
2398 && previous.iter().enumerate().all(|(ix, p)| {
2399 p.to_display_point(&display_map).row()
2400 == selections[ix].head().row()
2401 })
2402 })
2403 .unwrap_or(false);
2404 let new_positions = selections
2405 .into_iter()
2406 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2407 .collect();
2408 editor
2409 .change_list
2410 .push_to_change_list(pop_state, new_positions);
2411 }
2412 }
2413 _ => (),
2414 },
2415 ));
2416
2417 if let Some(dap_store) = editor
2418 .project
2419 .as_ref()
2420 .map(|project| project.read(cx).dap_store())
2421 {
2422 let weak_editor = cx.weak_entity();
2423
2424 editor
2425 ._subscriptions
2426 .push(
2427 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2428 let session_entity = cx.entity();
2429 weak_editor
2430 .update(cx, |editor, cx| {
2431 editor._subscriptions.push(
2432 cx.subscribe(&session_entity, Self::on_debug_session_event),
2433 );
2434 })
2435 .ok();
2436 }),
2437 );
2438
2439 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2440 editor
2441 ._subscriptions
2442 .push(cx.subscribe(&session, Self::on_debug_session_event));
2443 }
2444 }
2445
2446 // skip adding the initial selection to selection history
2447 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2448 editor.end_selection(window, cx);
2449 editor.selection_history.mode = SelectionHistoryMode::Normal;
2450
2451 editor.scroll_manager.show_scrollbars(window, cx);
2452 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2453
2454 if full_mode {
2455 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2456 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2457
2458 if editor.git_blame_inline_enabled {
2459 editor.start_git_blame_inline(false, window, cx);
2460 }
2461
2462 editor.go_to_active_debug_line(window, cx);
2463
2464 editor.minimap =
2465 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2466 editor.colors = Some(LspColorData::new(cx));
2467 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2468
2469 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2470 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2471 }
2472 editor.update_lsp_data(None, window, cx);
2473 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2474 }
2475
2476 editor
2477 }
2478
2479 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2480 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2481 }
2482
2483 pub fn deploy_mouse_context_menu(
2484 &mut self,
2485 position: gpui::Point<Pixels>,
2486 context_menu: Entity<ContextMenu>,
2487 window: &mut Window,
2488 cx: &mut Context<Self>,
2489 ) {
2490 self.mouse_context_menu = Some(MouseContextMenu::new(
2491 self,
2492 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2493 context_menu,
2494 window,
2495 cx,
2496 ));
2497 }
2498
2499 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2500 self.mouse_context_menu
2501 .as_ref()
2502 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2503 }
2504
2505 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2506 if self
2507 .selections
2508 .pending_anchor()
2509 .is_some_and(|pending_selection| {
2510 let snapshot = self.buffer().read(cx).snapshot(cx);
2511 pending_selection.range().includes(range, &snapshot)
2512 })
2513 {
2514 return true;
2515 }
2516
2517 self.selections
2518 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2519 .into_iter()
2520 .any(|selection| {
2521 // This is needed to cover a corner case, if we just check for an existing
2522 // selection in the fold range, having a cursor at the start of the fold
2523 // marks it as selected. Non-empty selections don't cause this.
2524 let length = selection.end - selection.start;
2525 length > 0
2526 })
2527 }
2528
2529 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2530 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2531 }
2532
2533 fn key_context_internal(
2534 &self,
2535 has_active_edit_prediction: bool,
2536 window: &mut Window,
2537 cx: &mut App,
2538 ) -> KeyContext {
2539 let mut key_context = KeyContext::new_with_defaults();
2540 key_context.add("Editor");
2541 let mode = match self.mode {
2542 EditorMode::SingleLine => "single_line",
2543 EditorMode::AutoHeight { .. } => "auto_height",
2544 EditorMode::Minimap { .. } => "minimap",
2545 EditorMode::Full { .. } => "full",
2546 };
2547
2548 if EditorSettings::jupyter_enabled(cx) {
2549 key_context.add("jupyter");
2550 }
2551
2552 key_context.set("mode", mode);
2553 if self.pending_rename.is_some() {
2554 key_context.add("renaming");
2555 }
2556
2557 if let Some(snippet_stack) = self.snippet_stack.last() {
2558 key_context.add("in_snippet");
2559
2560 if snippet_stack.active_index > 0 {
2561 key_context.add("has_previous_tabstop");
2562 }
2563
2564 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2565 key_context.add("has_next_tabstop");
2566 }
2567 }
2568
2569 match self.context_menu.borrow().as_ref() {
2570 Some(CodeContextMenu::Completions(menu)) => {
2571 if menu.visible() {
2572 key_context.add("menu");
2573 key_context.add("showing_completions");
2574 }
2575 }
2576 Some(CodeContextMenu::CodeActions(menu)) => {
2577 if menu.visible() {
2578 key_context.add("menu");
2579 key_context.add("showing_code_actions")
2580 }
2581 }
2582 None => {}
2583 }
2584
2585 if self.signature_help_state.has_multiple_signatures() {
2586 key_context.add("showing_signature_help");
2587 }
2588
2589 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2590 if !self.focus_handle(cx).contains_focused(window, cx)
2591 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2592 {
2593 for addon in self.addons.values() {
2594 addon.extend_key_context(&mut key_context, cx)
2595 }
2596 }
2597
2598 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2599 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2600 Some(
2601 file.full_path(cx)
2602 .extension()?
2603 .to_string_lossy()
2604 .into_owned(),
2605 )
2606 }) {
2607 key_context.set("extension", extension);
2608 }
2609 } else {
2610 key_context.add("multibuffer");
2611 }
2612
2613 if has_active_edit_prediction {
2614 if self.edit_prediction_in_conflict() {
2615 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2616 } else {
2617 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2618 key_context.add("copilot_suggestion");
2619 }
2620 }
2621
2622 if self.selection_mark_mode {
2623 key_context.add("selection_mode");
2624 }
2625
2626 let disjoint = self.selections.disjoint_anchors();
2627 let snapshot = self.snapshot(window, cx);
2628 let snapshot = snapshot.buffer_snapshot();
2629 if self.mode == EditorMode::SingleLine
2630 && let [selection] = disjoint
2631 && selection.start == selection.end
2632 && selection.end.to_offset(snapshot) == snapshot.len()
2633 {
2634 key_context.add("end_of_input");
2635 }
2636
2637 if self.has_any_expanded_diff_hunks(cx) {
2638 key_context.add("diffs_expanded");
2639 }
2640
2641 key_context
2642 }
2643
2644 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2645 self.last_bounds.as_ref()
2646 }
2647
2648 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2649 if self.mouse_cursor_hidden {
2650 self.mouse_cursor_hidden = false;
2651 cx.notify();
2652 }
2653 }
2654
2655 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2656 let hide_mouse_cursor = match origin {
2657 HideMouseCursorOrigin::TypingAction => {
2658 matches!(
2659 self.hide_mouse_mode,
2660 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2661 )
2662 }
2663 HideMouseCursorOrigin::MovementAction => {
2664 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2665 }
2666 };
2667 if self.mouse_cursor_hidden != hide_mouse_cursor {
2668 self.mouse_cursor_hidden = hide_mouse_cursor;
2669 cx.notify();
2670 }
2671 }
2672
2673 pub fn edit_prediction_in_conflict(&self) -> bool {
2674 if !self.show_edit_predictions_in_menu() {
2675 return false;
2676 }
2677
2678 let showing_completions = self
2679 .context_menu
2680 .borrow()
2681 .as_ref()
2682 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2683
2684 showing_completions
2685 || self.edit_prediction_requires_modifier()
2686 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2687 // bindings to insert tab characters.
2688 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2689 }
2690
2691 pub fn accept_edit_prediction_keybind(
2692 &self,
2693 accept_partial: bool,
2694 window: &mut Window,
2695 cx: &mut App,
2696 ) -> AcceptEditPredictionBinding {
2697 let key_context = self.key_context_internal(true, window, cx);
2698 let in_conflict = self.edit_prediction_in_conflict();
2699
2700 let bindings = if accept_partial {
2701 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2702 } else {
2703 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2704 };
2705
2706 // TODO: if the binding contains multiple keystrokes, display all of them, not
2707 // just the first one.
2708 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2709 !in_conflict
2710 || binding
2711 .keystrokes()
2712 .first()
2713 .is_some_and(|keystroke| keystroke.modifiers().modified())
2714 }))
2715 }
2716
2717 pub fn new_file(
2718 workspace: &mut Workspace,
2719 _: &workspace::NewFile,
2720 window: &mut Window,
2721 cx: &mut Context<Workspace>,
2722 ) {
2723 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2724 "Failed to create buffer",
2725 window,
2726 cx,
2727 |e, _, _| match e.error_code() {
2728 ErrorCode::RemoteUpgradeRequired => Some(format!(
2729 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2730 e.error_tag("required").unwrap_or("the latest version")
2731 )),
2732 _ => None,
2733 },
2734 );
2735 }
2736
2737 pub fn new_in_workspace(
2738 workspace: &mut Workspace,
2739 window: &mut Window,
2740 cx: &mut Context<Workspace>,
2741 ) -> Task<Result<Entity<Editor>>> {
2742 let project = workspace.project().clone();
2743 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2744
2745 cx.spawn_in(window, async move |workspace, cx| {
2746 let buffer = create.await?;
2747 workspace.update_in(cx, |workspace, window, cx| {
2748 let editor =
2749 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2750 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2751 editor
2752 })
2753 })
2754 }
2755
2756 fn new_file_vertical(
2757 workspace: &mut Workspace,
2758 _: &workspace::NewFileSplitVertical,
2759 window: &mut Window,
2760 cx: &mut Context<Workspace>,
2761 ) {
2762 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2763 }
2764
2765 fn new_file_horizontal(
2766 workspace: &mut Workspace,
2767 _: &workspace::NewFileSplitHorizontal,
2768 window: &mut Window,
2769 cx: &mut Context<Workspace>,
2770 ) {
2771 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2772 }
2773
2774 fn new_file_split(
2775 workspace: &mut Workspace,
2776 action: &workspace::NewFileSplit,
2777 window: &mut Window,
2778 cx: &mut Context<Workspace>,
2779 ) {
2780 Self::new_file_in_direction(workspace, action.0, window, cx)
2781 }
2782
2783 fn new_file_in_direction(
2784 workspace: &mut Workspace,
2785 direction: SplitDirection,
2786 window: &mut Window,
2787 cx: &mut Context<Workspace>,
2788 ) {
2789 let project = workspace.project().clone();
2790 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2791
2792 cx.spawn_in(window, async move |workspace, cx| {
2793 let buffer = create.await?;
2794 workspace.update_in(cx, move |workspace, window, cx| {
2795 workspace.split_item(
2796 direction,
2797 Box::new(
2798 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2799 ),
2800 window,
2801 cx,
2802 )
2803 })?;
2804 anyhow::Ok(())
2805 })
2806 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2807 match e.error_code() {
2808 ErrorCode::RemoteUpgradeRequired => Some(format!(
2809 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2810 e.error_tag("required").unwrap_or("the latest version")
2811 )),
2812 _ => None,
2813 }
2814 });
2815 }
2816
2817 pub fn leader_id(&self) -> Option<CollaboratorId> {
2818 self.leader_id
2819 }
2820
2821 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2822 &self.buffer
2823 }
2824
2825 pub fn project(&self) -> Option<&Entity<Project>> {
2826 self.project.as_ref()
2827 }
2828
2829 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2830 self.workspace.as_ref()?.0.upgrade()
2831 }
2832
2833 /// Returns the workspace serialization ID if this editor should be serialized.
2834 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2835 self.workspace
2836 .as_ref()
2837 .filter(|_| self.should_serialize_buffer())
2838 .and_then(|workspace| workspace.1)
2839 }
2840
2841 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2842 self.buffer().read(cx).title(cx)
2843 }
2844
2845 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2846 let git_blame_gutter_max_author_length = self
2847 .render_git_blame_gutter(cx)
2848 .then(|| {
2849 if let Some(blame) = self.blame.as_ref() {
2850 let max_author_length =
2851 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2852 Some(max_author_length)
2853 } else {
2854 None
2855 }
2856 })
2857 .flatten();
2858
2859 EditorSnapshot {
2860 mode: self.mode.clone(),
2861 show_gutter: self.show_gutter,
2862 show_line_numbers: self.show_line_numbers,
2863 show_git_diff_gutter: self.show_git_diff_gutter,
2864 show_code_actions: self.show_code_actions,
2865 show_runnables: self.show_runnables,
2866 show_breakpoints: self.show_breakpoints,
2867 git_blame_gutter_max_author_length,
2868 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2869 placeholder_display_snapshot: self
2870 .placeholder_display_map
2871 .as_ref()
2872 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2873 scroll_anchor: self.scroll_manager.anchor(),
2874 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2875 is_focused: self.focus_handle.is_focused(window),
2876 current_line_highlight: self
2877 .current_line_highlight
2878 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2879 gutter_hovered: self.gutter_hovered,
2880 }
2881 }
2882
2883 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2884 self.buffer.read(cx).language_at(point, cx)
2885 }
2886
2887 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2888 self.buffer.read(cx).read(cx).file_at(point).cloned()
2889 }
2890
2891 pub fn active_excerpt(
2892 &self,
2893 cx: &App,
2894 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2895 self.buffer
2896 .read(cx)
2897 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2898 }
2899
2900 pub fn mode(&self) -> &EditorMode {
2901 &self.mode
2902 }
2903
2904 pub fn set_mode(&mut self, mode: EditorMode) {
2905 self.mode = mode;
2906 }
2907
2908 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2909 self.collaboration_hub.as_deref()
2910 }
2911
2912 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2913 self.collaboration_hub = Some(hub);
2914 }
2915
2916 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2917 self.in_project_search = in_project_search;
2918 }
2919
2920 pub fn set_custom_context_menu(
2921 &mut self,
2922 f: impl 'static
2923 + Fn(
2924 &mut Self,
2925 DisplayPoint,
2926 &mut Window,
2927 &mut Context<Self>,
2928 ) -> Option<Entity<ui::ContextMenu>>,
2929 ) {
2930 self.custom_context_menu = Some(Box::new(f))
2931 }
2932
2933 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2934 self.completion_provider = provider;
2935 }
2936
2937 #[cfg(any(test, feature = "test-support"))]
2938 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2939 self.completion_provider.clone()
2940 }
2941
2942 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2943 self.semantics_provider.clone()
2944 }
2945
2946 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2947 self.semantics_provider = provider;
2948 }
2949
2950 pub fn set_edit_prediction_provider<T>(
2951 &mut self,
2952 provider: Option<Entity<T>>,
2953 window: &mut Window,
2954 cx: &mut Context<Self>,
2955 ) where
2956 T: EditPredictionProvider,
2957 {
2958 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2959 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2960 if this.focus_handle.is_focused(window) {
2961 this.update_visible_edit_prediction(window, cx);
2962 }
2963 }),
2964 provider: Arc::new(provider),
2965 });
2966 self.update_edit_prediction_settings(cx);
2967 self.refresh_edit_prediction(false, false, window, cx);
2968 }
2969
2970 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2971 self.placeholder_display_map
2972 .as_ref()
2973 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2974 }
2975
2976 pub fn set_placeholder_text(
2977 &mut self,
2978 placeholder_text: &str,
2979 window: &mut Window,
2980 cx: &mut Context<Self>,
2981 ) {
2982 let multibuffer = cx
2983 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2984
2985 let style = window.text_style();
2986
2987 self.placeholder_display_map = Some(cx.new(|cx| {
2988 DisplayMap::new(
2989 multibuffer,
2990 style.font(),
2991 style.font_size.to_pixels(window.rem_size()),
2992 None,
2993 FILE_HEADER_HEIGHT,
2994 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2995 Default::default(),
2996 DiagnosticSeverity::Off,
2997 cx,
2998 )
2999 }));
3000 cx.notify();
3001 }
3002
3003 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3004 self.cursor_shape = cursor_shape;
3005
3006 // Disrupt blink for immediate user feedback that the cursor shape has changed
3007 self.blink_manager.update(cx, BlinkManager::show_cursor);
3008
3009 cx.notify();
3010 }
3011
3012 pub fn set_current_line_highlight(
3013 &mut self,
3014 current_line_highlight: Option<CurrentLineHighlight>,
3015 ) {
3016 self.current_line_highlight = current_line_highlight;
3017 }
3018
3019 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3020 self.collapse_matches = collapse_matches;
3021 }
3022
3023 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3024 if self.collapse_matches {
3025 return range.start..range.start;
3026 }
3027 range.clone()
3028 }
3029
3030 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3031 self.display_map.read(cx).clip_at_line_ends
3032 }
3033
3034 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3035 if self.display_map.read(cx).clip_at_line_ends != clip {
3036 self.display_map
3037 .update(cx, |map, _| map.clip_at_line_ends = clip);
3038 }
3039 }
3040
3041 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3042 self.input_enabled = input_enabled;
3043 }
3044
3045 pub fn set_edit_predictions_hidden_for_vim_mode(
3046 &mut self,
3047 hidden: bool,
3048 window: &mut Window,
3049 cx: &mut Context<Self>,
3050 ) {
3051 if hidden != self.edit_predictions_hidden_for_vim_mode {
3052 self.edit_predictions_hidden_for_vim_mode = hidden;
3053 if hidden {
3054 self.update_visible_edit_prediction(window, cx);
3055 } else {
3056 self.refresh_edit_prediction(true, false, window, cx);
3057 }
3058 }
3059 }
3060
3061 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3062 self.menu_edit_predictions_policy = value;
3063 }
3064
3065 pub fn set_autoindent(&mut self, autoindent: bool) {
3066 if autoindent {
3067 self.autoindent_mode = Some(AutoindentMode::EachLine);
3068 } else {
3069 self.autoindent_mode = None;
3070 }
3071 }
3072
3073 pub fn read_only(&self, cx: &App) -> bool {
3074 self.read_only || self.buffer.read(cx).read_only()
3075 }
3076
3077 pub fn set_read_only(&mut self, read_only: bool) {
3078 self.read_only = read_only;
3079 }
3080
3081 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3082 self.use_autoclose = autoclose;
3083 }
3084
3085 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3086 self.use_auto_surround = auto_surround;
3087 }
3088
3089 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3090 self.auto_replace_emoji_shortcode = auto_replace;
3091 }
3092
3093 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3094 self.buffer_serialization = should_serialize.then(|| {
3095 BufferSerialization::new(
3096 ProjectSettings::get_global(cx)
3097 .session
3098 .restore_unsaved_buffers,
3099 )
3100 })
3101 }
3102
3103 fn should_serialize_buffer(&self) -> bool {
3104 self.buffer_serialization.is_some()
3105 }
3106
3107 pub fn toggle_edit_predictions(
3108 &mut self,
3109 _: &ToggleEditPrediction,
3110 window: &mut Window,
3111 cx: &mut Context<Self>,
3112 ) {
3113 if self.show_edit_predictions_override.is_some() {
3114 self.set_show_edit_predictions(None, window, cx);
3115 } else {
3116 let show_edit_predictions = !self.edit_predictions_enabled();
3117 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3118 }
3119 }
3120
3121 pub fn set_show_edit_predictions(
3122 &mut self,
3123 show_edit_predictions: Option<bool>,
3124 window: &mut Window,
3125 cx: &mut Context<Self>,
3126 ) {
3127 self.show_edit_predictions_override = show_edit_predictions;
3128 self.update_edit_prediction_settings(cx);
3129
3130 if let Some(false) = show_edit_predictions {
3131 self.discard_edit_prediction(false, cx);
3132 } else {
3133 self.refresh_edit_prediction(false, true, window, cx);
3134 }
3135 }
3136
3137 fn edit_predictions_disabled_in_scope(
3138 &self,
3139 buffer: &Entity<Buffer>,
3140 buffer_position: language::Anchor,
3141 cx: &App,
3142 ) -> bool {
3143 let snapshot = buffer.read(cx).snapshot();
3144 let settings = snapshot.settings_at(buffer_position, cx);
3145
3146 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3147 return false;
3148 };
3149
3150 scope.override_name().is_some_and(|scope_name| {
3151 settings
3152 .edit_predictions_disabled_in
3153 .iter()
3154 .any(|s| s == scope_name)
3155 })
3156 }
3157
3158 pub fn set_use_modal_editing(&mut self, to: bool) {
3159 self.use_modal_editing = to;
3160 }
3161
3162 pub fn use_modal_editing(&self) -> bool {
3163 self.use_modal_editing
3164 }
3165
3166 fn selections_did_change(
3167 &mut self,
3168 local: bool,
3169 old_cursor_position: &Anchor,
3170 effects: SelectionEffects,
3171 window: &mut Window,
3172 cx: &mut Context<Self>,
3173 ) {
3174 window.invalidate_character_coordinates();
3175
3176 // Copy selections to primary selection buffer
3177 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3178 if local {
3179 let selections = self
3180 .selections
3181 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3182 let buffer_handle = self.buffer.read(cx).read(cx);
3183
3184 let mut text = String::new();
3185 for (index, selection) in selections.iter().enumerate() {
3186 let text_for_selection = buffer_handle
3187 .text_for_range(selection.start..selection.end)
3188 .collect::<String>();
3189
3190 text.push_str(&text_for_selection);
3191 if index != selections.len() - 1 {
3192 text.push('\n');
3193 }
3194 }
3195
3196 if !text.is_empty() {
3197 cx.write_to_primary(ClipboardItem::new_string(text));
3198 }
3199 }
3200
3201 let selection_anchors = self.selections.disjoint_anchors_arc();
3202
3203 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3204 self.buffer.update(cx, |buffer, cx| {
3205 buffer.set_active_selections(
3206 &selection_anchors,
3207 self.selections.line_mode(),
3208 self.cursor_shape,
3209 cx,
3210 )
3211 });
3212 }
3213 let display_map = self
3214 .display_map
3215 .update(cx, |display_map, cx| display_map.snapshot(cx));
3216 let buffer = display_map.buffer_snapshot();
3217 if self.selections.count() == 1 {
3218 self.add_selections_state = None;
3219 }
3220 self.select_next_state = None;
3221 self.select_prev_state = None;
3222 self.select_syntax_node_history.try_clear();
3223 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3224 self.snippet_stack.invalidate(&selection_anchors, buffer);
3225 self.take_rename(false, window, cx);
3226
3227 let newest_selection = self.selections.newest_anchor();
3228 let new_cursor_position = newest_selection.head();
3229 let selection_start = newest_selection.start;
3230
3231 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3232 self.push_to_nav_history(
3233 *old_cursor_position,
3234 Some(new_cursor_position.to_point(buffer)),
3235 false,
3236 effects.nav_history == Some(true),
3237 cx,
3238 );
3239 }
3240
3241 if local {
3242 if let Some(buffer_id) = new_cursor_position.buffer_id {
3243 self.register_buffer(buffer_id, cx);
3244 }
3245
3246 let mut context_menu = self.context_menu.borrow_mut();
3247 let completion_menu = match context_menu.as_ref() {
3248 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3249 Some(CodeContextMenu::CodeActions(_)) => {
3250 *context_menu = None;
3251 None
3252 }
3253 None => None,
3254 };
3255 let completion_position = completion_menu.map(|menu| menu.initial_position);
3256 drop(context_menu);
3257
3258 if effects.completions
3259 && let Some(completion_position) = completion_position
3260 {
3261 let start_offset = selection_start.to_offset(buffer);
3262 let position_matches = start_offset == completion_position.to_offset(buffer);
3263 let continue_showing = if position_matches {
3264 if self.snippet_stack.is_empty() {
3265 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3266 == Some(CharKind::Word)
3267 } else {
3268 // Snippet choices can be shown even when the cursor is in whitespace.
3269 // Dismissing the menu with actions like backspace is handled by
3270 // invalidation regions.
3271 true
3272 }
3273 } else {
3274 false
3275 };
3276
3277 if continue_showing {
3278 self.open_or_update_completions_menu(None, None, false, window, cx);
3279 } else {
3280 self.hide_context_menu(window, cx);
3281 }
3282 }
3283
3284 hide_hover(self, cx);
3285
3286 if old_cursor_position.to_display_point(&display_map).row()
3287 != new_cursor_position.to_display_point(&display_map).row()
3288 {
3289 self.available_code_actions.take();
3290 }
3291 self.refresh_code_actions(window, cx);
3292 self.refresh_document_highlights(cx);
3293 refresh_linked_ranges(self, window, cx);
3294
3295 self.refresh_selected_text_highlights(false, window, cx);
3296 self.refresh_matching_bracket_highlights(window, cx);
3297 self.update_visible_edit_prediction(window, cx);
3298 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3299 self.inline_blame_popover.take();
3300 if self.git_blame_inline_enabled {
3301 self.start_inline_blame_timer(window, cx);
3302 }
3303 }
3304
3305 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3306 cx.emit(EditorEvent::SelectionsChanged { local });
3307
3308 let selections = &self.selections.disjoint_anchors_arc();
3309 if selections.len() == 1 {
3310 cx.emit(SearchEvent::ActiveMatchChanged)
3311 }
3312 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3313 let inmemory_selections = selections
3314 .iter()
3315 .map(|s| {
3316 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3317 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3318 })
3319 .collect();
3320 self.update_restoration_data(cx, |data| {
3321 data.selections = inmemory_selections;
3322 });
3323
3324 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3325 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3326 {
3327 let snapshot = self.buffer().read(cx).snapshot(cx);
3328 let selections = selections.clone();
3329 let background_executor = cx.background_executor().clone();
3330 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3331 self.serialize_selections = cx.background_spawn(async move {
3332 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3333 let db_selections = selections
3334 .iter()
3335 .map(|selection| {
3336 (
3337 selection.start.to_offset(&snapshot).0,
3338 selection.end.to_offset(&snapshot).0,
3339 )
3340 })
3341 .collect();
3342
3343 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3344 .await
3345 .with_context(|| {
3346 format!(
3347 "persisting editor selections for editor {editor_id}, \
3348 workspace {workspace_id:?}"
3349 )
3350 })
3351 .log_err();
3352 });
3353 }
3354 }
3355
3356 cx.notify();
3357 }
3358
3359 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3360 use text::ToOffset as _;
3361 use text::ToPoint as _;
3362
3363 if self.mode.is_minimap()
3364 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3365 {
3366 return;
3367 }
3368
3369 if !self.buffer().read(cx).is_singleton() {
3370 return;
3371 }
3372
3373 let display_snapshot = self
3374 .display_map
3375 .update(cx, |display_map, cx| display_map.snapshot(cx));
3376 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3377 return;
3378 };
3379 let inmemory_folds = display_snapshot
3380 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3381 .map(|fold| {
3382 fold.range.start.text_anchor.to_point(&snapshot)
3383 ..fold.range.end.text_anchor.to_point(&snapshot)
3384 })
3385 .collect();
3386 self.update_restoration_data(cx, |data| {
3387 data.folds = inmemory_folds;
3388 });
3389
3390 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3391 return;
3392 };
3393 let background_executor = cx.background_executor().clone();
3394 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3395 let db_folds = display_snapshot
3396 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3397 .map(|fold| {
3398 (
3399 fold.range.start.text_anchor.to_offset(&snapshot),
3400 fold.range.end.text_anchor.to_offset(&snapshot),
3401 )
3402 })
3403 .collect();
3404 self.serialize_folds = cx.background_spawn(async move {
3405 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3406 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3407 .await
3408 .with_context(|| {
3409 format!(
3410 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3411 )
3412 })
3413 .log_err();
3414 });
3415 }
3416
3417 pub fn sync_selections(
3418 &mut self,
3419 other: Entity<Editor>,
3420 cx: &mut Context<Self>,
3421 ) -> gpui::Subscription {
3422 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3423 if !other_selections.is_empty() {
3424 self.selections
3425 .change_with(&self.display_snapshot(cx), |selections| {
3426 selections.select_anchors(other_selections);
3427 });
3428 }
3429
3430 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3431 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3432 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3433 if other_selections.is_empty() {
3434 return;
3435 }
3436 let snapshot = this.display_snapshot(cx);
3437 this.selections.change_with(&snapshot, |selections| {
3438 selections.select_anchors(other_selections);
3439 });
3440 }
3441 });
3442
3443 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3444 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3445 let these_selections = this.selections.disjoint_anchors().to_vec();
3446 if these_selections.is_empty() {
3447 return;
3448 }
3449 other.update(cx, |other_editor, cx| {
3450 let snapshot = other_editor.display_snapshot(cx);
3451 other_editor
3452 .selections
3453 .change_with(&snapshot, |selections| {
3454 selections.select_anchors(these_selections);
3455 })
3456 });
3457 }
3458 });
3459
3460 Subscription::join(other_subscription, this_subscription)
3461 }
3462
3463 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3464 if self.buffer().read(cx).is_singleton() {
3465 return;
3466 }
3467 let snapshot = self.buffer.read(cx).snapshot(cx);
3468 let buffer_ids: HashSet<BufferId> = self
3469 .selections
3470 .disjoint_anchor_ranges()
3471 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3472 .collect();
3473 for buffer_id in buffer_ids {
3474 self.unfold_buffer(buffer_id, cx);
3475 }
3476 }
3477
3478 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3479 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3480 /// effects of selection change occur at the end of the transaction.
3481 pub fn change_selections<R>(
3482 &mut self,
3483 effects: SelectionEffects,
3484 window: &mut Window,
3485 cx: &mut Context<Self>,
3486 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3487 ) -> R {
3488 let snapshot = self.display_snapshot(cx);
3489 if let Some(state) = &mut self.deferred_selection_effects_state {
3490 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3491 state.effects.completions = effects.completions;
3492 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3493 let (changed, result) = self.selections.change_with(&snapshot, change);
3494 state.changed |= changed;
3495 return result;
3496 }
3497 let mut state = DeferredSelectionEffectsState {
3498 changed: false,
3499 effects,
3500 old_cursor_position: self.selections.newest_anchor().head(),
3501 history_entry: SelectionHistoryEntry {
3502 selections: self.selections.disjoint_anchors_arc(),
3503 select_next_state: self.select_next_state.clone(),
3504 select_prev_state: self.select_prev_state.clone(),
3505 add_selections_state: self.add_selections_state.clone(),
3506 },
3507 };
3508 let (changed, result) = self.selections.change_with(&snapshot, change);
3509 state.changed = state.changed || changed;
3510 if self.defer_selection_effects {
3511 self.deferred_selection_effects_state = Some(state);
3512 } else {
3513 self.apply_selection_effects(state, window, cx);
3514 }
3515 result
3516 }
3517
3518 /// Defers the effects of selection change, so that the effects of multiple calls to
3519 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3520 /// to selection history and the state of popovers based on selection position aren't
3521 /// erroneously updated.
3522 pub fn with_selection_effects_deferred<R>(
3523 &mut self,
3524 window: &mut Window,
3525 cx: &mut Context<Self>,
3526 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3527 ) -> R {
3528 let already_deferred = self.defer_selection_effects;
3529 self.defer_selection_effects = true;
3530 let result = update(self, window, cx);
3531 if !already_deferred {
3532 self.defer_selection_effects = false;
3533 if let Some(state) = self.deferred_selection_effects_state.take() {
3534 self.apply_selection_effects(state, window, cx);
3535 }
3536 }
3537 result
3538 }
3539
3540 fn apply_selection_effects(
3541 &mut self,
3542 state: DeferredSelectionEffectsState,
3543 window: &mut Window,
3544 cx: &mut Context<Self>,
3545 ) {
3546 if state.changed {
3547 self.selection_history.push(state.history_entry);
3548
3549 if let Some(autoscroll) = state.effects.scroll {
3550 self.request_autoscroll(autoscroll, cx);
3551 }
3552
3553 let old_cursor_position = &state.old_cursor_position;
3554
3555 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3556
3557 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3558 self.show_signature_help(&ShowSignatureHelp, window, cx);
3559 }
3560 }
3561 }
3562
3563 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3564 where
3565 I: IntoIterator<Item = (Range<S>, T)>,
3566 S: ToOffset,
3567 T: Into<Arc<str>>,
3568 {
3569 if self.read_only(cx) {
3570 return;
3571 }
3572
3573 self.buffer
3574 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3575 }
3576
3577 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3578 where
3579 I: IntoIterator<Item = (Range<S>, T)>,
3580 S: ToOffset,
3581 T: Into<Arc<str>>,
3582 {
3583 if self.read_only(cx) {
3584 return;
3585 }
3586
3587 self.buffer.update(cx, |buffer, cx| {
3588 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3589 });
3590 }
3591
3592 pub fn edit_with_block_indent<I, S, T>(
3593 &mut self,
3594 edits: I,
3595 original_indent_columns: Vec<Option<u32>>,
3596 cx: &mut Context<Self>,
3597 ) where
3598 I: IntoIterator<Item = (Range<S>, T)>,
3599 S: ToOffset,
3600 T: Into<Arc<str>>,
3601 {
3602 if self.read_only(cx) {
3603 return;
3604 }
3605
3606 self.buffer.update(cx, |buffer, cx| {
3607 buffer.edit(
3608 edits,
3609 Some(AutoindentMode::Block {
3610 original_indent_columns,
3611 }),
3612 cx,
3613 )
3614 });
3615 }
3616
3617 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3618 self.hide_context_menu(window, cx);
3619
3620 match phase {
3621 SelectPhase::Begin {
3622 position,
3623 add,
3624 click_count,
3625 } => self.begin_selection(position, add, click_count, window, cx),
3626 SelectPhase::BeginColumnar {
3627 position,
3628 goal_column,
3629 reset,
3630 mode,
3631 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3632 SelectPhase::Extend {
3633 position,
3634 click_count,
3635 } => self.extend_selection(position, click_count, window, cx),
3636 SelectPhase::Update {
3637 position,
3638 goal_column,
3639 scroll_delta,
3640 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3641 SelectPhase::End => self.end_selection(window, cx),
3642 }
3643 }
3644
3645 fn extend_selection(
3646 &mut self,
3647 position: DisplayPoint,
3648 click_count: usize,
3649 window: &mut Window,
3650 cx: &mut Context<Self>,
3651 ) {
3652 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3653 let tail = self
3654 .selections
3655 .newest::<MultiBufferOffset>(&display_map)
3656 .tail();
3657 let click_count = click_count.max(match self.selections.select_mode() {
3658 SelectMode::Character => 1,
3659 SelectMode::Word(_) => 2,
3660 SelectMode::Line(_) => 3,
3661 SelectMode::All => 4,
3662 });
3663 self.begin_selection(position, false, click_count, window, cx);
3664
3665 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3666
3667 let current_selection = match self.selections.select_mode() {
3668 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3669 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3670 };
3671
3672 let mut pending_selection = self
3673 .selections
3674 .pending_anchor()
3675 .cloned()
3676 .expect("extend_selection not called with pending selection");
3677
3678 if pending_selection
3679 .start
3680 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3681 == Ordering::Greater
3682 {
3683 pending_selection.start = current_selection.start;
3684 }
3685 if pending_selection
3686 .end
3687 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3688 == Ordering::Less
3689 {
3690 pending_selection.end = current_selection.end;
3691 pending_selection.reversed = true;
3692 }
3693
3694 let mut pending_mode = self.selections.pending_mode().unwrap();
3695 match &mut pending_mode {
3696 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3697 _ => {}
3698 }
3699
3700 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3701 SelectionEffects::scroll(Autoscroll::fit())
3702 } else {
3703 SelectionEffects::no_scroll()
3704 };
3705
3706 self.change_selections(effects, window, cx, |s| {
3707 s.set_pending(pending_selection.clone(), pending_mode);
3708 s.set_is_extending(true);
3709 });
3710 }
3711
3712 fn begin_selection(
3713 &mut self,
3714 position: DisplayPoint,
3715 add: bool,
3716 click_count: usize,
3717 window: &mut Window,
3718 cx: &mut Context<Self>,
3719 ) {
3720 if !self.focus_handle.is_focused(window) {
3721 self.last_focused_descendant = None;
3722 window.focus(&self.focus_handle);
3723 }
3724
3725 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3726 let buffer = display_map.buffer_snapshot();
3727 let position = display_map.clip_point(position, Bias::Left);
3728
3729 let start;
3730 let end;
3731 let mode;
3732 let mut auto_scroll;
3733 match click_count {
3734 1 => {
3735 start = buffer.anchor_before(position.to_point(&display_map));
3736 end = start;
3737 mode = SelectMode::Character;
3738 auto_scroll = true;
3739 }
3740 2 => {
3741 let position = display_map
3742 .clip_point(position, Bias::Left)
3743 .to_offset(&display_map, Bias::Left);
3744 let (range, _) = buffer.surrounding_word(position, None);
3745 start = buffer.anchor_before(range.start);
3746 end = buffer.anchor_before(range.end);
3747 mode = SelectMode::Word(start..end);
3748 auto_scroll = true;
3749 }
3750 3 => {
3751 let position = display_map
3752 .clip_point(position, Bias::Left)
3753 .to_point(&display_map);
3754 let line_start = display_map.prev_line_boundary(position).0;
3755 let next_line_start = buffer.clip_point(
3756 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3757 Bias::Left,
3758 );
3759 start = buffer.anchor_before(line_start);
3760 end = buffer.anchor_before(next_line_start);
3761 mode = SelectMode::Line(start..end);
3762 auto_scroll = true;
3763 }
3764 _ => {
3765 start = buffer.anchor_before(MultiBufferOffset(0));
3766 end = buffer.anchor_before(buffer.len());
3767 mode = SelectMode::All;
3768 auto_scroll = false;
3769 }
3770 }
3771 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3772
3773 let point_to_delete: Option<usize> = {
3774 let selected_points: Vec<Selection<Point>> =
3775 self.selections.disjoint_in_range(start..end, &display_map);
3776
3777 if !add || click_count > 1 {
3778 None
3779 } else if !selected_points.is_empty() {
3780 Some(selected_points[0].id)
3781 } else {
3782 let clicked_point_already_selected =
3783 self.selections.disjoint_anchors().iter().find(|selection| {
3784 selection.start.to_point(buffer) == start.to_point(buffer)
3785 || selection.end.to_point(buffer) == end.to_point(buffer)
3786 });
3787
3788 clicked_point_already_selected.map(|selection| selection.id)
3789 }
3790 };
3791
3792 let selections_count = self.selections.count();
3793 let effects = if auto_scroll {
3794 SelectionEffects::default()
3795 } else {
3796 SelectionEffects::no_scroll()
3797 };
3798
3799 self.change_selections(effects, window, cx, |s| {
3800 if let Some(point_to_delete) = point_to_delete {
3801 s.delete(point_to_delete);
3802
3803 if selections_count == 1 {
3804 s.set_pending_anchor_range(start..end, mode);
3805 }
3806 } else {
3807 if !add {
3808 s.clear_disjoint();
3809 }
3810
3811 s.set_pending_anchor_range(start..end, mode);
3812 }
3813 });
3814 }
3815
3816 fn begin_columnar_selection(
3817 &mut self,
3818 position: DisplayPoint,
3819 goal_column: u32,
3820 reset: bool,
3821 mode: ColumnarMode,
3822 window: &mut Window,
3823 cx: &mut Context<Self>,
3824 ) {
3825 if !self.focus_handle.is_focused(window) {
3826 self.last_focused_descendant = None;
3827 window.focus(&self.focus_handle);
3828 }
3829
3830 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3831
3832 if reset {
3833 let pointer_position = display_map
3834 .buffer_snapshot()
3835 .anchor_before(position.to_point(&display_map));
3836
3837 self.change_selections(
3838 SelectionEffects::scroll(Autoscroll::newest()),
3839 window,
3840 cx,
3841 |s| {
3842 s.clear_disjoint();
3843 s.set_pending_anchor_range(
3844 pointer_position..pointer_position,
3845 SelectMode::Character,
3846 );
3847 },
3848 );
3849 };
3850
3851 let tail = self.selections.newest::<Point>(&display_map).tail();
3852 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3853 self.columnar_selection_state = match mode {
3854 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3855 selection_tail: selection_anchor,
3856 display_point: if reset {
3857 if position.column() != goal_column {
3858 Some(DisplayPoint::new(position.row(), goal_column))
3859 } else {
3860 None
3861 }
3862 } else {
3863 None
3864 },
3865 }),
3866 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3867 selection_tail: selection_anchor,
3868 }),
3869 };
3870
3871 if !reset {
3872 self.select_columns(position, goal_column, &display_map, window, cx);
3873 }
3874 }
3875
3876 fn update_selection(
3877 &mut self,
3878 position: DisplayPoint,
3879 goal_column: u32,
3880 scroll_delta: gpui::Point<f32>,
3881 window: &mut Window,
3882 cx: &mut Context<Self>,
3883 ) {
3884 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3885
3886 if self.columnar_selection_state.is_some() {
3887 self.select_columns(position, goal_column, &display_map, window, cx);
3888 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3889 let buffer = display_map.buffer_snapshot();
3890 let head;
3891 let tail;
3892 let mode = self.selections.pending_mode().unwrap();
3893 match &mode {
3894 SelectMode::Character => {
3895 head = position.to_point(&display_map);
3896 tail = pending.tail().to_point(buffer);
3897 }
3898 SelectMode::Word(original_range) => {
3899 let offset = display_map
3900 .clip_point(position, Bias::Left)
3901 .to_offset(&display_map, Bias::Left);
3902 let original_range = original_range.to_offset(buffer);
3903
3904 let head_offset = if buffer.is_inside_word(offset, None)
3905 || original_range.contains(&offset)
3906 {
3907 let (word_range, _) = buffer.surrounding_word(offset, None);
3908 if word_range.start < original_range.start {
3909 word_range.start
3910 } else {
3911 word_range.end
3912 }
3913 } else {
3914 offset
3915 };
3916
3917 head = head_offset.to_point(buffer);
3918 if head_offset <= original_range.start {
3919 tail = original_range.end.to_point(buffer);
3920 } else {
3921 tail = original_range.start.to_point(buffer);
3922 }
3923 }
3924 SelectMode::Line(original_range) => {
3925 let original_range = original_range.to_point(display_map.buffer_snapshot());
3926
3927 let position = display_map
3928 .clip_point(position, Bias::Left)
3929 .to_point(&display_map);
3930 let line_start = display_map.prev_line_boundary(position).0;
3931 let next_line_start = buffer.clip_point(
3932 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3933 Bias::Left,
3934 );
3935
3936 if line_start < original_range.start {
3937 head = line_start
3938 } else {
3939 head = next_line_start
3940 }
3941
3942 if head <= original_range.start {
3943 tail = original_range.end;
3944 } else {
3945 tail = original_range.start;
3946 }
3947 }
3948 SelectMode::All => {
3949 return;
3950 }
3951 };
3952
3953 if head < tail {
3954 pending.start = buffer.anchor_before(head);
3955 pending.end = buffer.anchor_before(tail);
3956 pending.reversed = true;
3957 } else {
3958 pending.start = buffer.anchor_before(tail);
3959 pending.end = buffer.anchor_before(head);
3960 pending.reversed = false;
3961 }
3962
3963 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3964 s.set_pending(pending.clone(), mode);
3965 });
3966 } else {
3967 log::error!("update_selection dispatched with no pending selection");
3968 return;
3969 }
3970
3971 self.apply_scroll_delta(scroll_delta, window, cx);
3972 cx.notify();
3973 }
3974
3975 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3976 self.columnar_selection_state.take();
3977 if let Some(pending_mode) = self.selections.pending_mode() {
3978 let selections = self
3979 .selections
3980 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3981 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3982 s.select(selections);
3983 s.clear_pending();
3984 if s.is_extending() {
3985 s.set_is_extending(false);
3986 } else {
3987 s.set_select_mode(pending_mode);
3988 }
3989 });
3990 }
3991 }
3992
3993 fn select_columns(
3994 &mut self,
3995 head: DisplayPoint,
3996 goal_column: u32,
3997 display_map: &DisplaySnapshot,
3998 window: &mut Window,
3999 cx: &mut Context<Self>,
4000 ) {
4001 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4002 return;
4003 };
4004
4005 let tail = match columnar_state {
4006 ColumnarSelectionState::FromMouse {
4007 selection_tail,
4008 display_point,
4009 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4010 ColumnarSelectionState::FromSelection { selection_tail } => {
4011 selection_tail.to_display_point(display_map)
4012 }
4013 };
4014
4015 let start_row = cmp::min(tail.row(), head.row());
4016 let end_row = cmp::max(tail.row(), head.row());
4017 let start_column = cmp::min(tail.column(), goal_column);
4018 let end_column = cmp::max(tail.column(), goal_column);
4019 let reversed = start_column < tail.column();
4020
4021 let selection_ranges = (start_row.0..=end_row.0)
4022 .map(DisplayRow)
4023 .filter_map(|row| {
4024 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4025 || start_column <= display_map.line_len(row))
4026 && !display_map.is_block_line(row)
4027 {
4028 let start = display_map
4029 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4030 .to_point(display_map);
4031 let end = display_map
4032 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4033 .to_point(display_map);
4034 if reversed {
4035 Some(end..start)
4036 } else {
4037 Some(start..end)
4038 }
4039 } else {
4040 None
4041 }
4042 })
4043 .collect::<Vec<_>>();
4044 if selection_ranges.is_empty() {
4045 return;
4046 }
4047
4048 let ranges = match columnar_state {
4049 ColumnarSelectionState::FromMouse { .. } => {
4050 let mut non_empty_ranges = selection_ranges
4051 .iter()
4052 .filter(|selection_range| selection_range.start != selection_range.end)
4053 .peekable();
4054 if non_empty_ranges.peek().is_some() {
4055 non_empty_ranges.cloned().collect()
4056 } else {
4057 selection_ranges
4058 }
4059 }
4060 _ => selection_ranges,
4061 };
4062
4063 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4064 s.select_ranges(ranges);
4065 });
4066 cx.notify();
4067 }
4068
4069 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4070 self.selections
4071 .all_adjusted(snapshot)
4072 .iter()
4073 .any(|selection| !selection.is_empty())
4074 }
4075
4076 pub fn has_pending_nonempty_selection(&self) -> bool {
4077 let pending_nonempty_selection = match self.selections.pending_anchor() {
4078 Some(Selection { start, end, .. }) => start != end,
4079 None => false,
4080 };
4081
4082 pending_nonempty_selection
4083 || (self.columnar_selection_state.is_some()
4084 && self.selections.disjoint_anchors().len() > 1)
4085 }
4086
4087 pub fn has_pending_selection(&self) -> bool {
4088 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4089 }
4090
4091 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4092 self.selection_mark_mode = false;
4093 self.selection_drag_state = SelectionDragState::None;
4094
4095 if self.dismiss_menus_and_popups(true, window, cx) {
4096 cx.notify();
4097 return;
4098 }
4099 if self.clear_expanded_diff_hunks(cx) {
4100 cx.notify();
4101 return;
4102 }
4103 if self.show_git_blame_gutter {
4104 self.show_git_blame_gutter = false;
4105 cx.notify();
4106 return;
4107 }
4108
4109 if self.mode.is_full()
4110 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4111 {
4112 cx.notify();
4113 return;
4114 }
4115
4116 cx.propagate();
4117 }
4118
4119 pub fn dismiss_menus_and_popups(
4120 &mut self,
4121 is_user_requested: bool,
4122 window: &mut Window,
4123 cx: &mut Context<Self>,
4124 ) -> bool {
4125 if self.take_rename(false, window, cx).is_some() {
4126 return true;
4127 }
4128
4129 if self.hide_blame_popover(true, cx) {
4130 return true;
4131 }
4132
4133 if hide_hover(self, cx) {
4134 return true;
4135 }
4136
4137 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
4138 return true;
4139 }
4140
4141 if self.hide_context_menu(window, cx).is_some() {
4142 return true;
4143 }
4144
4145 if self.mouse_context_menu.take().is_some() {
4146 return true;
4147 }
4148
4149 if is_user_requested && self.discard_edit_prediction(true, cx) {
4150 return true;
4151 }
4152
4153 if self.snippet_stack.pop().is_some() {
4154 return true;
4155 }
4156
4157 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4158 self.dismiss_diagnostics(cx);
4159 return true;
4160 }
4161
4162 false
4163 }
4164
4165 fn linked_editing_ranges_for(
4166 &self,
4167 selection: Range<text::Anchor>,
4168 cx: &App,
4169 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4170 if self.linked_edit_ranges.is_empty() {
4171 return None;
4172 }
4173 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4174 selection.end.buffer_id.and_then(|end_buffer_id| {
4175 if selection.start.buffer_id != Some(end_buffer_id) {
4176 return None;
4177 }
4178 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4179 let snapshot = buffer.read(cx).snapshot();
4180 self.linked_edit_ranges
4181 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4182 .map(|ranges| (ranges, snapshot, buffer))
4183 })?;
4184 use text::ToOffset as TO;
4185 // find offset from the start of current range to current cursor position
4186 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4187
4188 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4189 let start_difference = start_offset - start_byte_offset;
4190 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4191 let end_difference = end_offset - start_byte_offset;
4192 // Current range has associated linked ranges.
4193 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4194 for range in linked_ranges.iter() {
4195 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4196 let end_offset = start_offset + end_difference;
4197 let start_offset = start_offset + start_difference;
4198 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4199 continue;
4200 }
4201 if self.selections.disjoint_anchor_ranges().any(|s| {
4202 if s.start.buffer_id != selection.start.buffer_id
4203 || s.end.buffer_id != selection.end.buffer_id
4204 {
4205 return false;
4206 }
4207 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4208 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4209 }) {
4210 continue;
4211 }
4212 let start = buffer_snapshot.anchor_after(start_offset);
4213 let end = buffer_snapshot.anchor_after(end_offset);
4214 linked_edits
4215 .entry(buffer.clone())
4216 .or_default()
4217 .push(start..end);
4218 }
4219 Some(linked_edits)
4220 }
4221
4222 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4223 let text: Arc<str> = text.into();
4224
4225 if self.read_only(cx) {
4226 return;
4227 }
4228
4229 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4230
4231 self.unfold_buffers_with_selections(cx);
4232
4233 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4234 let mut bracket_inserted = false;
4235 let mut edits = Vec::new();
4236 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4237 let mut new_selections = Vec::with_capacity(selections.len());
4238 let mut new_autoclose_regions = Vec::new();
4239 let snapshot = self.buffer.read(cx).read(cx);
4240 let mut clear_linked_edit_ranges = false;
4241
4242 for (selection, autoclose_region) in
4243 self.selections_with_autoclose_regions(selections, &snapshot)
4244 {
4245 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4246 // Determine if the inserted text matches the opening or closing
4247 // bracket of any of this language's bracket pairs.
4248 let mut bracket_pair = None;
4249 let mut is_bracket_pair_start = false;
4250 let mut is_bracket_pair_end = false;
4251 if !text.is_empty() {
4252 let mut bracket_pair_matching_end = None;
4253 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4254 // and they are removing the character that triggered IME popup.
4255 for (pair, enabled) in scope.brackets() {
4256 if !pair.close && !pair.surround {
4257 continue;
4258 }
4259
4260 if enabled && pair.start.ends_with(text.as_ref()) {
4261 let prefix_len = pair.start.len() - text.len();
4262 let preceding_text_matches_prefix = prefix_len == 0
4263 || (selection.start.column >= (prefix_len as u32)
4264 && snapshot.contains_str_at(
4265 Point::new(
4266 selection.start.row,
4267 selection.start.column - (prefix_len as u32),
4268 ),
4269 &pair.start[..prefix_len],
4270 ));
4271 if preceding_text_matches_prefix {
4272 bracket_pair = Some(pair.clone());
4273 is_bracket_pair_start = true;
4274 break;
4275 }
4276 }
4277 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4278 {
4279 // take first bracket pair matching end, but don't break in case a later bracket
4280 // pair matches start
4281 bracket_pair_matching_end = Some(pair.clone());
4282 }
4283 }
4284 if let Some(end) = bracket_pair_matching_end
4285 && bracket_pair.is_none()
4286 {
4287 bracket_pair = Some(end);
4288 is_bracket_pair_end = true;
4289 }
4290 }
4291
4292 if let Some(bracket_pair) = bracket_pair {
4293 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4294 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4295 let auto_surround =
4296 self.use_auto_surround && snapshot_settings.use_auto_surround;
4297 if selection.is_empty() {
4298 if is_bracket_pair_start {
4299 // If the inserted text is a suffix of an opening bracket and the
4300 // selection is preceded by the rest of the opening bracket, then
4301 // insert the closing bracket.
4302 let following_text_allows_autoclose = snapshot
4303 .chars_at(selection.start)
4304 .next()
4305 .is_none_or(|c| scope.should_autoclose_before(c));
4306
4307 let preceding_text_allows_autoclose = selection.start.column == 0
4308 || snapshot
4309 .reversed_chars_at(selection.start)
4310 .next()
4311 .is_none_or(|c| {
4312 bracket_pair.start != bracket_pair.end
4313 || !snapshot
4314 .char_classifier_at(selection.start)
4315 .is_word(c)
4316 });
4317
4318 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4319 && bracket_pair.start.len() == 1
4320 {
4321 let target = bracket_pair.start.chars().next().unwrap();
4322 let current_line_count = snapshot
4323 .reversed_chars_at(selection.start)
4324 .take_while(|&c| c != '\n')
4325 .filter(|&c| c == target)
4326 .count();
4327 current_line_count % 2 == 1
4328 } else {
4329 false
4330 };
4331
4332 if autoclose
4333 && bracket_pair.close
4334 && following_text_allows_autoclose
4335 && preceding_text_allows_autoclose
4336 && !is_closing_quote
4337 {
4338 let anchor = snapshot.anchor_before(selection.end);
4339 new_selections.push((selection.map(|_| anchor), text.len()));
4340 new_autoclose_regions.push((
4341 anchor,
4342 text.len(),
4343 selection.id,
4344 bracket_pair.clone(),
4345 ));
4346 edits.push((
4347 selection.range(),
4348 format!("{}{}", text, bracket_pair.end).into(),
4349 ));
4350 bracket_inserted = true;
4351 continue;
4352 }
4353 }
4354
4355 if let Some(region) = autoclose_region {
4356 // If the selection is followed by an auto-inserted closing bracket,
4357 // then don't insert that closing bracket again; just move the selection
4358 // past the closing bracket.
4359 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4360 && text.as_ref() == region.pair.end.as_str()
4361 && snapshot.contains_str_at(region.range.end, text.as_ref());
4362 if should_skip {
4363 let anchor = snapshot.anchor_after(selection.end);
4364 new_selections
4365 .push((selection.map(|_| anchor), region.pair.end.len()));
4366 continue;
4367 }
4368 }
4369
4370 let always_treat_brackets_as_autoclosed = snapshot
4371 .language_settings_at(selection.start, cx)
4372 .always_treat_brackets_as_autoclosed;
4373 if always_treat_brackets_as_autoclosed
4374 && is_bracket_pair_end
4375 && snapshot.contains_str_at(selection.end, text.as_ref())
4376 {
4377 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4378 // and the inserted text is a closing bracket and the selection is followed
4379 // by the closing bracket then move the selection past the closing bracket.
4380 let anchor = snapshot.anchor_after(selection.end);
4381 new_selections.push((selection.map(|_| anchor), text.len()));
4382 continue;
4383 }
4384 }
4385 // If an opening bracket is 1 character long and is typed while
4386 // text is selected, then surround that text with the bracket pair.
4387 else if auto_surround
4388 && bracket_pair.surround
4389 && is_bracket_pair_start
4390 && bracket_pair.start.chars().count() == 1
4391 {
4392 edits.push((selection.start..selection.start, text.clone()));
4393 edits.push((
4394 selection.end..selection.end,
4395 bracket_pair.end.as_str().into(),
4396 ));
4397 bracket_inserted = true;
4398 new_selections.push((
4399 Selection {
4400 id: selection.id,
4401 start: snapshot.anchor_after(selection.start),
4402 end: snapshot.anchor_before(selection.end),
4403 reversed: selection.reversed,
4404 goal: selection.goal,
4405 },
4406 0,
4407 ));
4408 continue;
4409 }
4410 }
4411 }
4412
4413 if self.auto_replace_emoji_shortcode
4414 && selection.is_empty()
4415 && text.as_ref().ends_with(':')
4416 && let Some(possible_emoji_short_code) =
4417 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4418 && !possible_emoji_short_code.is_empty()
4419 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4420 {
4421 let emoji_shortcode_start = Point::new(
4422 selection.start.row,
4423 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4424 );
4425
4426 // Remove shortcode from buffer
4427 edits.push((
4428 emoji_shortcode_start..selection.start,
4429 "".to_string().into(),
4430 ));
4431 new_selections.push((
4432 Selection {
4433 id: selection.id,
4434 start: snapshot.anchor_after(emoji_shortcode_start),
4435 end: snapshot.anchor_before(selection.start),
4436 reversed: selection.reversed,
4437 goal: selection.goal,
4438 },
4439 0,
4440 ));
4441
4442 // Insert emoji
4443 let selection_start_anchor = snapshot.anchor_after(selection.start);
4444 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4445 edits.push((selection.start..selection.end, emoji.to_string().into()));
4446
4447 continue;
4448 }
4449
4450 // If not handling any auto-close operation, then just replace the selected
4451 // text with the given input and move the selection to the end of the
4452 // newly inserted text.
4453 let anchor = snapshot.anchor_after(selection.end);
4454 if !self.linked_edit_ranges.is_empty() {
4455 let start_anchor = snapshot.anchor_before(selection.start);
4456
4457 let is_word_char = text.chars().next().is_none_or(|char| {
4458 let classifier = snapshot
4459 .char_classifier_at(start_anchor.to_offset(&snapshot))
4460 .scope_context(Some(CharScopeContext::LinkedEdit));
4461 classifier.is_word(char)
4462 });
4463
4464 if is_word_char {
4465 if let Some(ranges) = self
4466 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4467 {
4468 for (buffer, edits) in ranges {
4469 linked_edits
4470 .entry(buffer.clone())
4471 .or_default()
4472 .extend(edits.into_iter().map(|range| (range, text.clone())));
4473 }
4474 }
4475 } else {
4476 clear_linked_edit_ranges = true;
4477 }
4478 }
4479
4480 new_selections.push((selection.map(|_| anchor), 0));
4481 edits.push((selection.start..selection.end, text.clone()));
4482 }
4483
4484 drop(snapshot);
4485
4486 self.transact(window, cx, |this, window, cx| {
4487 if clear_linked_edit_ranges {
4488 this.linked_edit_ranges.clear();
4489 }
4490 let initial_buffer_versions =
4491 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4492
4493 this.buffer.update(cx, |buffer, cx| {
4494 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4495 });
4496 for (buffer, edits) in linked_edits {
4497 buffer.update(cx, |buffer, cx| {
4498 let snapshot = buffer.snapshot();
4499 let edits = edits
4500 .into_iter()
4501 .map(|(range, text)| {
4502 use text::ToPoint as TP;
4503 let end_point = TP::to_point(&range.end, &snapshot);
4504 let start_point = TP::to_point(&range.start, &snapshot);
4505 (start_point..end_point, text)
4506 })
4507 .sorted_by_key(|(range, _)| range.start);
4508 buffer.edit(edits, None, cx);
4509 })
4510 }
4511 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4512 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4513 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4514 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4515 new_anchor_selections,
4516 &map,
4517 )
4518 .zip(new_selection_deltas)
4519 .map(|(selection, delta)| Selection {
4520 id: selection.id,
4521 start: selection.start + delta,
4522 end: selection.end + delta,
4523 reversed: selection.reversed,
4524 goal: SelectionGoal::None,
4525 })
4526 .collect::<Vec<_>>();
4527
4528 let mut i = 0;
4529 for (position, delta, selection_id, pair) in new_autoclose_regions {
4530 let position = position.to_offset(map.buffer_snapshot()) + delta;
4531 let start = map.buffer_snapshot().anchor_before(position);
4532 let end = map.buffer_snapshot().anchor_after(position);
4533 while let Some(existing_state) = this.autoclose_regions.get(i) {
4534 match existing_state
4535 .range
4536 .start
4537 .cmp(&start, map.buffer_snapshot())
4538 {
4539 Ordering::Less => i += 1,
4540 Ordering::Greater => break,
4541 Ordering::Equal => {
4542 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4543 Ordering::Less => i += 1,
4544 Ordering::Equal => break,
4545 Ordering::Greater => break,
4546 }
4547 }
4548 }
4549 }
4550 this.autoclose_regions.insert(
4551 i,
4552 AutocloseRegion {
4553 selection_id,
4554 range: start..end,
4555 pair,
4556 },
4557 );
4558 }
4559
4560 let had_active_edit_prediction = this.has_active_edit_prediction();
4561 this.change_selections(
4562 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4563 window,
4564 cx,
4565 |s| s.select(new_selections),
4566 );
4567
4568 if !bracket_inserted
4569 && let Some(on_type_format_task) =
4570 this.trigger_on_type_formatting(text.to_string(), window, cx)
4571 {
4572 on_type_format_task.detach_and_log_err(cx);
4573 }
4574
4575 let editor_settings = EditorSettings::get_global(cx);
4576 if bracket_inserted
4577 && (editor_settings.auto_signature_help
4578 || editor_settings.show_signature_help_after_edits)
4579 {
4580 this.show_signature_help(&ShowSignatureHelp, window, cx);
4581 }
4582
4583 let trigger_in_words =
4584 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4585 if this.hard_wrap.is_some() {
4586 let latest: Range<Point> = this.selections.newest(&map).range();
4587 if latest.is_empty()
4588 && this
4589 .buffer()
4590 .read(cx)
4591 .snapshot(cx)
4592 .line_len(MultiBufferRow(latest.start.row))
4593 == latest.start.column
4594 {
4595 this.rewrap_impl(
4596 RewrapOptions {
4597 override_language_settings: true,
4598 preserve_existing_whitespace: true,
4599 },
4600 cx,
4601 )
4602 }
4603 }
4604 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4605 refresh_linked_ranges(this, window, cx);
4606 this.refresh_edit_prediction(true, false, window, cx);
4607 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4608 });
4609 }
4610
4611 fn find_possible_emoji_shortcode_at_position(
4612 snapshot: &MultiBufferSnapshot,
4613 position: Point,
4614 ) -> Option<String> {
4615 let mut chars = Vec::new();
4616 let mut found_colon = false;
4617 for char in snapshot.reversed_chars_at(position).take(100) {
4618 // Found a possible emoji shortcode in the middle of the buffer
4619 if found_colon {
4620 if char.is_whitespace() {
4621 chars.reverse();
4622 return Some(chars.iter().collect());
4623 }
4624 // If the previous character is not a whitespace, we are in the middle of a word
4625 // and we only want to complete the shortcode if the word is made up of other emojis
4626 let mut containing_word = String::new();
4627 for ch in snapshot
4628 .reversed_chars_at(position)
4629 .skip(chars.len() + 1)
4630 .take(100)
4631 {
4632 if ch.is_whitespace() {
4633 break;
4634 }
4635 containing_word.push(ch);
4636 }
4637 let containing_word = containing_word.chars().rev().collect::<String>();
4638 if util::word_consists_of_emojis(containing_word.as_str()) {
4639 chars.reverse();
4640 return Some(chars.iter().collect());
4641 }
4642 }
4643
4644 if char.is_whitespace() || !char.is_ascii() {
4645 return None;
4646 }
4647 if char == ':' {
4648 found_colon = true;
4649 } else {
4650 chars.push(char);
4651 }
4652 }
4653 // Found a possible emoji shortcode at the beginning of the buffer
4654 chars.reverse();
4655 Some(chars.iter().collect())
4656 }
4657
4658 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4659 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4660 self.transact(window, cx, |this, window, cx| {
4661 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4662 let selections = this
4663 .selections
4664 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4665 let multi_buffer = this.buffer.read(cx);
4666 let buffer = multi_buffer.snapshot(cx);
4667 selections
4668 .iter()
4669 .map(|selection| {
4670 let start_point = selection.start.to_point(&buffer);
4671 let mut existing_indent =
4672 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4673 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4674 let start = selection.start;
4675 let end = selection.end;
4676 let selection_is_empty = start == end;
4677 let language_scope = buffer.language_scope_at(start);
4678 let (
4679 comment_delimiter,
4680 doc_delimiter,
4681 insert_extra_newline,
4682 indent_on_newline,
4683 indent_on_extra_newline,
4684 ) = if let Some(language) = &language_scope {
4685 let mut insert_extra_newline =
4686 insert_extra_newline_brackets(&buffer, start..end, language)
4687 || insert_extra_newline_tree_sitter(&buffer, start..end);
4688
4689 // Comment extension on newline is allowed only for cursor selections
4690 let comment_delimiter = maybe!({
4691 if !selection_is_empty {
4692 return None;
4693 }
4694
4695 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4696 return None;
4697 }
4698
4699 let delimiters = language.line_comment_prefixes();
4700 let max_len_of_delimiter =
4701 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4702 let (snapshot, range) =
4703 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4704
4705 let num_of_whitespaces = snapshot
4706 .chars_for_range(range.clone())
4707 .take_while(|c| c.is_whitespace())
4708 .count();
4709 let comment_candidate = snapshot
4710 .chars_for_range(range.clone())
4711 .skip(num_of_whitespaces)
4712 .take(max_len_of_delimiter)
4713 .collect::<String>();
4714 let (delimiter, trimmed_len) = delimiters
4715 .iter()
4716 .filter_map(|delimiter| {
4717 let prefix = delimiter.trim_end();
4718 if comment_candidate.starts_with(prefix) {
4719 Some((delimiter, prefix.len()))
4720 } else {
4721 None
4722 }
4723 })
4724 .max_by_key(|(_, len)| *len)?;
4725
4726 if let Some(BlockCommentConfig {
4727 start: block_start, ..
4728 }) = language.block_comment()
4729 {
4730 let block_start_trimmed = block_start.trim_end();
4731 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4732 let line_content = snapshot
4733 .chars_for_range(range)
4734 .skip(num_of_whitespaces)
4735 .take(block_start_trimmed.len())
4736 .collect::<String>();
4737
4738 if line_content.starts_with(block_start_trimmed) {
4739 return None;
4740 }
4741 }
4742 }
4743
4744 let cursor_is_placed_after_comment_marker =
4745 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4746 if cursor_is_placed_after_comment_marker {
4747 Some(delimiter.clone())
4748 } else {
4749 None
4750 }
4751 });
4752
4753 let mut indent_on_newline = IndentSize::spaces(0);
4754 let mut indent_on_extra_newline = IndentSize::spaces(0);
4755
4756 let doc_delimiter = maybe!({
4757 if !selection_is_empty {
4758 return None;
4759 }
4760
4761 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4762 return None;
4763 }
4764
4765 let BlockCommentConfig {
4766 start: start_tag,
4767 end: end_tag,
4768 prefix: delimiter,
4769 tab_size: len,
4770 } = language.documentation_comment()?;
4771 let is_within_block_comment = buffer
4772 .language_scope_at(start_point)
4773 .is_some_and(|scope| scope.override_name() == Some("comment"));
4774 if !is_within_block_comment {
4775 return None;
4776 }
4777
4778 let (snapshot, range) =
4779 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4780
4781 let num_of_whitespaces = snapshot
4782 .chars_for_range(range.clone())
4783 .take_while(|c| c.is_whitespace())
4784 .count();
4785
4786 // 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.
4787 let column = start_point.column;
4788 let cursor_is_after_start_tag = {
4789 let start_tag_len = start_tag.len();
4790 let start_tag_line = snapshot
4791 .chars_for_range(range.clone())
4792 .skip(num_of_whitespaces)
4793 .take(start_tag_len)
4794 .collect::<String>();
4795 if start_tag_line.starts_with(start_tag.as_ref()) {
4796 num_of_whitespaces + start_tag_len <= column as usize
4797 } else {
4798 false
4799 }
4800 };
4801
4802 let cursor_is_after_delimiter = {
4803 let delimiter_trim = delimiter.trim_end();
4804 let delimiter_line = snapshot
4805 .chars_for_range(range.clone())
4806 .skip(num_of_whitespaces)
4807 .take(delimiter_trim.len())
4808 .collect::<String>();
4809 if delimiter_line.starts_with(delimiter_trim) {
4810 num_of_whitespaces + delimiter_trim.len() <= column as usize
4811 } else {
4812 false
4813 }
4814 };
4815
4816 let cursor_is_before_end_tag_if_exists = {
4817 let mut char_position = 0u32;
4818 let mut end_tag_offset = None;
4819
4820 'outer: for chunk in snapshot.text_for_range(range) {
4821 if let Some(byte_pos) = chunk.find(&**end_tag) {
4822 let chars_before_match =
4823 chunk[..byte_pos].chars().count() as u32;
4824 end_tag_offset =
4825 Some(char_position + chars_before_match);
4826 break 'outer;
4827 }
4828 char_position += chunk.chars().count() as u32;
4829 }
4830
4831 if let Some(end_tag_offset) = end_tag_offset {
4832 let cursor_is_before_end_tag = column <= end_tag_offset;
4833 if cursor_is_after_start_tag {
4834 if cursor_is_before_end_tag {
4835 insert_extra_newline = true;
4836 }
4837 let cursor_is_at_start_of_end_tag =
4838 column == end_tag_offset;
4839 if cursor_is_at_start_of_end_tag {
4840 indent_on_extra_newline.len = *len;
4841 }
4842 }
4843 cursor_is_before_end_tag
4844 } else {
4845 true
4846 }
4847 };
4848
4849 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4850 && cursor_is_before_end_tag_if_exists
4851 {
4852 if cursor_is_after_start_tag {
4853 indent_on_newline.len = *len;
4854 }
4855 Some(delimiter.clone())
4856 } else {
4857 None
4858 }
4859 });
4860
4861 (
4862 comment_delimiter,
4863 doc_delimiter,
4864 insert_extra_newline,
4865 indent_on_newline,
4866 indent_on_extra_newline,
4867 )
4868 } else {
4869 (
4870 None,
4871 None,
4872 false,
4873 IndentSize::default(),
4874 IndentSize::default(),
4875 )
4876 };
4877
4878 let prevent_auto_indent = doc_delimiter.is_some();
4879 let delimiter = comment_delimiter.or(doc_delimiter);
4880
4881 let capacity_for_delimiter =
4882 delimiter.as_deref().map(str::len).unwrap_or_default();
4883 let mut new_text = String::with_capacity(
4884 1 + capacity_for_delimiter
4885 + existing_indent.len as usize
4886 + indent_on_newline.len as usize
4887 + indent_on_extra_newline.len as usize,
4888 );
4889 new_text.push('\n');
4890 new_text.extend(existing_indent.chars());
4891 new_text.extend(indent_on_newline.chars());
4892
4893 if let Some(delimiter) = &delimiter {
4894 new_text.push_str(delimiter);
4895 }
4896
4897 if insert_extra_newline {
4898 new_text.push('\n');
4899 new_text.extend(existing_indent.chars());
4900 new_text.extend(indent_on_extra_newline.chars());
4901 }
4902
4903 let anchor = buffer.anchor_after(end);
4904 let new_selection = selection.map(|_| anchor);
4905 (
4906 ((start..end, new_text), prevent_auto_indent),
4907 (insert_extra_newline, new_selection),
4908 )
4909 })
4910 .unzip()
4911 };
4912
4913 let mut auto_indent_edits = Vec::new();
4914 let mut edits = Vec::new();
4915 for (edit, prevent_auto_indent) in edits_with_flags {
4916 if prevent_auto_indent {
4917 edits.push(edit);
4918 } else {
4919 auto_indent_edits.push(edit);
4920 }
4921 }
4922 if !edits.is_empty() {
4923 this.edit(edits, cx);
4924 }
4925 if !auto_indent_edits.is_empty() {
4926 this.edit_with_autoindent(auto_indent_edits, cx);
4927 }
4928
4929 let buffer = this.buffer.read(cx).snapshot(cx);
4930 let new_selections = selection_info
4931 .into_iter()
4932 .map(|(extra_newline_inserted, new_selection)| {
4933 let mut cursor = new_selection.end.to_point(&buffer);
4934 if extra_newline_inserted {
4935 cursor.row -= 1;
4936 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4937 }
4938 new_selection.map(|_| cursor)
4939 })
4940 .collect();
4941
4942 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4943 this.refresh_edit_prediction(true, false, window, cx);
4944 });
4945 }
4946
4947 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4948 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4949
4950 let buffer = self.buffer.read(cx);
4951 let snapshot = buffer.snapshot(cx);
4952
4953 let mut edits = Vec::new();
4954 let mut rows = Vec::new();
4955
4956 for (rows_inserted, selection) in self
4957 .selections
4958 .all_adjusted(&self.display_snapshot(cx))
4959 .into_iter()
4960 .enumerate()
4961 {
4962 let cursor = selection.head();
4963 let row = cursor.row;
4964
4965 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4966
4967 let newline = "\n".to_string();
4968 edits.push((start_of_line..start_of_line, newline));
4969
4970 rows.push(row + rows_inserted as u32);
4971 }
4972
4973 self.transact(window, cx, |editor, window, cx| {
4974 editor.edit(edits, cx);
4975
4976 editor.change_selections(Default::default(), window, cx, |s| {
4977 let mut index = 0;
4978 s.move_cursors_with(|map, _, _| {
4979 let row = rows[index];
4980 index += 1;
4981
4982 let point = Point::new(row, 0);
4983 let boundary = map.next_line_boundary(point).1;
4984 let clipped = map.clip_point(boundary, Bias::Left);
4985
4986 (clipped, SelectionGoal::None)
4987 });
4988 });
4989
4990 let mut indent_edits = Vec::new();
4991 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4992 for row in rows {
4993 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4994 for (row, indent) in indents {
4995 if indent.len == 0 {
4996 continue;
4997 }
4998
4999 let text = match indent.kind {
5000 IndentKind::Space => " ".repeat(indent.len as usize),
5001 IndentKind::Tab => "\t".repeat(indent.len as usize),
5002 };
5003 let point = Point::new(row.0, 0);
5004 indent_edits.push((point..point, text));
5005 }
5006 }
5007 editor.edit(indent_edits, cx);
5008 });
5009 }
5010
5011 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5012 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5013
5014 let buffer = self.buffer.read(cx);
5015 let snapshot = buffer.snapshot(cx);
5016
5017 let mut edits = Vec::new();
5018 let mut rows = Vec::new();
5019 let mut rows_inserted = 0;
5020
5021 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5022 let cursor = selection.head();
5023 let row = cursor.row;
5024
5025 let point = Point::new(row + 1, 0);
5026 let start_of_line = snapshot.clip_point(point, Bias::Left);
5027
5028 let newline = "\n".to_string();
5029 edits.push((start_of_line..start_of_line, newline));
5030
5031 rows_inserted += 1;
5032 rows.push(row + rows_inserted);
5033 }
5034
5035 self.transact(window, cx, |editor, window, cx| {
5036 editor.edit(edits, cx);
5037
5038 editor.change_selections(Default::default(), window, cx, |s| {
5039 let mut index = 0;
5040 s.move_cursors_with(|map, _, _| {
5041 let row = rows[index];
5042 index += 1;
5043
5044 let point = Point::new(row, 0);
5045 let boundary = map.next_line_boundary(point).1;
5046 let clipped = map.clip_point(boundary, Bias::Left);
5047
5048 (clipped, SelectionGoal::None)
5049 });
5050 });
5051
5052 let mut indent_edits = Vec::new();
5053 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5054 for row in rows {
5055 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5056 for (row, indent) in indents {
5057 if indent.len == 0 {
5058 continue;
5059 }
5060
5061 let text = match indent.kind {
5062 IndentKind::Space => " ".repeat(indent.len as usize),
5063 IndentKind::Tab => "\t".repeat(indent.len as usize),
5064 };
5065 let point = Point::new(row.0, 0);
5066 indent_edits.push((point..point, text));
5067 }
5068 }
5069 editor.edit(indent_edits, cx);
5070 });
5071 }
5072
5073 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5074 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5075 original_indent_columns: Vec::new(),
5076 });
5077 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5078 }
5079
5080 fn insert_with_autoindent_mode(
5081 &mut self,
5082 text: &str,
5083 autoindent_mode: Option<AutoindentMode>,
5084 window: &mut Window,
5085 cx: &mut Context<Self>,
5086 ) {
5087 if self.read_only(cx) {
5088 return;
5089 }
5090
5091 let text: Arc<str> = text.into();
5092 self.transact(window, cx, |this, window, cx| {
5093 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5094 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5095 let anchors = {
5096 let snapshot = buffer.read(cx);
5097 old_selections
5098 .iter()
5099 .map(|s| {
5100 let anchor = snapshot.anchor_after(s.head());
5101 s.map(|_| anchor)
5102 })
5103 .collect::<Vec<_>>()
5104 };
5105 buffer.edit(
5106 old_selections
5107 .iter()
5108 .map(|s| (s.start..s.end, text.clone())),
5109 autoindent_mode,
5110 cx,
5111 );
5112 anchors
5113 });
5114
5115 this.change_selections(Default::default(), window, cx, |s| {
5116 s.select_anchors(selection_anchors);
5117 });
5118
5119 cx.notify();
5120 });
5121 }
5122
5123 fn trigger_completion_on_input(
5124 &mut self,
5125 text: &str,
5126 trigger_in_words: bool,
5127 window: &mut Window,
5128 cx: &mut Context<Self>,
5129 ) {
5130 let completions_source = self
5131 .context_menu
5132 .borrow()
5133 .as_ref()
5134 .and_then(|menu| match menu {
5135 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5136 CodeContextMenu::CodeActions(_) => None,
5137 });
5138
5139 match completions_source {
5140 Some(CompletionsMenuSource::Words { .. }) => {
5141 self.open_or_update_completions_menu(
5142 Some(CompletionsMenuSource::Words {
5143 ignore_threshold: false,
5144 }),
5145 None,
5146 trigger_in_words,
5147 window,
5148 cx,
5149 );
5150 }
5151 _ => self.open_or_update_completions_menu(
5152 None,
5153 Some(text.to_owned()).filter(|x| !x.is_empty()),
5154 true,
5155 window,
5156 cx,
5157 ),
5158 }
5159 }
5160
5161 /// If any empty selections is touching the start of its innermost containing autoclose
5162 /// region, expand it to select the brackets.
5163 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5164 let selections = self
5165 .selections
5166 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5167 let buffer = self.buffer.read(cx).read(cx);
5168 let new_selections = self
5169 .selections_with_autoclose_regions(selections, &buffer)
5170 .map(|(mut selection, region)| {
5171 if !selection.is_empty() {
5172 return selection;
5173 }
5174
5175 if let Some(region) = region {
5176 let mut range = region.range.to_offset(&buffer);
5177 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5178 range.start -= region.pair.start.len();
5179 if buffer.contains_str_at(range.start, ®ion.pair.start)
5180 && buffer.contains_str_at(range.end, ®ion.pair.end)
5181 {
5182 range.end += region.pair.end.len();
5183 selection.start = range.start;
5184 selection.end = range.end;
5185
5186 return selection;
5187 }
5188 }
5189 }
5190
5191 let always_treat_brackets_as_autoclosed = buffer
5192 .language_settings_at(selection.start, cx)
5193 .always_treat_brackets_as_autoclosed;
5194
5195 if !always_treat_brackets_as_autoclosed {
5196 return selection;
5197 }
5198
5199 if let Some(scope) = buffer.language_scope_at(selection.start) {
5200 for (pair, enabled) in scope.brackets() {
5201 if !enabled || !pair.close {
5202 continue;
5203 }
5204
5205 if buffer.contains_str_at(selection.start, &pair.end) {
5206 let pair_start_len = pair.start.len();
5207 if buffer.contains_str_at(
5208 selection.start.saturating_sub_usize(pair_start_len),
5209 &pair.start,
5210 ) {
5211 selection.start -= pair_start_len;
5212 selection.end += pair.end.len();
5213
5214 return selection;
5215 }
5216 }
5217 }
5218 }
5219
5220 selection
5221 })
5222 .collect();
5223
5224 drop(buffer);
5225 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5226 selections.select(new_selections)
5227 });
5228 }
5229
5230 /// Iterate the given selections, and for each one, find the smallest surrounding
5231 /// autoclose region. This uses the ordering of the selections and the autoclose
5232 /// regions to avoid repeated comparisons.
5233 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5234 &'a self,
5235 selections: impl IntoIterator<Item = Selection<D>>,
5236 buffer: &'a MultiBufferSnapshot,
5237 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5238 let mut i = 0;
5239 let mut regions = self.autoclose_regions.as_slice();
5240 selections.into_iter().map(move |selection| {
5241 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5242
5243 let mut enclosing = None;
5244 while let Some(pair_state) = regions.get(i) {
5245 if pair_state.range.end.to_offset(buffer) < range.start {
5246 regions = ®ions[i + 1..];
5247 i = 0;
5248 } else if pair_state.range.start.to_offset(buffer) > range.end {
5249 break;
5250 } else {
5251 if pair_state.selection_id == selection.id {
5252 enclosing = Some(pair_state);
5253 }
5254 i += 1;
5255 }
5256 }
5257
5258 (selection, enclosing)
5259 })
5260 }
5261
5262 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5263 fn invalidate_autoclose_regions(
5264 &mut self,
5265 mut selections: &[Selection<Anchor>],
5266 buffer: &MultiBufferSnapshot,
5267 ) {
5268 self.autoclose_regions.retain(|state| {
5269 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5270 return false;
5271 }
5272
5273 let mut i = 0;
5274 while let Some(selection) = selections.get(i) {
5275 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5276 selections = &selections[1..];
5277 continue;
5278 }
5279 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5280 break;
5281 }
5282 if selection.id == state.selection_id {
5283 return true;
5284 } else {
5285 i += 1;
5286 }
5287 }
5288 false
5289 });
5290 }
5291
5292 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5293 let offset = position.to_offset(buffer);
5294 let (word_range, kind) =
5295 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5296 if offset > word_range.start && kind == Some(CharKind::Word) {
5297 Some(
5298 buffer
5299 .text_for_range(word_range.start..offset)
5300 .collect::<String>(),
5301 )
5302 } else {
5303 None
5304 }
5305 }
5306
5307 pub fn visible_excerpts(
5308 &self,
5309 cx: &mut Context<Editor>,
5310 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5311 let Some(project) = self.project() else {
5312 return HashMap::default();
5313 };
5314 let project = project.read(cx);
5315 let multi_buffer = self.buffer().read(cx);
5316 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5317 let multi_buffer_visible_start = self
5318 .scroll_manager
5319 .anchor()
5320 .anchor
5321 .to_point(&multi_buffer_snapshot);
5322 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5323 multi_buffer_visible_start
5324 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5325 Bias::Left,
5326 );
5327 multi_buffer_snapshot
5328 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5329 .into_iter()
5330 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5331 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5332 let buffer_file = project::File::from_dyn(buffer.file())?;
5333 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5334 let worktree_entry = buffer_worktree
5335 .read(cx)
5336 .entry_for_id(buffer_file.project_entry_id()?)?;
5337 if worktree_entry.is_ignored {
5338 None
5339 } else {
5340 Some((
5341 excerpt_id,
5342 (
5343 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5344 buffer.version().clone(),
5345 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5346 ),
5347 ))
5348 }
5349 })
5350 .collect()
5351 }
5352
5353 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5354 TextLayoutDetails {
5355 text_system: window.text_system().clone(),
5356 editor_style: self.style.clone().unwrap(),
5357 rem_size: window.rem_size(),
5358 scroll_anchor: self.scroll_manager.anchor(),
5359 visible_rows: self.visible_line_count(),
5360 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5361 }
5362 }
5363
5364 fn trigger_on_type_formatting(
5365 &self,
5366 input: String,
5367 window: &mut Window,
5368 cx: &mut Context<Self>,
5369 ) -> Option<Task<Result<()>>> {
5370 if input.len() != 1 {
5371 return None;
5372 }
5373
5374 let project = self.project()?;
5375 let position = self.selections.newest_anchor().head();
5376 let (buffer, buffer_position) = self
5377 .buffer
5378 .read(cx)
5379 .text_anchor_for_position(position, cx)?;
5380
5381 let settings = language_settings::language_settings(
5382 buffer
5383 .read(cx)
5384 .language_at(buffer_position)
5385 .map(|l| l.name()),
5386 buffer.read(cx).file(),
5387 cx,
5388 );
5389 if !settings.use_on_type_format {
5390 return None;
5391 }
5392
5393 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5394 // hence we do LSP request & edit on host side only — add formats to host's history.
5395 let push_to_lsp_host_history = true;
5396 // If this is not the host, append its history with new edits.
5397 let push_to_client_history = project.read(cx).is_via_collab();
5398
5399 let on_type_formatting = project.update(cx, |project, cx| {
5400 project.on_type_format(
5401 buffer.clone(),
5402 buffer_position,
5403 input,
5404 push_to_lsp_host_history,
5405 cx,
5406 )
5407 });
5408 Some(cx.spawn_in(window, async move |editor, cx| {
5409 if let Some(transaction) = on_type_formatting.await? {
5410 if push_to_client_history {
5411 buffer
5412 .update(cx, |buffer, _| {
5413 buffer.push_transaction(transaction, Instant::now());
5414 buffer.finalize_last_transaction();
5415 })
5416 .ok();
5417 }
5418 editor.update(cx, |editor, cx| {
5419 editor.refresh_document_highlights(cx);
5420 })?;
5421 }
5422 Ok(())
5423 }))
5424 }
5425
5426 pub fn show_word_completions(
5427 &mut self,
5428 _: &ShowWordCompletions,
5429 window: &mut Window,
5430 cx: &mut Context<Self>,
5431 ) {
5432 self.open_or_update_completions_menu(
5433 Some(CompletionsMenuSource::Words {
5434 ignore_threshold: true,
5435 }),
5436 None,
5437 false,
5438 window,
5439 cx,
5440 );
5441 }
5442
5443 pub fn show_completions(
5444 &mut self,
5445 _: &ShowCompletions,
5446 window: &mut Window,
5447 cx: &mut Context<Self>,
5448 ) {
5449 self.open_or_update_completions_menu(None, None, false, window, cx);
5450 }
5451
5452 fn open_or_update_completions_menu(
5453 &mut self,
5454 requested_source: Option<CompletionsMenuSource>,
5455 trigger: Option<String>,
5456 trigger_in_words: bool,
5457 window: &mut Window,
5458 cx: &mut Context<Self>,
5459 ) {
5460 if self.pending_rename.is_some() {
5461 return;
5462 }
5463
5464 let completions_source = self
5465 .context_menu
5466 .borrow()
5467 .as_ref()
5468 .and_then(|menu| match menu {
5469 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5470 CodeContextMenu::CodeActions(_) => None,
5471 });
5472
5473 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5474
5475 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5476 // inserted and selected. To handle that case, the start of the selection is used so that
5477 // the menu starts with all choices.
5478 let position = self
5479 .selections
5480 .newest_anchor()
5481 .start
5482 .bias_right(&multibuffer_snapshot);
5483 if position.diff_base_anchor.is_some() {
5484 return;
5485 }
5486 let buffer_position = multibuffer_snapshot.anchor_before(position);
5487 let Some(buffer) = buffer_position
5488 .buffer_id
5489 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5490 else {
5491 return;
5492 };
5493 let buffer_snapshot = buffer.read(cx).snapshot();
5494
5495 let query: Option<Arc<String>> =
5496 Self::completion_query(&multibuffer_snapshot, buffer_position)
5497 .map(|query| query.into());
5498
5499 drop(multibuffer_snapshot);
5500
5501 // Hide the current completions menu when query is empty. Without this, cached
5502 // completions from before the trigger char may be reused (#32774).
5503 if query.is_none() {
5504 let menu_is_open = matches!(
5505 self.context_menu.borrow().as_ref(),
5506 Some(CodeContextMenu::Completions(_))
5507 );
5508 if menu_is_open {
5509 self.hide_context_menu(window, cx);
5510 }
5511 }
5512
5513 let mut ignore_word_threshold = false;
5514 let provider = match requested_source {
5515 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5516 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5517 ignore_word_threshold = ignore_threshold;
5518 None
5519 }
5520 Some(CompletionsMenuSource::SnippetChoices)
5521 | Some(CompletionsMenuSource::SnippetsOnly) => {
5522 log::error!("bug: SnippetChoices requested_source is not handled");
5523 None
5524 }
5525 };
5526
5527 let sort_completions = provider
5528 .as_ref()
5529 .is_some_and(|provider| provider.sort_completions());
5530
5531 let filter_completions = provider
5532 .as_ref()
5533 .is_none_or(|provider| provider.filter_completions());
5534
5535 let was_snippets_only = matches!(
5536 completions_source,
5537 Some(CompletionsMenuSource::SnippetsOnly)
5538 );
5539
5540 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5541 if filter_completions {
5542 menu.filter(
5543 query.clone().unwrap_or_default(),
5544 buffer_position.text_anchor,
5545 &buffer,
5546 provider.clone(),
5547 window,
5548 cx,
5549 );
5550 }
5551 // When `is_incomplete` is false, no need to re-query completions when the current query
5552 // is a suffix of the initial query.
5553 let was_complete = !menu.is_incomplete;
5554 if was_complete && !was_snippets_only {
5555 // If the new query is a suffix of the old query (typing more characters) and
5556 // the previous result was complete, the existing completions can be filtered.
5557 //
5558 // Note that snippet completions are always complete.
5559 let query_matches = match (&menu.initial_query, &query) {
5560 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5561 (None, _) => true,
5562 _ => false,
5563 };
5564 if query_matches {
5565 let position_matches = if menu.initial_position == position {
5566 true
5567 } else {
5568 let snapshot = self.buffer.read(cx).read(cx);
5569 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5570 };
5571 if position_matches {
5572 return;
5573 }
5574 }
5575 }
5576 };
5577
5578 let Anchor {
5579 excerpt_id: buffer_excerpt_id,
5580 text_anchor: buffer_position,
5581 ..
5582 } = buffer_position;
5583
5584 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5585 buffer_snapshot.surrounding_word(buffer_position, None)
5586 {
5587 let word_to_exclude = buffer_snapshot
5588 .text_for_range(word_range.clone())
5589 .collect::<String>();
5590 (
5591 buffer_snapshot.anchor_before(word_range.start)
5592 ..buffer_snapshot.anchor_after(buffer_position),
5593 Some(word_to_exclude),
5594 )
5595 } else {
5596 (buffer_position..buffer_position, None)
5597 };
5598
5599 let language = buffer_snapshot
5600 .language_at(buffer_position)
5601 .map(|language| language.name());
5602
5603 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5604 .completions
5605 .clone();
5606
5607 let show_completion_documentation = buffer_snapshot
5608 .settings_at(buffer_position, cx)
5609 .show_completion_documentation;
5610
5611 // The document can be large, so stay in reasonable bounds when searching for words,
5612 // otherwise completion pop-up might be slow to appear.
5613 const WORD_LOOKUP_ROWS: u32 = 5_000;
5614 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5615 let min_word_search = buffer_snapshot.clip_point(
5616 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5617 Bias::Left,
5618 );
5619 let max_word_search = buffer_snapshot.clip_point(
5620 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5621 Bias::Right,
5622 );
5623 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5624 ..buffer_snapshot.point_to_offset(max_word_search);
5625
5626 let skip_digits = query
5627 .as_ref()
5628 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5629
5630 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5631 trigger.as_ref().is_none_or(|trigger| {
5632 provider.is_completion_trigger(
5633 &buffer,
5634 position.text_anchor,
5635 trigger,
5636 trigger_in_words,
5637 completions_source.is_some(),
5638 cx,
5639 )
5640 })
5641 });
5642
5643 let provider_responses = if let Some(provider) = &provider
5644 && load_provider_completions
5645 {
5646 let trigger_character =
5647 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5648 let completion_context = CompletionContext {
5649 trigger_kind: match &trigger_character {
5650 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5651 None => CompletionTriggerKind::INVOKED,
5652 },
5653 trigger_character,
5654 };
5655
5656 provider.completions(
5657 buffer_excerpt_id,
5658 &buffer,
5659 buffer_position,
5660 completion_context,
5661 window,
5662 cx,
5663 )
5664 } else {
5665 Task::ready(Ok(Vec::new()))
5666 };
5667
5668 let load_word_completions = if !self.word_completions_enabled {
5669 false
5670 } else if requested_source
5671 == Some(CompletionsMenuSource::Words {
5672 ignore_threshold: true,
5673 })
5674 {
5675 true
5676 } else {
5677 load_provider_completions
5678 && completion_settings.words != WordsCompletionMode::Disabled
5679 && (ignore_word_threshold || {
5680 let words_min_length = completion_settings.words_min_length;
5681 // check whether word has at least `words_min_length` characters
5682 let query_chars = query.iter().flat_map(|q| q.chars());
5683 query_chars.take(words_min_length).count() == words_min_length
5684 })
5685 };
5686
5687 let mut words = if load_word_completions {
5688 cx.background_spawn({
5689 let buffer_snapshot = buffer_snapshot.clone();
5690 async move {
5691 buffer_snapshot.words_in_range(WordsQuery {
5692 fuzzy_contents: None,
5693 range: word_search_range,
5694 skip_digits,
5695 })
5696 }
5697 })
5698 } else {
5699 Task::ready(BTreeMap::default())
5700 };
5701
5702 let snippets = if let Some(provider) = &provider
5703 && provider.show_snippets()
5704 && let Some(project) = self.project()
5705 {
5706 let char_classifier = buffer_snapshot
5707 .char_classifier_at(buffer_position)
5708 .scope_context(Some(CharScopeContext::Completion));
5709 project.update(cx, |project, cx| {
5710 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5711 })
5712 } else {
5713 Task::ready(Ok(CompletionResponse {
5714 completions: Vec::new(),
5715 display_options: Default::default(),
5716 is_incomplete: false,
5717 }))
5718 };
5719
5720 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5721
5722 let id = post_inc(&mut self.next_completion_id);
5723 let task = cx.spawn_in(window, async move |editor, cx| {
5724 let Ok(()) = editor.update(cx, |this, _| {
5725 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5726 }) else {
5727 return;
5728 };
5729
5730 // TODO: Ideally completions from different sources would be selectively re-queried, so
5731 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5732 let mut completions = Vec::new();
5733 let mut is_incomplete = false;
5734 let mut display_options: Option<CompletionDisplayOptions> = None;
5735 if let Some(provider_responses) = provider_responses.await.log_err()
5736 && !provider_responses.is_empty()
5737 {
5738 for response in provider_responses {
5739 completions.extend(response.completions);
5740 is_incomplete = is_incomplete || response.is_incomplete;
5741 match display_options.as_mut() {
5742 None => {
5743 display_options = Some(response.display_options);
5744 }
5745 Some(options) => options.merge(&response.display_options),
5746 }
5747 }
5748 if completion_settings.words == WordsCompletionMode::Fallback {
5749 words = Task::ready(BTreeMap::default());
5750 }
5751 }
5752 let display_options = display_options.unwrap_or_default();
5753
5754 let mut words = words.await;
5755 if let Some(word_to_exclude) = &word_to_exclude {
5756 words.remove(word_to_exclude);
5757 }
5758 for lsp_completion in &completions {
5759 words.remove(&lsp_completion.new_text);
5760 }
5761 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5762 replace_range: word_replace_range.clone(),
5763 new_text: word.clone(),
5764 label: CodeLabel::plain(word, None),
5765 match_start: None,
5766 snippet_deduplication_key: None,
5767 icon_path: None,
5768 documentation: None,
5769 source: CompletionSource::BufferWord {
5770 word_range,
5771 resolved: false,
5772 },
5773 insert_text_mode: Some(InsertTextMode::AS_IS),
5774 confirm: None,
5775 }));
5776
5777 completions.extend(
5778 snippets
5779 .await
5780 .into_iter()
5781 .flat_map(|response| response.completions),
5782 );
5783
5784 let menu = if completions.is_empty() {
5785 None
5786 } else {
5787 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5788 let languages = editor
5789 .workspace
5790 .as_ref()
5791 .and_then(|(workspace, _)| workspace.upgrade())
5792 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5793 let menu = CompletionsMenu::new(
5794 id,
5795 requested_source.unwrap_or(if load_provider_completions {
5796 CompletionsMenuSource::Normal
5797 } else {
5798 CompletionsMenuSource::SnippetsOnly
5799 }),
5800 sort_completions,
5801 show_completion_documentation,
5802 position,
5803 query.clone(),
5804 is_incomplete,
5805 buffer.clone(),
5806 completions.into(),
5807 display_options,
5808 snippet_sort_order,
5809 languages,
5810 language,
5811 cx,
5812 );
5813
5814 let query = if filter_completions { query } else { None };
5815 let matches_task = menu.do_async_filtering(
5816 query.unwrap_or_default(),
5817 buffer_position,
5818 &buffer,
5819 cx,
5820 );
5821 (menu, matches_task)
5822 }) else {
5823 return;
5824 };
5825
5826 let matches = matches_task.await;
5827
5828 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5829 // Newer menu already set, so exit.
5830 if let Some(CodeContextMenu::Completions(prev_menu)) =
5831 editor.context_menu.borrow().as_ref()
5832 && prev_menu.id > id
5833 {
5834 return;
5835 };
5836
5837 // Only valid to take prev_menu because either the new menu is immediately set
5838 // below, or the menu is hidden.
5839 if let Some(CodeContextMenu::Completions(prev_menu)) =
5840 editor.context_menu.borrow_mut().take()
5841 {
5842 let position_matches =
5843 if prev_menu.initial_position == menu.initial_position {
5844 true
5845 } else {
5846 let snapshot = editor.buffer.read(cx).read(cx);
5847 prev_menu.initial_position.to_offset(&snapshot)
5848 == menu.initial_position.to_offset(&snapshot)
5849 };
5850 if position_matches {
5851 // Preserve markdown cache before `set_filter_results` because it will
5852 // try to populate the documentation cache.
5853 menu.preserve_markdown_cache(prev_menu);
5854 }
5855 };
5856
5857 menu.set_filter_results(matches, provider, window, cx);
5858 }) else {
5859 return;
5860 };
5861
5862 menu.visible().then_some(menu)
5863 };
5864
5865 editor
5866 .update_in(cx, |editor, window, cx| {
5867 if editor.focus_handle.is_focused(window)
5868 && let Some(menu) = menu
5869 {
5870 *editor.context_menu.borrow_mut() =
5871 Some(CodeContextMenu::Completions(menu));
5872
5873 crate::hover_popover::hide_hover(editor, cx);
5874 if editor.show_edit_predictions_in_menu() {
5875 editor.update_visible_edit_prediction(window, cx);
5876 } else {
5877 editor.discard_edit_prediction(false, cx);
5878 }
5879
5880 cx.notify();
5881 return;
5882 }
5883
5884 if editor.completion_tasks.len() <= 1 {
5885 // If there are no more completion tasks and the last menu was empty, we should hide it.
5886 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5887 // If it was already hidden and we don't show edit predictions in the menu,
5888 // we should also show the edit prediction when available.
5889 if was_hidden && editor.show_edit_predictions_in_menu() {
5890 editor.update_visible_edit_prediction(window, cx);
5891 }
5892 }
5893 })
5894 .ok();
5895 });
5896
5897 self.completion_tasks.push((id, task));
5898 }
5899
5900 #[cfg(feature = "test-support")]
5901 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5902 let menu = self.context_menu.borrow();
5903 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5904 let completions = menu.completions.borrow();
5905 Some(completions.to_vec())
5906 } else {
5907 None
5908 }
5909 }
5910
5911 pub fn with_completions_menu_matching_id<R>(
5912 &self,
5913 id: CompletionId,
5914 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5915 ) -> R {
5916 let mut context_menu = self.context_menu.borrow_mut();
5917 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5918 return f(None);
5919 };
5920 if completions_menu.id != id {
5921 return f(None);
5922 }
5923 f(Some(completions_menu))
5924 }
5925
5926 pub fn confirm_completion(
5927 &mut self,
5928 action: &ConfirmCompletion,
5929 window: &mut Window,
5930 cx: &mut Context<Self>,
5931 ) -> Option<Task<Result<()>>> {
5932 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5933 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5934 }
5935
5936 pub fn confirm_completion_insert(
5937 &mut self,
5938 _: &ConfirmCompletionInsert,
5939 window: &mut Window,
5940 cx: &mut Context<Self>,
5941 ) -> Option<Task<Result<()>>> {
5942 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5943 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5944 }
5945
5946 pub fn confirm_completion_replace(
5947 &mut self,
5948 _: &ConfirmCompletionReplace,
5949 window: &mut Window,
5950 cx: &mut Context<Self>,
5951 ) -> Option<Task<Result<()>>> {
5952 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5953 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5954 }
5955
5956 pub fn compose_completion(
5957 &mut self,
5958 action: &ComposeCompletion,
5959 window: &mut Window,
5960 cx: &mut Context<Self>,
5961 ) -> Option<Task<Result<()>>> {
5962 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5963 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5964 }
5965
5966 fn do_completion(
5967 &mut self,
5968 item_ix: Option<usize>,
5969 intent: CompletionIntent,
5970 window: &mut Window,
5971 cx: &mut Context<Editor>,
5972 ) -> Option<Task<Result<()>>> {
5973 use language::ToOffset as _;
5974
5975 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5976 else {
5977 return None;
5978 };
5979
5980 let candidate_id = {
5981 let entries = completions_menu.entries.borrow();
5982 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5983 if self.show_edit_predictions_in_menu() {
5984 self.discard_edit_prediction(true, cx);
5985 }
5986 mat.candidate_id
5987 };
5988
5989 let completion = completions_menu
5990 .completions
5991 .borrow()
5992 .get(candidate_id)?
5993 .clone();
5994 cx.stop_propagation();
5995
5996 let buffer_handle = completions_menu.buffer.clone();
5997
5998 let CompletionEdit {
5999 new_text,
6000 snippet,
6001 replace_range,
6002 } = process_completion_for_edit(
6003 &completion,
6004 intent,
6005 &buffer_handle,
6006 &completions_menu.initial_position.text_anchor,
6007 cx,
6008 );
6009
6010 let buffer = buffer_handle.read(cx);
6011 let snapshot = self.buffer.read(cx).snapshot(cx);
6012 let newest_anchor = self.selections.newest_anchor();
6013 let replace_range_multibuffer = {
6014 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6015 excerpt.map_range_from_buffer(replace_range.clone())
6016 };
6017 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6018 return None;
6019 }
6020
6021 let old_text = buffer
6022 .text_for_range(replace_range.clone())
6023 .collect::<String>();
6024 let lookbehind = newest_anchor
6025 .start
6026 .text_anchor
6027 .to_offset(buffer)
6028 .saturating_sub(replace_range.start.0);
6029 let lookahead = replace_range
6030 .end
6031 .0
6032 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6033 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6034 let suffix = &old_text[lookbehind.min(old_text.len())..];
6035
6036 let selections = self
6037 .selections
6038 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6039 let mut ranges = Vec::new();
6040 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6041
6042 for selection in &selections {
6043 let range = if selection.id == newest_anchor.id {
6044 replace_range_multibuffer.clone()
6045 } else {
6046 let mut range = selection.range();
6047
6048 // if prefix is present, don't duplicate it
6049 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6050 range.start = range.start.saturating_sub_usize(lookbehind);
6051
6052 // if suffix is also present, mimic the newest cursor and replace it
6053 if selection.id != newest_anchor.id
6054 && snapshot.contains_str_at(range.end, suffix)
6055 {
6056 range.end += lookahead;
6057 }
6058 }
6059 range
6060 };
6061
6062 ranges.push(range.clone());
6063
6064 if !self.linked_edit_ranges.is_empty() {
6065 let start_anchor = snapshot.anchor_before(range.start);
6066 let end_anchor = snapshot.anchor_after(range.end);
6067 if let Some(ranges) = self
6068 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6069 {
6070 for (buffer, edits) in ranges {
6071 linked_edits
6072 .entry(buffer.clone())
6073 .or_default()
6074 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6075 }
6076 }
6077 }
6078 }
6079
6080 let common_prefix_len = old_text
6081 .chars()
6082 .zip(new_text.chars())
6083 .take_while(|(a, b)| a == b)
6084 .map(|(a, _)| a.len_utf8())
6085 .sum::<usize>();
6086
6087 cx.emit(EditorEvent::InputHandled {
6088 utf16_range_to_replace: None,
6089 text: new_text[common_prefix_len..].into(),
6090 });
6091
6092 self.transact(window, cx, |editor, window, cx| {
6093 if let Some(mut snippet) = snippet {
6094 snippet.text = new_text.to_string();
6095 editor
6096 .insert_snippet(&ranges, snippet, window, cx)
6097 .log_err();
6098 } else {
6099 editor.buffer.update(cx, |multi_buffer, cx| {
6100 let auto_indent = match completion.insert_text_mode {
6101 Some(InsertTextMode::AS_IS) => None,
6102 _ => editor.autoindent_mode.clone(),
6103 };
6104 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6105 multi_buffer.edit(edits, auto_indent, cx);
6106 });
6107 }
6108 for (buffer, edits) in linked_edits {
6109 buffer.update(cx, |buffer, cx| {
6110 let snapshot = buffer.snapshot();
6111 let edits = edits
6112 .into_iter()
6113 .map(|(range, text)| {
6114 use text::ToPoint as TP;
6115 let end_point = TP::to_point(&range.end, &snapshot);
6116 let start_point = TP::to_point(&range.start, &snapshot);
6117 (start_point..end_point, text)
6118 })
6119 .sorted_by_key(|(range, _)| range.start);
6120 buffer.edit(edits, None, cx);
6121 })
6122 }
6123
6124 editor.refresh_edit_prediction(true, false, window, cx);
6125 });
6126 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6127
6128 let show_new_completions_on_confirm = completion
6129 .confirm
6130 .as_ref()
6131 .is_some_and(|confirm| confirm(intent, window, cx));
6132 if show_new_completions_on_confirm {
6133 self.open_or_update_completions_menu(None, None, false, window, cx);
6134 }
6135
6136 let provider = self.completion_provider.as_ref()?;
6137 drop(completion);
6138 let apply_edits = provider.apply_additional_edits_for_completion(
6139 buffer_handle,
6140 completions_menu.completions.clone(),
6141 candidate_id,
6142 true,
6143 cx,
6144 );
6145
6146 let editor_settings = EditorSettings::get_global(cx);
6147 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6148 // After the code completion is finished, users often want to know what signatures are needed.
6149 // so we should automatically call signature_help
6150 self.show_signature_help(&ShowSignatureHelp, window, cx);
6151 }
6152
6153 Some(cx.foreground_executor().spawn(async move {
6154 apply_edits.await?;
6155 Ok(())
6156 }))
6157 }
6158
6159 pub fn toggle_code_actions(
6160 &mut self,
6161 action: &ToggleCodeActions,
6162 window: &mut Window,
6163 cx: &mut Context<Self>,
6164 ) {
6165 let quick_launch = action.quick_launch;
6166 let mut context_menu = self.context_menu.borrow_mut();
6167 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6168 if code_actions.deployed_from == action.deployed_from {
6169 // Toggle if we're selecting the same one
6170 *context_menu = None;
6171 cx.notify();
6172 return;
6173 } else {
6174 // Otherwise, clear it and start a new one
6175 *context_menu = None;
6176 cx.notify();
6177 }
6178 }
6179 drop(context_menu);
6180 let snapshot = self.snapshot(window, cx);
6181 let deployed_from = action.deployed_from.clone();
6182 let action = action.clone();
6183 self.completion_tasks.clear();
6184 self.discard_edit_prediction(false, cx);
6185
6186 let multibuffer_point = match &action.deployed_from {
6187 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6188 DisplayPoint::new(*row, 0).to_point(&snapshot)
6189 }
6190 _ => self
6191 .selections
6192 .newest::<Point>(&snapshot.display_snapshot)
6193 .head(),
6194 };
6195 let Some((buffer, buffer_row)) = snapshot
6196 .buffer_snapshot()
6197 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6198 .and_then(|(buffer_snapshot, range)| {
6199 self.buffer()
6200 .read(cx)
6201 .buffer(buffer_snapshot.remote_id())
6202 .map(|buffer| (buffer, range.start.row))
6203 })
6204 else {
6205 return;
6206 };
6207 let buffer_id = buffer.read(cx).remote_id();
6208 let tasks = self
6209 .tasks
6210 .get(&(buffer_id, buffer_row))
6211 .map(|t| Arc::new(t.to_owned()));
6212
6213 if !self.focus_handle.is_focused(window) {
6214 return;
6215 }
6216 let project = self.project.clone();
6217
6218 let code_actions_task = match deployed_from {
6219 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6220 _ => self.code_actions(buffer_row, window, cx),
6221 };
6222
6223 let runnable_task = match deployed_from {
6224 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6225 _ => {
6226 let mut task_context_task = Task::ready(None);
6227 if let Some(tasks) = &tasks
6228 && let Some(project) = project
6229 {
6230 task_context_task =
6231 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6232 }
6233
6234 cx.spawn_in(window, {
6235 let buffer = buffer.clone();
6236 async move |editor, cx| {
6237 let task_context = task_context_task.await;
6238
6239 let resolved_tasks =
6240 tasks
6241 .zip(task_context.clone())
6242 .map(|(tasks, task_context)| ResolvedTasks {
6243 templates: tasks.resolve(&task_context).collect(),
6244 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6245 multibuffer_point.row,
6246 tasks.column,
6247 )),
6248 });
6249 let debug_scenarios = editor
6250 .update(cx, |editor, cx| {
6251 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6252 })?
6253 .await;
6254 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6255 }
6256 })
6257 }
6258 };
6259
6260 cx.spawn_in(window, async move |editor, cx| {
6261 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6262 let code_actions = code_actions_task.await;
6263 let spawn_straight_away = quick_launch
6264 && resolved_tasks
6265 .as_ref()
6266 .is_some_and(|tasks| tasks.templates.len() == 1)
6267 && code_actions
6268 .as_ref()
6269 .is_none_or(|actions| actions.is_empty())
6270 && debug_scenarios.is_empty();
6271
6272 editor.update_in(cx, |editor, window, cx| {
6273 crate::hover_popover::hide_hover(editor, cx);
6274 let actions = CodeActionContents::new(
6275 resolved_tasks,
6276 code_actions,
6277 debug_scenarios,
6278 task_context.unwrap_or_default(),
6279 );
6280
6281 // Don't show the menu if there are no actions available
6282 if actions.is_empty() {
6283 cx.notify();
6284 return Task::ready(Ok(()));
6285 }
6286
6287 *editor.context_menu.borrow_mut() =
6288 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6289 buffer,
6290 actions,
6291 selected_item: Default::default(),
6292 scroll_handle: UniformListScrollHandle::default(),
6293 deployed_from,
6294 }));
6295 cx.notify();
6296 if spawn_straight_away
6297 && let Some(task) = editor.confirm_code_action(
6298 &ConfirmCodeAction { item_ix: Some(0) },
6299 window,
6300 cx,
6301 )
6302 {
6303 return task;
6304 }
6305
6306 Task::ready(Ok(()))
6307 })
6308 })
6309 .detach_and_log_err(cx);
6310 }
6311
6312 fn debug_scenarios(
6313 &mut self,
6314 resolved_tasks: &Option<ResolvedTasks>,
6315 buffer: &Entity<Buffer>,
6316 cx: &mut App,
6317 ) -> Task<Vec<task::DebugScenario>> {
6318 maybe!({
6319 let project = self.project()?;
6320 let dap_store = project.read(cx).dap_store();
6321 let mut scenarios = vec![];
6322 let resolved_tasks = resolved_tasks.as_ref()?;
6323 let buffer = buffer.read(cx);
6324 let language = buffer.language()?;
6325 let file = buffer.file();
6326 let debug_adapter = language_settings(language.name().into(), file, cx)
6327 .debuggers
6328 .first()
6329 .map(SharedString::from)
6330 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6331
6332 dap_store.update(cx, |dap_store, cx| {
6333 for (_, task) in &resolved_tasks.templates {
6334 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6335 task.original_task().clone(),
6336 debug_adapter.clone().into(),
6337 task.display_label().to_owned().into(),
6338 cx,
6339 );
6340 scenarios.push(maybe_scenario);
6341 }
6342 });
6343 Some(cx.background_spawn(async move {
6344 futures::future::join_all(scenarios)
6345 .await
6346 .into_iter()
6347 .flatten()
6348 .collect::<Vec<_>>()
6349 }))
6350 })
6351 .unwrap_or_else(|| Task::ready(vec![]))
6352 }
6353
6354 fn code_actions(
6355 &mut self,
6356 buffer_row: u32,
6357 window: &mut Window,
6358 cx: &mut Context<Self>,
6359 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6360 let mut task = self.code_actions_task.take();
6361 cx.spawn_in(window, async move |editor, cx| {
6362 while let Some(prev_task) = task {
6363 prev_task.await.log_err();
6364 task = editor
6365 .update(cx, |this, _| this.code_actions_task.take())
6366 .ok()?;
6367 }
6368
6369 editor
6370 .update(cx, |editor, cx| {
6371 editor
6372 .available_code_actions
6373 .clone()
6374 .and_then(|(location, code_actions)| {
6375 let snapshot = location.buffer.read(cx).snapshot();
6376 let point_range = location.range.to_point(&snapshot);
6377 let point_range = point_range.start.row..=point_range.end.row;
6378 if point_range.contains(&buffer_row) {
6379 Some(code_actions)
6380 } else {
6381 None
6382 }
6383 })
6384 })
6385 .ok()
6386 .flatten()
6387 })
6388 }
6389
6390 pub fn confirm_code_action(
6391 &mut self,
6392 action: &ConfirmCodeAction,
6393 window: &mut Window,
6394 cx: &mut Context<Self>,
6395 ) -> Option<Task<Result<()>>> {
6396 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6397
6398 let actions_menu =
6399 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6400 menu
6401 } else {
6402 return None;
6403 };
6404
6405 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6406 let action = actions_menu.actions.get(action_ix)?;
6407 let title = action.label();
6408 let buffer = actions_menu.buffer;
6409 let workspace = self.workspace()?;
6410
6411 match action {
6412 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6413 workspace.update(cx, |workspace, cx| {
6414 workspace.schedule_resolved_task(
6415 task_source_kind,
6416 resolved_task,
6417 false,
6418 window,
6419 cx,
6420 );
6421
6422 Some(Task::ready(Ok(())))
6423 })
6424 }
6425 CodeActionsItem::CodeAction {
6426 excerpt_id,
6427 action,
6428 provider,
6429 } => {
6430 let apply_code_action =
6431 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6432 let workspace = workspace.downgrade();
6433 Some(cx.spawn_in(window, async move |editor, cx| {
6434 let project_transaction = apply_code_action.await?;
6435 Self::open_project_transaction(
6436 &editor,
6437 workspace,
6438 project_transaction,
6439 title,
6440 cx,
6441 )
6442 .await
6443 }))
6444 }
6445 CodeActionsItem::DebugScenario(scenario) => {
6446 let context = actions_menu.actions.context;
6447
6448 workspace.update(cx, |workspace, cx| {
6449 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6450 workspace.start_debug_session(
6451 scenario,
6452 context,
6453 Some(buffer),
6454 None,
6455 window,
6456 cx,
6457 );
6458 });
6459 Some(Task::ready(Ok(())))
6460 }
6461 }
6462 }
6463
6464 pub async fn open_project_transaction(
6465 editor: &WeakEntity<Editor>,
6466 workspace: WeakEntity<Workspace>,
6467 transaction: ProjectTransaction,
6468 title: String,
6469 cx: &mut AsyncWindowContext,
6470 ) -> Result<()> {
6471 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6472 cx.update(|_, cx| {
6473 entries.sort_unstable_by_key(|(buffer, _)| {
6474 buffer.read(cx).file().map(|f| f.path().clone())
6475 });
6476 })?;
6477 if entries.is_empty() {
6478 return Ok(());
6479 }
6480
6481 // If the project transaction's edits are all contained within this editor, then
6482 // avoid opening a new editor to display them.
6483
6484 if let [(buffer, transaction)] = &*entries {
6485 let excerpt = editor.update(cx, |editor, cx| {
6486 editor
6487 .buffer()
6488 .read(cx)
6489 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6490 })?;
6491 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6492 && excerpted_buffer == *buffer
6493 {
6494 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6495 let excerpt_range = excerpt_range.to_offset(buffer);
6496 buffer
6497 .edited_ranges_for_transaction::<usize>(transaction)
6498 .all(|range| {
6499 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6500 })
6501 })?;
6502
6503 if all_edits_within_excerpt {
6504 return Ok(());
6505 }
6506 }
6507 }
6508
6509 let mut ranges_to_highlight = Vec::new();
6510 let excerpt_buffer = cx.new(|cx| {
6511 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6512 for (buffer_handle, transaction) in &entries {
6513 let edited_ranges = buffer_handle
6514 .read(cx)
6515 .edited_ranges_for_transaction::<Point>(transaction)
6516 .collect::<Vec<_>>();
6517 let (ranges, _) = multibuffer.set_excerpts_for_path(
6518 PathKey::for_buffer(buffer_handle, cx),
6519 buffer_handle.clone(),
6520 edited_ranges,
6521 multibuffer_context_lines(cx),
6522 cx,
6523 );
6524
6525 ranges_to_highlight.extend(ranges);
6526 }
6527 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6528 multibuffer
6529 })?;
6530
6531 workspace.update_in(cx, |workspace, window, cx| {
6532 let project = workspace.project().clone();
6533 let editor =
6534 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6535 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6536 editor.update(cx, |editor, cx| {
6537 editor.highlight_background::<Self>(
6538 &ranges_to_highlight,
6539 |theme| theme.colors().editor_highlighted_line_background,
6540 cx,
6541 );
6542 });
6543 })?;
6544
6545 Ok(())
6546 }
6547
6548 pub fn clear_code_action_providers(&mut self) {
6549 self.code_action_providers.clear();
6550 self.available_code_actions.take();
6551 }
6552
6553 pub fn add_code_action_provider(
6554 &mut self,
6555 provider: Rc<dyn CodeActionProvider>,
6556 window: &mut Window,
6557 cx: &mut Context<Self>,
6558 ) {
6559 if self
6560 .code_action_providers
6561 .iter()
6562 .any(|existing_provider| existing_provider.id() == provider.id())
6563 {
6564 return;
6565 }
6566
6567 self.code_action_providers.push(provider);
6568 self.refresh_code_actions(window, cx);
6569 }
6570
6571 pub fn remove_code_action_provider(
6572 &mut self,
6573 id: Arc<str>,
6574 window: &mut Window,
6575 cx: &mut Context<Self>,
6576 ) {
6577 self.code_action_providers
6578 .retain(|provider| provider.id() != id);
6579 self.refresh_code_actions(window, cx);
6580 }
6581
6582 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6583 !self.code_action_providers.is_empty()
6584 && EditorSettings::get_global(cx).toolbar.code_actions
6585 }
6586
6587 pub fn has_available_code_actions(&self) -> bool {
6588 self.available_code_actions
6589 .as_ref()
6590 .is_some_and(|(_, actions)| !actions.is_empty())
6591 }
6592
6593 fn render_inline_code_actions(
6594 &self,
6595 icon_size: ui::IconSize,
6596 display_row: DisplayRow,
6597 is_active: bool,
6598 cx: &mut Context<Self>,
6599 ) -> AnyElement {
6600 let show_tooltip = !self.context_menu_visible();
6601 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6602 .icon_size(icon_size)
6603 .shape(ui::IconButtonShape::Square)
6604 .icon_color(ui::Color::Hidden)
6605 .toggle_state(is_active)
6606 .when(show_tooltip, |this| {
6607 this.tooltip({
6608 let focus_handle = self.focus_handle.clone();
6609 move |_window, cx| {
6610 Tooltip::for_action_in(
6611 "Toggle Code Actions",
6612 &ToggleCodeActions {
6613 deployed_from: None,
6614 quick_launch: false,
6615 },
6616 &focus_handle,
6617 cx,
6618 )
6619 }
6620 })
6621 })
6622 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6623 window.focus(&editor.focus_handle(cx));
6624 editor.toggle_code_actions(
6625 &crate::actions::ToggleCodeActions {
6626 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6627 display_row,
6628 )),
6629 quick_launch: false,
6630 },
6631 window,
6632 cx,
6633 );
6634 }))
6635 .into_any_element()
6636 }
6637
6638 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6639 &self.context_menu
6640 }
6641
6642 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6643 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6644 cx.background_executor()
6645 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6646 .await;
6647
6648 let (start_buffer, start, _, end, newest_selection) = this
6649 .update(cx, |this, cx| {
6650 let newest_selection = this.selections.newest_anchor().clone();
6651 if newest_selection.head().diff_base_anchor.is_some() {
6652 return None;
6653 }
6654 let display_snapshot = this.display_snapshot(cx);
6655 let newest_selection_adjusted =
6656 this.selections.newest_adjusted(&display_snapshot);
6657 let buffer = this.buffer.read(cx);
6658
6659 let (start_buffer, start) =
6660 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6661 let (end_buffer, end) =
6662 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6663
6664 Some((start_buffer, start, end_buffer, end, newest_selection))
6665 })?
6666 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6667 .context(
6668 "Expected selection to lie in a single buffer when refreshing code actions",
6669 )?;
6670 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6671 let providers = this.code_action_providers.clone();
6672 let tasks = this
6673 .code_action_providers
6674 .iter()
6675 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6676 .collect::<Vec<_>>();
6677 (providers, tasks)
6678 })?;
6679
6680 let mut actions = Vec::new();
6681 for (provider, provider_actions) in
6682 providers.into_iter().zip(future::join_all(tasks).await)
6683 {
6684 if let Some(provider_actions) = provider_actions.log_err() {
6685 actions.extend(provider_actions.into_iter().map(|action| {
6686 AvailableCodeAction {
6687 excerpt_id: newest_selection.start.excerpt_id,
6688 action,
6689 provider: provider.clone(),
6690 }
6691 }));
6692 }
6693 }
6694
6695 this.update(cx, |this, cx| {
6696 this.available_code_actions = if actions.is_empty() {
6697 None
6698 } else {
6699 Some((
6700 Location {
6701 buffer: start_buffer,
6702 range: start..end,
6703 },
6704 actions.into(),
6705 ))
6706 };
6707 cx.notify();
6708 })
6709 }));
6710 }
6711
6712 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6713 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6714 self.show_git_blame_inline = false;
6715
6716 self.show_git_blame_inline_delay_task =
6717 Some(cx.spawn_in(window, async move |this, cx| {
6718 cx.background_executor().timer(delay).await;
6719
6720 this.update(cx, |this, cx| {
6721 this.show_git_blame_inline = true;
6722 cx.notify();
6723 })
6724 .log_err();
6725 }));
6726 }
6727 }
6728
6729 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6730 let snapshot = self.snapshot(window, cx);
6731 let cursor = self
6732 .selections
6733 .newest::<Point>(&snapshot.display_snapshot)
6734 .head();
6735 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6736 else {
6737 return;
6738 };
6739
6740 let Some(blame) = self.blame.as_ref() else {
6741 return;
6742 };
6743
6744 let row_info = RowInfo {
6745 buffer_id: Some(buffer.remote_id()),
6746 buffer_row: Some(point.row),
6747 ..Default::default()
6748 };
6749 let Some((buffer, blame_entry)) = blame
6750 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6751 .flatten()
6752 else {
6753 return;
6754 };
6755
6756 let anchor = self.selections.newest_anchor().head();
6757 let position = self.to_pixel_point(anchor, &snapshot, window);
6758 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6759 self.show_blame_popover(
6760 buffer,
6761 &blame_entry,
6762 position + last_bounds.origin,
6763 true,
6764 cx,
6765 );
6766 };
6767 }
6768
6769 fn show_blame_popover(
6770 &mut self,
6771 buffer: BufferId,
6772 blame_entry: &BlameEntry,
6773 position: gpui::Point<Pixels>,
6774 ignore_timeout: bool,
6775 cx: &mut Context<Self>,
6776 ) {
6777 if let Some(state) = &mut self.inline_blame_popover {
6778 state.hide_task.take();
6779 } else {
6780 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6781 let blame_entry = blame_entry.clone();
6782 let show_task = cx.spawn(async move |editor, cx| {
6783 if !ignore_timeout {
6784 cx.background_executor()
6785 .timer(std::time::Duration::from_millis(blame_popover_delay))
6786 .await;
6787 }
6788 editor
6789 .update(cx, |editor, cx| {
6790 editor.inline_blame_popover_show_task.take();
6791 let Some(blame) = editor.blame.as_ref() else {
6792 return;
6793 };
6794 let blame = blame.read(cx);
6795 let details = blame.details_for_entry(buffer, &blame_entry);
6796 let markdown = cx.new(|cx| {
6797 Markdown::new(
6798 details
6799 .as_ref()
6800 .map(|message| message.message.clone())
6801 .unwrap_or_default(),
6802 None,
6803 None,
6804 cx,
6805 )
6806 });
6807 editor.inline_blame_popover = Some(InlineBlamePopover {
6808 position,
6809 hide_task: None,
6810 popover_bounds: None,
6811 popover_state: InlineBlamePopoverState {
6812 scroll_handle: ScrollHandle::new(),
6813 commit_message: details,
6814 markdown,
6815 },
6816 keyboard_grace: ignore_timeout,
6817 });
6818 cx.notify();
6819 })
6820 .ok();
6821 });
6822 self.inline_blame_popover_show_task = Some(show_task);
6823 }
6824 }
6825
6826 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6827 self.inline_blame_popover_show_task.take();
6828 if let Some(state) = &mut self.inline_blame_popover {
6829 let hide_task = cx.spawn(async move |editor, cx| {
6830 if !ignore_timeout {
6831 cx.background_executor()
6832 .timer(std::time::Duration::from_millis(100))
6833 .await;
6834 }
6835 editor
6836 .update(cx, |editor, cx| {
6837 editor.inline_blame_popover.take();
6838 cx.notify();
6839 })
6840 .ok();
6841 });
6842 state.hide_task = Some(hide_task);
6843 true
6844 } else {
6845 false
6846 }
6847 }
6848
6849 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6850 if self.pending_rename.is_some() {
6851 return None;
6852 }
6853
6854 let provider = self.semantics_provider.clone()?;
6855 let buffer = self.buffer.read(cx);
6856 let newest_selection = self.selections.newest_anchor().clone();
6857 let cursor_position = newest_selection.head();
6858 let (cursor_buffer, cursor_buffer_position) =
6859 buffer.text_anchor_for_position(cursor_position, cx)?;
6860 let (tail_buffer, tail_buffer_position) =
6861 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6862 if cursor_buffer != tail_buffer {
6863 return None;
6864 }
6865
6866 let snapshot = cursor_buffer.read(cx).snapshot();
6867 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6868 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6869 if start_word_range != end_word_range {
6870 self.document_highlights_task.take();
6871 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6872 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6873 return None;
6874 }
6875
6876 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6877 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6878 cx.background_executor()
6879 .timer(Duration::from_millis(debounce))
6880 .await;
6881
6882 let highlights = if let Some(highlights) = cx
6883 .update(|cx| {
6884 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6885 })
6886 .ok()
6887 .flatten()
6888 {
6889 highlights.await.log_err()
6890 } else {
6891 None
6892 };
6893
6894 if let Some(highlights) = highlights {
6895 this.update(cx, |this, cx| {
6896 if this.pending_rename.is_some() {
6897 return;
6898 }
6899
6900 let buffer = this.buffer.read(cx);
6901 if buffer
6902 .text_anchor_for_position(cursor_position, cx)
6903 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6904 {
6905 return;
6906 }
6907
6908 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6909 let mut write_ranges = Vec::new();
6910 let mut read_ranges = Vec::new();
6911 for highlight in highlights {
6912 let buffer_id = cursor_buffer.read(cx).remote_id();
6913 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6914 {
6915 let start = highlight
6916 .range
6917 .start
6918 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6919 let end = highlight
6920 .range
6921 .end
6922 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6923 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6924 continue;
6925 }
6926
6927 let range =
6928 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6929 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6930 write_ranges.push(range);
6931 } else {
6932 read_ranges.push(range);
6933 }
6934 }
6935 }
6936
6937 this.highlight_background::<DocumentHighlightRead>(
6938 &read_ranges,
6939 |theme| theme.colors().editor_document_highlight_read_background,
6940 cx,
6941 );
6942 this.highlight_background::<DocumentHighlightWrite>(
6943 &write_ranges,
6944 |theme| theme.colors().editor_document_highlight_write_background,
6945 cx,
6946 );
6947 cx.notify();
6948 })
6949 .log_err();
6950 }
6951 }));
6952 None
6953 }
6954
6955 fn prepare_highlight_query_from_selection(
6956 &mut self,
6957 window: &Window,
6958 cx: &mut Context<Editor>,
6959 ) -> Option<(String, Range<Anchor>)> {
6960 if matches!(self.mode, EditorMode::SingleLine) {
6961 return None;
6962 }
6963 if !EditorSettings::get_global(cx).selection_highlight {
6964 return None;
6965 }
6966 if self.selections.count() != 1 || self.selections.line_mode() {
6967 return None;
6968 }
6969 let snapshot = self.snapshot(window, cx);
6970 let selection = self.selections.newest::<Point>(&snapshot);
6971 // If the selection spans multiple rows OR it is empty
6972 if selection.start.row != selection.end.row
6973 || selection.start.column == selection.end.column
6974 {
6975 return None;
6976 }
6977 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6978 let query = snapshot
6979 .buffer_snapshot()
6980 .text_for_range(selection_anchor_range.clone())
6981 .collect::<String>();
6982 if query.trim().is_empty() {
6983 return None;
6984 }
6985 Some((query, selection_anchor_range))
6986 }
6987
6988 fn update_selection_occurrence_highlights(
6989 &mut self,
6990 query_text: String,
6991 query_range: Range<Anchor>,
6992 multi_buffer_range_to_query: Range<Point>,
6993 use_debounce: bool,
6994 window: &mut Window,
6995 cx: &mut Context<Editor>,
6996 ) -> Task<()> {
6997 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6998 cx.spawn_in(window, async move |editor, cx| {
6999 if use_debounce {
7000 cx.background_executor()
7001 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7002 .await;
7003 }
7004 let match_task = cx.background_spawn(async move {
7005 let buffer_ranges = multi_buffer_snapshot
7006 .range_to_buffer_ranges(multi_buffer_range_to_query)
7007 .into_iter()
7008 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7009 let mut match_ranges = Vec::new();
7010 let Ok(regex) = project::search::SearchQuery::text(
7011 query_text.clone(),
7012 false,
7013 false,
7014 false,
7015 Default::default(),
7016 Default::default(),
7017 false,
7018 None,
7019 ) else {
7020 return Vec::default();
7021 };
7022 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7023 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7024 match_ranges.extend(
7025 regex
7026 .search(
7027 buffer_snapshot,
7028 Some(search_range.start.0..search_range.end.0),
7029 )
7030 .await
7031 .into_iter()
7032 .filter_map(|match_range| {
7033 let match_start = buffer_snapshot
7034 .anchor_after(search_range.start + match_range.start);
7035 let match_end = buffer_snapshot
7036 .anchor_before(search_range.start + match_range.end);
7037 let match_anchor_range = Anchor::range_in_buffer(
7038 excerpt_id,
7039 buffer_snapshot.remote_id(),
7040 match_start..match_end,
7041 );
7042 (match_anchor_range != query_range).then_some(match_anchor_range)
7043 }),
7044 );
7045 }
7046 match_ranges
7047 });
7048 let match_ranges = match_task.await;
7049 editor
7050 .update_in(cx, |editor, _, cx| {
7051 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7052 if !match_ranges.is_empty() {
7053 editor.highlight_background::<SelectedTextHighlight>(
7054 &match_ranges,
7055 |theme| theme.colors().editor_document_highlight_bracket_background,
7056 cx,
7057 )
7058 }
7059 })
7060 .log_err();
7061 })
7062 }
7063
7064 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7065 struct NewlineFold;
7066 let type_id = std::any::TypeId::of::<NewlineFold>();
7067 if !self.mode.is_single_line() {
7068 return;
7069 }
7070 let snapshot = self.snapshot(window, cx);
7071 if snapshot.buffer_snapshot().max_point().row == 0 {
7072 return;
7073 }
7074 let task = cx.background_spawn(async move {
7075 let new_newlines = snapshot
7076 .buffer_chars_at(MultiBufferOffset(0))
7077 .filter_map(|(c, i)| {
7078 if c == '\n' {
7079 Some(
7080 snapshot.buffer_snapshot().anchor_after(i)
7081 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7082 )
7083 } else {
7084 None
7085 }
7086 })
7087 .collect::<Vec<_>>();
7088 let existing_newlines = snapshot
7089 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7090 .filter_map(|fold| {
7091 if fold.placeholder.type_tag == Some(type_id) {
7092 Some(fold.range.start..fold.range.end)
7093 } else {
7094 None
7095 }
7096 })
7097 .collect::<Vec<_>>();
7098
7099 (new_newlines, existing_newlines)
7100 });
7101 self.folding_newlines = cx.spawn(async move |this, cx| {
7102 let (new_newlines, existing_newlines) = task.await;
7103 if new_newlines == existing_newlines {
7104 return;
7105 }
7106 let placeholder = FoldPlaceholder {
7107 render: Arc::new(move |_, _, cx| {
7108 div()
7109 .bg(cx.theme().status().hint_background)
7110 .border_b_1()
7111 .size_full()
7112 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7113 .border_color(cx.theme().status().hint)
7114 .child("\\n")
7115 .into_any()
7116 }),
7117 constrain_width: false,
7118 merge_adjacent: false,
7119 type_tag: Some(type_id),
7120 };
7121 let creases = new_newlines
7122 .into_iter()
7123 .map(|range| Crease::simple(range, placeholder.clone()))
7124 .collect();
7125 this.update(cx, |this, cx| {
7126 this.display_map.update(cx, |display_map, cx| {
7127 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7128 display_map.fold(creases, cx);
7129 });
7130 })
7131 .ok();
7132 });
7133 }
7134
7135 fn refresh_selected_text_highlights(
7136 &mut self,
7137 on_buffer_edit: bool,
7138 window: &mut Window,
7139 cx: &mut Context<Editor>,
7140 ) {
7141 let Some((query_text, query_range)) =
7142 self.prepare_highlight_query_from_selection(window, cx)
7143 else {
7144 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7145 self.quick_selection_highlight_task.take();
7146 self.debounced_selection_highlight_task.take();
7147 return;
7148 };
7149 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7150 if on_buffer_edit
7151 || self
7152 .quick_selection_highlight_task
7153 .as_ref()
7154 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7155 {
7156 let multi_buffer_visible_start = self
7157 .scroll_manager
7158 .anchor()
7159 .anchor
7160 .to_point(&multi_buffer_snapshot);
7161 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7162 multi_buffer_visible_start
7163 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7164 Bias::Left,
7165 );
7166 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7167 self.quick_selection_highlight_task = Some((
7168 query_range.clone(),
7169 self.update_selection_occurrence_highlights(
7170 query_text.clone(),
7171 query_range.clone(),
7172 multi_buffer_visible_range,
7173 false,
7174 window,
7175 cx,
7176 ),
7177 ));
7178 }
7179 if on_buffer_edit
7180 || self
7181 .debounced_selection_highlight_task
7182 .as_ref()
7183 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7184 {
7185 let multi_buffer_start = multi_buffer_snapshot
7186 .anchor_before(MultiBufferOffset(0))
7187 .to_point(&multi_buffer_snapshot);
7188 let multi_buffer_end = multi_buffer_snapshot
7189 .anchor_after(multi_buffer_snapshot.len())
7190 .to_point(&multi_buffer_snapshot);
7191 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7192 self.debounced_selection_highlight_task = Some((
7193 query_range.clone(),
7194 self.update_selection_occurrence_highlights(
7195 query_text,
7196 query_range,
7197 multi_buffer_full_range,
7198 true,
7199 window,
7200 cx,
7201 ),
7202 ));
7203 }
7204 }
7205
7206 pub fn refresh_edit_prediction(
7207 &mut self,
7208 debounce: bool,
7209 user_requested: bool,
7210 window: &mut Window,
7211 cx: &mut Context<Self>,
7212 ) -> Option<()> {
7213 if DisableAiSettings::get_global(cx).disable_ai {
7214 return None;
7215 }
7216
7217 let provider = self.edit_prediction_provider()?;
7218 let cursor = self.selections.newest_anchor().head();
7219 let (buffer, cursor_buffer_position) =
7220 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7221
7222 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7223 self.discard_edit_prediction(false, cx);
7224 return None;
7225 }
7226
7227 self.update_visible_edit_prediction(window, cx);
7228
7229 if !user_requested
7230 && (!self.should_show_edit_predictions()
7231 || !self.is_focused(window)
7232 || buffer.read(cx).is_empty())
7233 {
7234 self.discard_edit_prediction(false, cx);
7235 return None;
7236 }
7237
7238 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7239 Some(())
7240 }
7241
7242 fn show_edit_predictions_in_menu(&self) -> bool {
7243 match self.edit_prediction_settings {
7244 EditPredictionSettings::Disabled => false,
7245 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7246 }
7247 }
7248
7249 pub fn edit_predictions_enabled(&self) -> bool {
7250 match self.edit_prediction_settings {
7251 EditPredictionSettings::Disabled => false,
7252 EditPredictionSettings::Enabled { .. } => true,
7253 }
7254 }
7255
7256 fn edit_prediction_requires_modifier(&self) -> bool {
7257 match self.edit_prediction_settings {
7258 EditPredictionSettings::Disabled => false,
7259 EditPredictionSettings::Enabled {
7260 preview_requires_modifier,
7261 ..
7262 } => preview_requires_modifier,
7263 }
7264 }
7265
7266 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7267 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7268 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7269 self.discard_edit_prediction(false, cx);
7270 } else {
7271 let selection = self.selections.newest_anchor();
7272 let cursor = selection.head();
7273
7274 if let Some((buffer, cursor_buffer_position)) =
7275 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7276 {
7277 self.edit_prediction_settings =
7278 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7279 }
7280 }
7281 }
7282
7283 fn edit_prediction_settings_at_position(
7284 &self,
7285 buffer: &Entity<Buffer>,
7286 buffer_position: language::Anchor,
7287 cx: &App,
7288 ) -> EditPredictionSettings {
7289 if !self.mode.is_full()
7290 || !self.show_edit_predictions_override.unwrap_or(true)
7291 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7292 {
7293 return EditPredictionSettings::Disabled;
7294 }
7295
7296 let buffer = buffer.read(cx);
7297
7298 let file = buffer.file();
7299
7300 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7301 return EditPredictionSettings::Disabled;
7302 };
7303
7304 let by_provider = matches!(
7305 self.menu_edit_predictions_policy,
7306 MenuEditPredictionsPolicy::ByProvider
7307 );
7308
7309 let show_in_menu = by_provider
7310 && self
7311 .edit_prediction_provider
7312 .as_ref()
7313 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7314
7315 let preview_requires_modifier =
7316 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7317
7318 EditPredictionSettings::Enabled {
7319 show_in_menu,
7320 preview_requires_modifier,
7321 }
7322 }
7323
7324 fn should_show_edit_predictions(&self) -> bool {
7325 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7326 }
7327
7328 pub fn edit_prediction_preview_is_active(&self) -> bool {
7329 matches!(
7330 self.edit_prediction_preview,
7331 EditPredictionPreview::Active { .. }
7332 )
7333 }
7334
7335 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7336 let cursor = self.selections.newest_anchor().head();
7337 if let Some((buffer, cursor_position)) =
7338 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7339 {
7340 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7341 } else {
7342 false
7343 }
7344 }
7345
7346 pub fn supports_minimap(&self, cx: &App) -> bool {
7347 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7348 }
7349
7350 fn edit_predictions_enabled_in_buffer(
7351 &self,
7352 buffer: &Entity<Buffer>,
7353 buffer_position: language::Anchor,
7354 cx: &App,
7355 ) -> bool {
7356 maybe!({
7357 if self.read_only(cx) {
7358 return Some(false);
7359 }
7360 let provider = self.edit_prediction_provider()?;
7361 if !provider.is_enabled(buffer, buffer_position, cx) {
7362 return Some(false);
7363 }
7364 let buffer = buffer.read(cx);
7365 let Some(file) = buffer.file() else {
7366 return Some(true);
7367 };
7368 let settings = all_language_settings(Some(file), cx);
7369 Some(settings.edit_predictions_enabled_for_file(file, cx))
7370 })
7371 .unwrap_or(false)
7372 }
7373
7374 fn cycle_edit_prediction(
7375 &mut self,
7376 direction: Direction,
7377 window: &mut Window,
7378 cx: &mut Context<Self>,
7379 ) -> Option<()> {
7380 let provider = self.edit_prediction_provider()?;
7381 let cursor = self.selections.newest_anchor().head();
7382 let (buffer, cursor_buffer_position) =
7383 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7384 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7385 return None;
7386 }
7387
7388 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7389 self.update_visible_edit_prediction(window, cx);
7390
7391 Some(())
7392 }
7393
7394 pub fn show_edit_prediction(
7395 &mut self,
7396 _: &ShowEditPrediction,
7397 window: &mut Window,
7398 cx: &mut Context<Self>,
7399 ) {
7400 if !self.has_active_edit_prediction() {
7401 self.refresh_edit_prediction(false, true, window, cx);
7402 return;
7403 }
7404
7405 self.update_visible_edit_prediction(window, cx);
7406 }
7407
7408 pub fn display_cursor_names(
7409 &mut self,
7410 _: &DisplayCursorNames,
7411 window: &mut Window,
7412 cx: &mut Context<Self>,
7413 ) {
7414 self.show_cursor_names(window, cx);
7415 }
7416
7417 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7418 self.show_cursor_names = true;
7419 cx.notify();
7420 cx.spawn_in(window, async move |this, cx| {
7421 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7422 this.update(cx, |this, cx| {
7423 this.show_cursor_names = false;
7424 cx.notify()
7425 })
7426 .ok()
7427 })
7428 .detach();
7429 }
7430
7431 pub fn next_edit_prediction(
7432 &mut self,
7433 _: &NextEditPrediction,
7434 window: &mut Window,
7435 cx: &mut Context<Self>,
7436 ) {
7437 if self.has_active_edit_prediction() {
7438 self.cycle_edit_prediction(Direction::Next, 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 previous_edit_prediction(
7450 &mut self,
7451 _: &PreviousEditPrediction,
7452 window: &mut Window,
7453 cx: &mut Context<Self>,
7454 ) {
7455 if self.has_active_edit_prediction() {
7456 self.cycle_edit_prediction(Direction::Prev, window, cx);
7457 } else {
7458 let is_copilot_disabled = self
7459 .refresh_edit_prediction(false, true, window, cx)
7460 .is_none();
7461 if is_copilot_disabled {
7462 cx.propagate();
7463 }
7464 }
7465 }
7466
7467 pub fn accept_edit_prediction(
7468 &mut self,
7469 _: &AcceptEditPrediction,
7470 window: &mut Window,
7471 cx: &mut Context<Self>,
7472 ) {
7473 if self.show_edit_predictions_in_menu() {
7474 self.hide_context_menu(window, cx);
7475 }
7476
7477 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7478 return;
7479 };
7480
7481 match &active_edit_prediction.completion {
7482 EditPrediction::MoveWithin { target, .. } => {
7483 let target = *target;
7484
7485 if let Some(position_map) = &self.last_position_map {
7486 if position_map
7487 .visible_row_range
7488 .contains(&target.to_display_point(&position_map.snapshot).row())
7489 || !self.edit_prediction_requires_modifier()
7490 {
7491 self.unfold_ranges(&[target..target], true, false, cx);
7492 // Note that this is also done in vim's handler of the Tab action.
7493 self.change_selections(
7494 SelectionEffects::scroll(Autoscroll::newest()),
7495 window,
7496 cx,
7497 |selections| {
7498 selections.select_anchor_ranges([target..target]);
7499 },
7500 );
7501 self.clear_row_highlights::<EditPredictionPreview>();
7502
7503 self.edit_prediction_preview
7504 .set_previous_scroll_position(None);
7505 } else {
7506 self.edit_prediction_preview
7507 .set_previous_scroll_position(Some(
7508 position_map.snapshot.scroll_anchor,
7509 ));
7510
7511 self.highlight_rows::<EditPredictionPreview>(
7512 target..target,
7513 cx.theme().colors().editor_highlighted_line_background,
7514 RowHighlightOptions {
7515 autoscroll: true,
7516 ..Default::default()
7517 },
7518 cx,
7519 );
7520 self.request_autoscroll(Autoscroll::fit(), cx);
7521 }
7522 }
7523 }
7524 EditPrediction::MoveOutside { snapshot, target } => {
7525 if let Some(workspace) = self.workspace() {
7526 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7527 .detach_and_log_err(cx);
7528 }
7529 }
7530 EditPrediction::Edit { edits, .. } => {
7531 self.report_edit_prediction_event(
7532 active_edit_prediction.completion_id.clone(),
7533 true,
7534 cx,
7535 );
7536
7537 if let Some(provider) = self.edit_prediction_provider() {
7538 provider.accept(cx);
7539 }
7540
7541 // Store the transaction ID and selections before applying the edit
7542 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7543
7544 let snapshot = self.buffer.read(cx).snapshot(cx);
7545 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7546
7547 self.buffer.update(cx, |buffer, cx| {
7548 buffer.edit(edits.iter().cloned(), None, cx)
7549 });
7550
7551 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7552 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7553 });
7554
7555 let selections = self.selections.disjoint_anchors_arc();
7556 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7557 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7558 if has_new_transaction {
7559 self.selection_history
7560 .insert_transaction(transaction_id_now, selections);
7561 }
7562 }
7563
7564 self.update_visible_edit_prediction(window, cx);
7565 if self.active_edit_prediction.is_none() {
7566 self.refresh_edit_prediction(true, true, window, cx);
7567 }
7568
7569 cx.notify();
7570 }
7571 }
7572
7573 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7574 }
7575
7576 pub fn accept_partial_edit_prediction(
7577 &mut self,
7578 _: &AcceptPartialEditPrediction,
7579 window: &mut Window,
7580 cx: &mut Context<Self>,
7581 ) {
7582 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7583 return;
7584 };
7585 if self.selections.count() != 1 {
7586 return;
7587 }
7588
7589 match &active_edit_prediction.completion {
7590 EditPrediction::MoveWithin { target, .. } => {
7591 let target = *target;
7592 self.change_selections(
7593 SelectionEffects::scroll(Autoscroll::newest()),
7594 window,
7595 cx,
7596 |selections| {
7597 selections.select_anchor_ranges([target..target]);
7598 },
7599 );
7600 }
7601 EditPrediction::MoveOutside { snapshot, target } => {
7602 if let Some(workspace) = self.workspace() {
7603 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7604 .detach_and_log_err(cx);
7605 }
7606 }
7607 EditPrediction::Edit { edits, .. } => {
7608 self.report_edit_prediction_event(
7609 active_edit_prediction.completion_id.clone(),
7610 true,
7611 cx,
7612 );
7613
7614 // Find an insertion that starts at the cursor position.
7615 let snapshot = self.buffer.read(cx).snapshot(cx);
7616 let cursor_offset = self
7617 .selections
7618 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7619 .head();
7620 let insertion = edits.iter().find_map(|(range, text)| {
7621 let range = range.to_offset(&snapshot);
7622 if range.is_empty() && range.start == cursor_offset {
7623 Some(text)
7624 } else {
7625 None
7626 }
7627 });
7628
7629 if let Some(text) = insertion {
7630 let mut partial_completion = text
7631 .chars()
7632 .by_ref()
7633 .take_while(|c| c.is_alphabetic())
7634 .collect::<String>();
7635 if partial_completion.is_empty() {
7636 partial_completion = text
7637 .chars()
7638 .by_ref()
7639 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7640 .collect::<String>();
7641 }
7642
7643 cx.emit(EditorEvent::InputHandled {
7644 utf16_range_to_replace: None,
7645 text: partial_completion.clone().into(),
7646 });
7647
7648 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7649
7650 self.refresh_edit_prediction(true, true, window, cx);
7651 cx.notify();
7652 } else {
7653 self.accept_edit_prediction(&Default::default(), window, cx);
7654 }
7655 }
7656 }
7657 }
7658
7659 fn discard_edit_prediction(
7660 &mut self,
7661 should_report_edit_prediction_event: bool,
7662 cx: &mut Context<Self>,
7663 ) -> bool {
7664 if should_report_edit_prediction_event {
7665 let completion_id = self
7666 .active_edit_prediction
7667 .as_ref()
7668 .and_then(|active_completion| active_completion.completion_id.clone());
7669
7670 self.report_edit_prediction_event(completion_id, false, cx);
7671 }
7672
7673 if let Some(provider) = self.edit_prediction_provider() {
7674 provider.discard(cx);
7675 }
7676
7677 self.take_active_edit_prediction(cx)
7678 }
7679
7680 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7681 let Some(provider) = self.edit_prediction_provider() else {
7682 return;
7683 };
7684
7685 let Some((_, buffer, _)) = self
7686 .buffer
7687 .read(cx)
7688 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7689 else {
7690 return;
7691 };
7692
7693 let extension = buffer
7694 .read(cx)
7695 .file()
7696 .and_then(|file| Some(file.path().extension()?.to_string()));
7697
7698 let event_type = match accepted {
7699 true => "Edit Prediction Accepted",
7700 false => "Edit Prediction Discarded",
7701 };
7702 telemetry::event!(
7703 event_type,
7704 provider = provider.name(),
7705 prediction_id = id,
7706 suggestion_accepted = accepted,
7707 file_extension = extension,
7708 );
7709 }
7710
7711 fn open_editor_at_anchor(
7712 snapshot: &language::BufferSnapshot,
7713 target: language::Anchor,
7714 workspace: &Entity<Workspace>,
7715 window: &mut Window,
7716 cx: &mut App,
7717 ) -> Task<Result<()>> {
7718 workspace.update(cx, |workspace, cx| {
7719 let path = snapshot.file().map(|file| file.full_path(cx));
7720 let Some(path) =
7721 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7722 else {
7723 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7724 };
7725 let target = text::ToPoint::to_point(&target, snapshot);
7726 let item = workspace.open_path(path, None, true, window, cx);
7727 window.spawn(cx, async move |cx| {
7728 let Some(editor) = item.await?.downcast::<Editor>() else {
7729 return Ok(());
7730 };
7731 editor
7732 .update_in(cx, |editor, window, cx| {
7733 editor.go_to_singleton_buffer_point(target, window, cx);
7734 })
7735 .ok();
7736 anyhow::Ok(())
7737 })
7738 })
7739 }
7740
7741 pub fn has_active_edit_prediction(&self) -> bool {
7742 self.active_edit_prediction.is_some()
7743 }
7744
7745 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7746 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7747 return false;
7748 };
7749
7750 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7751 self.clear_highlights::<EditPredictionHighlight>(cx);
7752 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7753 true
7754 }
7755
7756 /// Returns true when we're displaying the edit prediction popover below the cursor
7757 /// like we are not previewing and the LSP autocomplete menu is visible
7758 /// or we are in `when_holding_modifier` mode.
7759 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7760 if self.edit_prediction_preview_is_active()
7761 || !self.show_edit_predictions_in_menu()
7762 || !self.edit_predictions_enabled()
7763 {
7764 return false;
7765 }
7766
7767 if self.has_visible_completions_menu() {
7768 return true;
7769 }
7770
7771 has_completion && self.edit_prediction_requires_modifier()
7772 }
7773
7774 fn handle_modifiers_changed(
7775 &mut self,
7776 modifiers: Modifiers,
7777 position_map: &PositionMap,
7778 window: &mut Window,
7779 cx: &mut Context<Self>,
7780 ) {
7781 // Ensure that the edit prediction preview is updated, even when not
7782 // enabled, if there's an active edit prediction preview.
7783 if self.show_edit_predictions_in_menu()
7784 || matches!(
7785 self.edit_prediction_preview,
7786 EditPredictionPreview::Active { .. }
7787 )
7788 {
7789 self.update_edit_prediction_preview(&modifiers, window, cx);
7790 }
7791
7792 self.update_selection_mode(&modifiers, position_map, window, cx);
7793
7794 let mouse_position = window.mouse_position();
7795 if !position_map.text_hitbox.is_hovered(window) {
7796 return;
7797 }
7798
7799 self.update_hovered_link(
7800 position_map.point_for_position(mouse_position),
7801 &position_map.snapshot,
7802 modifiers,
7803 window,
7804 cx,
7805 )
7806 }
7807
7808 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7809 match EditorSettings::get_global(cx).multi_cursor_modifier {
7810 MultiCursorModifier::Alt => modifiers.secondary(),
7811 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7812 }
7813 }
7814
7815 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7816 match EditorSettings::get_global(cx).multi_cursor_modifier {
7817 MultiCursorModifier::Alt => modifiers.alt,
7818 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7819 }
7820 }
7821
7822 fn columnar_selection_mode(
7823 modifiers: &Modifiers,
7824 cx: &mut Context<Self>,
7825 ) -> Option<ColumnarMode> {
7826 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7827 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7828 Some(ColumnarMode::FromMouse)
7829 } else if Self::is_alt_pressed(modifiers, cx) {
7830 Some(ColumnarMode::FromSelection)
7831 } else {
7832 None
7833 }
7834 } else {
7835 None
7836 }
7837 }
7838
7839 fn update_selection_mode(
7840 &mut self,
7841 modifiers: &Modifiers,
7842 position_map: &PositionMap,
7843 window: &mut Window,
7844 cx: &mut Context<Self>,
7845 ) {
7846 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7847 return;
7848 };
7849 if self.selections.pending_anchor().is_none() {
7850 return;
7851 }
7852
7853 let mouse_position = window.mouse_position();
7854 let point_for_position = position_map.point_for_position(mouse_position);
7855 let position = point_for_position.previous_valid;
7856
7857 self.select(
7858 SelectPhase::BeginColumnar {
7859 position,
7860 reset: false,
7861 mode,
7862 goal_column: point_for_position.exact_unclipped.column(),
7863 },
7864 window,
7865 cx,
7866 );
7867 }
7868
7869 fn update_edit_prediction_preview(
7870 &mut self,
7871 modifiers: &Modifiers,
7872 window: &mut Window,
7873 cx: &mut Context<Self>,
7874 ) {
7875 let mut modifiers_held = false;
7876 if let Some(accept_keystroke) = self
7877 .accept_edit_prediction_keybind(false, window, cx)
7878 .keystroke()
7879 {
7880 modifiers_held = modifiers_held
7881 || (accept_keystroke.modifiers() == modifiers
7882 && accept_keystroke.modifiers().modified());
7883 };
7884 if let Some(accept_partial_keystroke) = self
7885 .accept_edit_prediction_keybind(true, window, cx)
7886 .keystroke()
7887 {
7888 modifiers_held = modifiers_held
7889 || (accept_partial_keystroke.modifiers() == modifiers
7890 && accept_partial_keystroke.modifiers().modified());
7891 }
7892
7893 if modifiers_held {
7894 if matches!(
7895 self.edit_prediction_preview,
7896 EditPredictionPreview::Inactive { .. }
7897 ) {
7898 if let Some(provider) = self.edit_prediction_provider.as_ref() {
7899 provider.provider.did_show(cx)
7900 }
7901
7902 self.edit_prediction_preview = EditPredictionPreview::Active {
7903 previous_scroll_position: None,
7904 since: Instant::now(),
7905 };
7906
7907 self.update_visible_edit_prediction(window, cx);
7908 cx.notify();
7909 }
7910 } else if let EditPredictionPreview::Active {
7911 previous_scroll_position,
7912 since,
7913 } = self.edit_prediction_preview
7914 {
7915 if let (Some(previous_scroll_position), Some(position_map)) =
7916 (previous_scroll_position, self.last_position_map.as_ref())
7917 {
7918 self.set_scroll_position(
7919 previous_scroll_position
7920 .scroll_position(&position_map.snapshot.display_snapshot),
7921 window,
7922 cx,
7923 );
7924 }
7925
7926 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7927 released_too_fast: since.elapsed() < Duration::from_millis(200),
7928 };
7929 self.clear_row_highlights::<EditPredictionPreview>();
7930 self.update_visible_edit_prediction(window, cx);
7931 cx.notify();
7932 }
7933 }
7934
7935 fn update_visible_edit_prediction(
7936 &mut self,
7937 _window: &mut Window,
7938 cx: &mut Context<Self>,
7939 ) -> Option<()> {
7940 if DisableAiSettings::get_global(cx).disable_ai {
7941 return None;
7942 }
7943
7944 if self.ime_transaction.is_some() {
7945 self.discard_edit_prediction(false, cx);
7946 return None;
7947 }
7948
7949 let selection = self.selections.newest_anchor();
7950 let cursor = selection.head();
7951 let multibuffer = self.buffer.read(cx).snapshot(cx);
7952 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7953 let excerpt_id = cursor.excerpt_id;
7954
7955 let show_in_menu = self.show_edit_predictions_in_menu();
7956 let completions_menu_has_precedence = !show_in_menu
7957 && (self.context_menu.borrow().is_some()
7958 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7959
7960 if completions_menu_has_precedence
7961 || !offset_selection.is_empty()
7962 || self
7963 .active_edit_prediction
7964 .as_ref()
7965 .is_some_and(|completion| {
7966 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7967 return false;
7968 };
7969 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7970 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7971 !invalidation_range.contains(&offset_selection.head())
7972 })
7973 {
7974 self.discard_edit_prediction(false, cx);
7975 return None;
7976 }
7977
7978 self.take_active_edit_prediction(cx);
7979 let Some(provider) = self.edit_prediction_provider() else {
7980 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7981 return None;
7982 };
7983
7984 let (buffer, cursor_buffer_position) =
7985 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7986
7987 self.edit_prediction_settings =
7988 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7989
7990 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7991
7992 if self.edit_prediction_indent_conflict {
7993 let cursor_point = cursor.to_point(&multibuffer);
7994
7995 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7996
7997 if let Some((_, indent)) = indents.iter().next()
7998 && indent.len == cursor_point.column
7999 {
8000 self.edit_prediction_indent_conflict = false;
8001 }
8002 }
8003
8004 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8005
8006 let (completion_id, edits, edit_preview) = match edit_prediction {
8007 edit_prediction::EditPrediction::Local {
8008 id,
8009 edits,
8010 edit_preview,
8011 } => (id, edits, edit_preview),
8012 edit_prediction::EditPrediction::Jump {
8013 id,
8014 snapshot,
8015 target,
8016 } => {
8017 self.stale_edit_prediction_in_menu = None;
8018 self.active_edit_prediction = Some(EditPredictionState {
8019 inlay_ids: vec![],
8020 completion: EditPrediction::MoveOutside { snapshot, target },
8021 completion_id: id,
8022 invalidation_range: None,
8023 });
8024 cx.notify();
8025 return Some(());
8026 }
8027 };
8028
8029 let edits = edits
8030 .into_iter()
8031 .flat_map(|(range, new_text)| {
8032 Some((
8033 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8034 new_text,
8035 ))
8036 })
8037 .collect::<Vec<_>>();
8038 if edits.is_empty() {
8039 return None;
8040 }
8041
8042 let first_edit_start = edits.first().unwrap().0.start;
8043 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8044 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8045
8046 let last_edit_end = edits.last().unwrap().0.end;
8047 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8048 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8049
8050 let cursor_row = cursor.to_point(&multibuffer).row;
8051
8052 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8053
8054 let mut inlay_ids = Vec::new();
8055 let invalidation_row_range;
8056 let move_invalidation_row_range = if cursor_row < edit_start_row {
8057 Some(cursor_row..edit_end_row)
8058 } else if cursor_row > edit_end_row {
8059 Some(edit_start_row..cursor_row)
8060 } else {
8061 None
8062 };
8063 let supports_jump = self
8064 .edit_prediction_provider
8065 .as_ref()
8066 .map(|provider| provider.provider.supports_jump_to_edit())
8067 .unwrap_or(true);
8068
8069 let is_move = supports_jump
8070 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8071 let completion = if is_move {
8072 invalidation_row_range =
8073 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8074 let target = first_edit_start;
8075 EditPrediction::MoveWithin { target, snapshot }
8076 } else {
8077 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8078 && !self.edit_predictions_hidden_for_vim_mode;
8079
8080 if show_completions_in_buffer {
8081 if let Some(provider) = &self.edit_prediction_provider {
8082 provider.provider.did_show(cx);
8083 }
8084 if edits
8085 .iter()
8086 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8087 {
8088 let mut inlays = Vec::new();
8089 for (range, new_text) in &edits {
8090 let inlay = Inlay::edit_prediction(
8091 post_inc(&mut self.next_inlay_id),
8092 range.start,
8093 new_text.as_ref(),
8094 );
8095 inlay_ids.push(inlay.id);
8096 inlays.push(inlay);
8097 }
8098
8099 self.splice_inlays(&[], inlays, cx);
8100 } else {
8101 let background_color = cx.theme().status().deleted_background;
8102 self.highlight_text::<EditPredictionHighlight>(
8103 edits.iter().map(|(range, _)| range.clone()).collect(),
8104 HighlightStyle {
8105 background_color: Some(background_color),
8106 ..Default::default()
8107 },
8108 cx,
8109 );
8110 }
8111 }
8112
8113 invalidation_row_range = edit_start_row..edit_end_row;
8114
8115 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8116 if provider.show_tab_accept_marker() {
8117 EditDisplayMode::TabAccept
8118 } else {
8119 EditDisplayMode::Inline
8120 }
8121 } else {
8122 EditDisplayMode::DiffPopover
8123 };
8124
8125 EditPrediction::Edit {
8126 edits,
8127 edit_preview,
8128 display_mode,
8129 snapshot,
8130 }
8131 };
8132
8133 let invalidation_range = multibuffer
8134 .anchor_before(Point::new(invalidation_row_range.start, 0))
8135 ..multibuffer.anchor_after(Point::new(
8136 invalidation_row_range.end,
8137 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8138 ));
8139
8140 self.stale_edit_prediction_in_menu = None;
8141 self.active_edit_prediction = Some(EditPredictionState {
8142 inlay_ids,
8143 completion,
8144 completion_id,
8145 invalidation_range: Some(invalidation_range),
8146 });
8147
8148 cx.notify();
8149
8150 Some(())
8151 }
8152
8153 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8154 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8155 }
8156
8157 fn clear_tasks(&mut self) {
8158 self.tasks.clear()
8159 }
8160
8161 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8162 if self.tasks.insert(key, value).is_some() {
8163 // This case should hopefully be rare, but just in case...
8164 log::error!(
8165 "multiple different run targets found on a single line, only the last target will be rendered"
8166 )
8167 }
8168 }
8169
8170 /// Get all display points of breakpoints that will be rendered within editor
8171 ///
8172 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8173 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8174 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8175 fn active_breakpoints(
8176 &self,
8177 range: Range<DisplayRow>,
8178 window: &mut Window,
8179 cx: &mut Context<Self>,
8180 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8181 let mut breakpoint_display_points = HashMap::default();
8182
8183 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8184 return breakpoint_display_points;
8185 };
8186
8187 let snapshot = self.snapshot(window, cx);
8188
8189 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8190 let Some(project) = self.project() else {
8191 return breakpoint_display_points;
8192 };
8193
8194 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8195 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8196
8197 for (buffer_snapshot, range, excerpt_id) in
8198 multi_buffer_snapshot.range_to_buffer_ranges(range)
8199 {
8200 let Some(buffer) = project
8201 .read(cx)
8202 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8203 else {
8204 continue;
8205 };
8206 let breakpoints = breakpoint_store.read(cx).breakpoints(
8207 &buffer,
8208 Some(
8209 buffer_snapshot.anchor_before(range.start)
8210 ..buffer_snapshot.anchor_after(range.end),
8211 ),
8212 buffer_snapshot,
8213 cx,
8214 );
8215 for (breakpoint, state) in breakpoints {
8216 let multi_buffer_anchor =
8217 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8218 let position = multi_buffer_anchor
8219 .to_point(&multi_buffer_snapshot)
8220 .to_display_point(&snapshot);
8221
8222 breakpoint_display_points.insert(
8223 position.row(),
8224 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8225 );
8226 }
8227 }
8228
8229 breakpoint_display_points
8230 }
8231
8232 fn breakpoint_context_menu(
8233 &self,
8234 anchor: Anchor,
8235 window: &mut Window,
8236 cx: &mut Context<Self>,
8237 ) -> Entity<ui::ContextMenu> {
8238 let weak_editor = cx.weak_entity();
8239 let focus_handle = self.focus_handle(cx);
8240
8241 let row = self
8242 .buffer
8243 .read(cx)
8244 .snapshot(cx)
8245 .summary_for_anchor::<Point>(&anchor)
8246 .row;
8247
8248 let breakpoint = self
8249 .breakpoint_at_row(row, window, cx)
8250 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8251
8252 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8253 "Edit Log Breakpoint"
8254 } else {
8255 "Set Log Breakpoint"
8256 };
8257
8258 let condition_breakpoint_msg = if breakpoint
8259 .as_ref()
8260 .is_some_and(|bp| bp.1.condition.is_some())
8261 {
8262 "Edit Condition Breakpoint"
8263 } else {
8264 "Set Condition Breakpoint"
8265 };
8266
8267 let hit_condition_breakpoint_msg = if breakpoint
8268 .as_ref()
8269 .is_some_and(|bp| bp.1.hit_condition.is_some())
8270 {
8271 "Edit Hit Condition Breakpoint"
8272 } else {
8273 "Set Hit Condition Breakpoint"
8274 };
8275
8276 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8277 "Unset Breakpoint"
8278 } else {
8279 "Set Breakpoint"
8280 };
8281
8282 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8283
8284 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8285 BreakpointState::Enabled => Some("Disable"),
8286 BreakpointState::Disabled => Some("Enable"),
8287 });
8288
8289 let (anchor, breakpoint) =
8290 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8291
8292 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8293 menu.on_blur_subscription(Subscription::new(|| {}))
8294 .context(focus_handle)
8295 .when(run_to_cursor, |this| {
8296 let weak_editor = weak_editor.clone();
8297 this.entry("Run to cursor", None, move |window, cx| {
8298 weak_editor
8299 .update(cx, |editor, cx| {
8300 editor.change_selections(
8301 SelectionEffects::no_scroll(),
8302 window,
8303 cx,
8304 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8305 );
8306 })
8307 .ok();
8308
8309 window.dispatch_action(Box::new(RunToCursor), cx);
8310 })
8311 .separator()
8312 })
8313 .when_some(toggle_state_msg, |this, msg| {
8314 this.entry(msg, None, {
8315 let weak_editor = weak_editor.clone();
8316 let breakpoint = breakpoint.clone();
8317 move |_window, cx| {
8318 weak_editor
8319 .update(cx, |this, cx| {
8320 this.edit_breakpoint_at_anchor(
8321 anchor,
8322 breakpoint.as_ref().clone(),
8323 BreakpointEditAction::InvertState,
8324 cx,
8325 );
8326 })
8327 .log_err();
8328 }
8329 })
8330 })
8331 .entry(set_breakpoint_msg, None, {
8332 let weak_editor = weak_editor.clone();
8333 let breakpoint = breakpoint.clone();
8334 move |_window, cx| {
8335 weak_editor
8336 .update(cx, |this, cx| {
8337 this.edit_breakpoint_at_anchor(
8338 anchor,
8339 breakpoint.as_ref().clone(),
8340 BreakpointEditAction::Toggle,
8341 cx,
8342 );
8343 })
8344 .log_err();
8345 }
8346 })
8347 .entry(log_breakpoint_msg, None, {
8348 let breakpoint = breakpoint.clone();
8349 let weak_editor = weak_editor.clone();
8350 move |window, cx| {
8351 weak_editor
8352 .update(cx, |this, cx| {
8353 this.add_edit_breakpoint_block(
8354 anchor,
8355 breakpoint.as_ref(),
8356 BreakpointPromptEditAction::Log,
8357 window,
8358 cx,
8359 );
8360 })
8361 .log_err();
8362 }
8363 })
8364 .entry(condition_breakpoint_msg, None, {
8365 let breakpoint = breakpoint.clone();
8366 let weak_editor = weak_editor.clone();
8367 move |window, cx| {
8368 weak_editor
8369 .update(cx, |this, cx| {
8370 this.add_edit_breakpoint_block(
8371 anchor,
8372 breakpoint.as_ref(),
8373 BreakpointPromptEditAction::Condition,
8374 window,
8375 cx,
8376 );
8377 })
8378 .log_err();
8379 }
8380 })
8381 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8382 weak_editor
8383 .update(cx, |this, cx| {
8384 this.add_edit_breakpoint_block(
8385 anchor,
8386 breakpoint.as_ref(),
8387 BreakpointPromptEditAction::HitCondition,
8388 window,
8389 cx,
8390 );
8391 })
8392 .log_err();
8393 })
8394 })
8395 }
8396
8397 fn render_breakpoint(
8398 &self,
8399 position: Anchor,
8400 row: DisplayRow,
8401 breakpoint: &Breakpoint,
8402 state: Option<BreakpointSessionState>,
8403 cx: &mut Context<Self>,
8404 ) -> IconButton {
8405 let is_rejected = state.is_some_and(|s| !s.verified);
8406 // Is it a breakpoint that shows up when hovering over gutter?
8407 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8408 (false, false),
8409 |PhantomBreakpointIndicator {
8410 is_active,
8411 display_row,
8412 collides_with_existing_breakpoint,
8413 }| {
8414 (
8415 is_active && display_row == row,
8416 collides_with_existing_breakpoint,
8417 )
8418 },
8419 );
8420
8421 let (color, icon) = {
8422 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8423 (false, false) => ui::IconName::DebugBreakpoint,
8424 (true, false) => ui::IconName::DebugLogBreakpoint,
8425 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8426 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8427 };
8428
8429 let color = if is_phantom {
8430 Color::Hint
8431 } else if is_rejected {
8432 Color::Disabled
8433 } else {
8434 Color::Debugger
8435 };
8436
8437 (color, icon)
8438 };
8439
8440 let breakpoint = Arc::from(breakpoint.clone());
8441
8442 let alt_as_text = gpui::Keystroke {
8443 modifiers: Modifiers::secondary_key(),
8444 ..Default::default()
8445 };
8446 let primary_action_text = if breakpoint.is_disabled() {
8447 "Enable breakpoint"
8448 } else if is_phantom && !collides_with_existing {
8449 "Set breakpoint"
8450 } else {
8451 "Unset breakpoint"
8452 };
8453 let focus_handle = self.focus_handle.clone();
8454
8455 let meta = if is_rejected {
8456 SharedString::from("No executable code is associated with this line.")
8457 } else if collides_with_existing && !breakpoint.is_disabled() {
8458 SharedString::from(format!(
8459 "{alt_as_text}-click to disable,\nright-click for more options."
8460 ))
8461 } else {
8462 SharedString::from("Right-click for more options.")
8463 };
8464 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8465 .icon_size(IconSize::XSmall)
8466 .size(ui::ButtonSize::None)
8467 .when(is_rejected, |this| {
8468 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8469 })
8470 .icon_color(color)
8471 .style(ButtonStyle::Transparent)
8472 .on_click(cx.listener({
8473 move |editor, event: &ClickEvent, window, cx| {
8474 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8475 BreakpointEditAction::InvertState
8476 } else {
8477 BreakpointEditAction::Toggle
8478 };
8479
8480 window.focus(&editor.focus_handle(cx));
8481 editor.edit_breakpoint_at_anchor(
8482 position,
8483 breakpoint.as_ref().clone(),
8484 edit_action,
8485 cx,
8486 );
8487 }
8488 }))
8489 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8490 editor.set_breakpoint_context_menu(
8491 row,
8492 Some(position),
8493 event.position(),
8494 window,
8495 cx,
8496 );
8497 }))
8498 .tooltip(move |_window, cx| {
8499 Tooltip::with_meta_in(
8500 primary_action_text,
8501 Some(&ToggleBreakpoint),
8502 meta.clone(),
8503 &focus_handle,
8504 cx,
8505 )
8506 })
8507 }
8508
8509 fn build_tasks_context(
8510 project: &Entity<Project>,
8511 buffer: &Entity<Buffer>,
8512 buffer_row: u32,
8513 tasks: &Arc<RunnableTasks>,
8514 cx: &mut Context<Self>,
8515 ) -> Task<Option<task::TaskContext>> {
8516 let position = Point::new(buffer_row, tasks.column);
8517 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8518 let location = Location {
8519 buffer: buffer.clone(),
8520 range: range_start..range_start,
8521 };
8522 // Fill in the environmental variables from the tree-sitter captures
8523 let mut captured_task_variables = TaskVariables::default();
8524 for (capture_name, value) in tasks.extra_variables.clone() {
8525 captured_task_variables.insert(
8526 task::VariableName::Custom(capture_name.into()),
8527 value.clone(),
8528 );
8529 }
8530 project.update(cx, |project, cx| {
8531 project.task_store().update(cx, |task_store, cx| {
8532 task_store.task_context_for_location(captured_task_variables, location, cx)
8533 })
8534 })
8535 }
8536
8537 pub fn spawn_nearest_task(
8538 &mut self,
8539 action: &SpawnNearestTask,
8540 window: &mut Window,
8541 cx: &mut Context<Self>,
8542 ) {
8543 let Some((workspace, _)) = self.workspace.clone() else {
8544 return;
8545 };
8546 let Some(project) = self.project.clone() else {
8547 return;
8548 };
8549
8550 // Try to find a closest, enclosing node using tree-sitter that has a task
8551 let Some((buffer, buffer_row, tasks)) = self
8552 .find_enclosing_node_task(cx)
8553 // Or find the task that's closest in row-distance.
8554 .or_else(|| self.find_closest_task(cx))
8555 else {
8556 return;
8557 };
8558
8559 let reveal_strategy = action.reveal;
8560 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8561 cx.spawn_in(window, async move |_, cx| {
8562 let context = task_context.await?;
8563 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8564
8565 let resolved = &mut resolved_task.resolved;
8566 resolved.reveal = reveal_strategy;
8567
8568 workspace
8569 .update_in(cx, |workspace, window, cx| {
8570 workspace.schedule_resolved_task(
8571 task_source_kind,
8572 resolved_task,
8573 false,
8574 window,
8575 cx,
8576 );
8577 })
8578 .ok()
8579 })
8580 .detach();
8581 }
8582
8583 fn find_closest_task(
8584 &mut self,
8585 cx: &mut Context<Self>,
8586 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8587 let cursor_row = self
8588 .selections
8589 .newest_adjusted(&self.display_snapshot(cx))
8590 .head()
8591 .row;
8592
8593 let ((buffer_id, row), tasks) = self
8594 .tasks
8595 .iter()
8596 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8597
8598 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8599 let tasks = Arc::new(tasks.to_owned());
8600 Some((buffer, *row, tasks))
8601 }
8602
8603 fn find_enclosing_node_task(
8604 &mut self,
8605 cx: &mut Context<Self>,
8606 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8607 let snapshot = self.buffer.read(cx).snapshot(cx);
8608 let offset = self
8609 .selections
8610 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8611 .head();
8612 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8613 let offset = excerpt.map_offset_to_buffer(offset);
8614 let buffer_id = excerpt.buffer().remote_id();
8615
8616 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8617 let mut cursor = layer.node().walk();
8618
8619 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8620 if cursor.node().end_byte() == offset.0 {
8621 cursor.goto_next_sibling();
8622 }
8623 }
8624
8625 // Ascend to the smallest ancestor that contains the range and has a task.
8626 loop {
8627 let node = cursor.node();
8628 let node_range = node.byte_range();
8629 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8630
8631 // Check if this node contains our offset
8632 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8633 // If it contains offset, check for task
8634 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8635 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8636 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8637 }
8638 }
8639
8640 if !cursor.goto_parent() {
8641 break;
8642 }
8643 }
8644 None
8645 }
8646
8647 fn render_run_indicator(
8648 &self,
8649 _style: &EditorStyle,
8650 is_active: bool,
8651 row: DisplayRow,
8652 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8653 cx: &mut Context<Self>,
8654 ) -> IconButton {
8655 let color = Color::Muted;
8656 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8657
8658 IconButton::new(
8659 ("run_indicator", row.0 as usize),
8660 ui::IconName::PlayOutlined,
8661 )
8662 .shape(ui::IconButtonShape::Square)
8663 .icon_size(IconSize::XSmall)
8664 .icon_color(color)
8665 .toggle_state(is_active)
8666 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8667 let quick_launch = match e {
8668 ClickEvent::Keyboard(_) => true,
8669 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8670 };
8671
8672 window.focus(&editor.focus_handle(cx));
8673 editor.toggle_code_actions(
8674 &ToggleCodeActions {
8675 deployed_from: Some(CodeActionSource::RunMenu(row)),
8676 quick_launch,
8677 },
8678 window,
8679 cx,
8680 );
8681 }))
8682 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8683 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8684 }))
8685 }
8686
8687 pub fn context_menu_visible(&self) -> bool {
8688 !self.edit_prediction_preview_is_active()
8689 && self
8690 .context_menu
8691 .borrow()
8692 .as_ref()
8693 .is_some_and(|menu| menu.visible())
8694 }
8695
8696 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8697 self.context_menu
8698 .borrow()
8699 .as_ref()
8700 .map(|menu| menu.origin())
8701 }
8702
8703 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8704 self.context_menu_options = Some(options);
8705 }
8706
8707 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8708 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8709
8710 fn render_edit_prediction_popover(
8711 &mut self,
8712 text_bounds: &Bounds<Pixels>,
8713 content_origin: gpui::Point<Pixels>,
8714 right_margin: Pixels,
8715 editor_snapshot: &EditorSnapshot,
8716 visible_row_range: Range<DisplayRow>,
8717 scroll_top: ScrollOffset,
8718 scroll_bottom: ScrollOffset,
8719 line_layouts: &[LineWithInvisibles],
8720 line_height: Pixels,
8721 scroll_position: gpui::Point<ScrollOffset>,
8722 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8723 newest_selection_head: Option<DisplayPoint>,
8724 editor_width: Pixels,
8725 style: &EditorStyle,
8726 window: &mut Window,
8727 cx: &mut App,
8728 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8729 if self.mode().is_minimap() {
8730 return None;
8731 }
8732 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8733
8734 if self.edit_prediction_visible_in_cursor_popover(true) {
8735 return None;
8736 }
8737
8738 match &active_edit_prediction.completion {
8739 EditPrediction::MoveWithin { target, .. } => {
8740 let target_display_point = target.to_display_point(editor_snapshot);
8741
8742 if self.edit_prediction_requires_modifier() {
8743 if !self.edit_prediction_preview_is_active() {
8744 return None;
8745 }
8746
8747 self.render_edit_prediction_modifier_jump_popover(
8748 text_bounds,
8749 content_origin,
8750 visible_row_range,
8751 line_layouts,
8752 line_height,
8753 scroll_pixel_position,
8754 newest_selection_head,
8755 target_display_point,
8756 window,
8757 cx,
8758 )
8759 } else {
8760 self.render_edit_prediction_eager_jump_popover(
8761 text_bounds,
8762 content_origin,
8763 editor_snapshot,
8764 visible_row_range,
8765 scroll_top,
8766 scroll_bottom,
8767 line_height,
8768 scroll_pixel_position,
8769 target_display_point,
8770 editor_width,
8771 window,
8772 cx,
8773 )
8774 }
8775 }
8776 EditPrediction::Edit {
8777 display_mode: EditDisplayMode::Inline,
8778 ..
8779 } => None,
8780 EditPrediction::Edit {
8781 display_mode: EditDisplayMode::TabAccept,
8782 edits,
8783 ..
8784 } => {
8785 let range = &edits.first()?.0;
8786 let target_display_point = range.end.to_display_point(editor_snapshot);
8787
8788 self.render_edit_prediction_end_of_line_popover(
8789 "Accept",
8790 editor_snapshot,
8791 visible_row_range,
8792 target_display_point,
8793 line_height,
8794 scroll_pixel_position,
8795 content_origin,
8796 editor_width,
8797 window,
8798 cx,
8799 )
8800 }
8801 EditPrediction::Edit {
8802 edits,
8803 edit_preview,
8804 display_mode: EditDisplayMode::DiffPopover,
8805 snapshot,
8806 } => self.render_edit_prediction_diff_popover(
8807 text_bounds,
8808 content_origin,
8809 right_margin,
8810 editor_snapshot,
8811 visible_row_range,
8812 line_layouts,
8813 line_height,
8814 scroll_position,
8815 scroll_pixel_position,
8816 newest_selection_head,
8817 editor_width,
8818 style,
8819 edits,
8820 edit_preview,
8821 snapshot,
8822 window,
8823 cx,
8824 ),
8825 EditPrediction::MoveOutside { snapshot, .. } => {
8826 let file_name = snapshot
8827 .file()
8828 .map(|file| file.file_name(cx))
8829 .unwrap_or("untitled");
8830 let mut element = self
8831 .render_edit_prediction_line_popover(
8832 format!("Jump to {file_name}"),
8833 Some(IconName::ZedPredict),
8834 window,
8835 cx,
8836 )
8837 .into_any();
8838
8839 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8840 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8841 let origin_y = text_bounds.size.height - size.height - px(30.);
8842 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8843 element.prepaint_at(origin, window, cx);
8844
8845 Some((element, origin))
8846 }
8847 }
8848 }
8849
8850 fn render_edit_prediction_modifier_jump_popover(
8851 &mut self,
8852 text_bounds: &Bounds<Pixels>,
8853 content_origin: gpui::Point<Pixels>,
8854 visible_row_range: Range<DisplayRow>,
8855 line_layouts: &[LineWithInvisibles],
8856 line_height: Pixels,
8857 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8858 newest_selection_head: Option<DisplayPoint>,
8859 target_display_point: DisplayPoint,
8860 window: &mut Window,
8861 cx: &mut App,
8862 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8863 let scrolled_content_origin =
8864 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8865
8866 const SCROLL_PADDING_Y: Pixels = px(12.);
8867
8868 if target_display_point.row() < visible_row_range.start {
8869 return self.render_edit_prediction_scroll_popover(
8870 |_| SCROLL_PADDING_Y,
8871 IconName::ArrowUp,
8872 visible_row_range,
8873 line_layouts,
8874 newest_selection_head,
8875 scrolled_content_origin,
8876 window,
8877 cx,
8878 );
8879 } else if target_display_point.row() >= visible_row_range.end {
8880 return self.render_edit_prediction_scroll_popover(
8881 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8882 IconName::ArrowDown,
8883 visible_row_range,
8884 line_layouts,
8885 newest_selection_head,
8886 scrolled_content_origin,
8887 window,
8888 cx,
8889 );
8890 }
8891
8892 const POLE_WIDTH: Pixels = px(2.);
8893
8894 let line_layout =
8895 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8896 let target_column = target_display_point.column() as usize;
8897
8898 let target_x = line_layout.x_for_index(target_column);
8899 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8900 - scroll_pixel_position.y;
8901
8902 let flag_on_right = target_x < text_bounds.size.width / 2.;
8903
8904 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8905 border_color.l += 0.001;
8906
8907 let mut element = v_flex()
8908 .items_end()
8909 .when(flag_on_right, |el| el.items_start())
8910 .child(if flag_on_right {
8911 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8912 .rounded_bl(px(0.))
8913 .rounded_tl(px(0.))
8914 .border_l_2()
8915 .border_color(border_color)
8916 } else {
8917 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8918 .rounded_br(px(0.))
8919 .rounded_tr(px(0.))
8920 .border_r_2()
8921 .border_color(border_color)
8922 })
8923 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8924 .into_any();
8925
8926 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8927
8928 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8929 - point(
8930 if flag_on_right {
8931 POLE_WIDTH
8932 } else {
8933 size.width - POLE_WIDTH
8934 },
8935 size.height - line_height,
8936 );
8937
8938 origin.x = origin.x.max(content_origin.x);
8939
8940 element.prepaint_at(origin, window, cx);
8941
8942 Some((element, origin))
8943 }
8944
8945 fn render_edit_prediction_scroll_popover(
8946 &mut self,
8947 to_y: impl Fn(Size<Pixels>) -> Pixels,
8948 scroll_icon: IconName,
8949 visible_row_range: Range<DisplayRow>,
8950 line_layouts: &[LineWithInvisibles],
8951 newest_selection_head: Option<DisplayPoint>,
8952 scrolled_content_origin: gpui::Point<Pixels>,
8953 window: &mut Window,
8954 cx: &mut App,
8955 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8956 let mut element = self
8957 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8958 .into_any();
8959
8960 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8961
8962 let cursor = newest_selection_head?;
8963 let cursor_row_layout =
8964 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8965 let cursor_column = cursor.column() as usize;
8966
8967 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8968
8969 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8970
8971 element.prepaint_at(origin, window, cx);
8972 Some((element, origin))
8973 }
8974
8975 fn render_edit_prediction_eager_jump_popover(
8976 &mut self,
8977 text_bounds: &Bounds<Pixels>,
8978 content_origin: gpui::Point<Pixels>,
8979 editor_snapshot: &EditorSnapshot,
8980 visible_row_range: Range<DisplayRow>,
8981 scroll_top: ScrollOffset,
8982 scroll_bottom: ScrollOffset,
8983 line_height: Pixels,
8984 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8985 target_display_point: DisplayPoint,
8986 editor_width: Pixels,
8987 window: &mut Window,
8988 cx: &mut App,
8989 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8990 if target_display_point.row().as_f64() < scroll_top {
8991 let mut element = self
8992 .render_edit_prediction_line_popover(
8993 "Jump to Edit",
8994 Some(IconName::ArrowUp),
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 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 if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9010 let mut element = self
9011 .render_edit_prediction_line_popover(
9012 "Jump to Edit",
9013 Some(IconName::ArrowDown),
9014 window,
9015 cx,
9016 )
9017 .into_any();
9018
9019 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9020 let offset = point(
9021 (text_bounds.size.width - size.width) / 2.,
9022 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9023 );
9024
9025 let origin = text_bounds.origin + offset;
9026 element.prepaint_at(origin, window, cx);
9027 Some((element, origin))
9028 } else {
9029 self.render_edit_prediction_end_of_line_popover(
9030 "Jump to Edit",
9031 editor_snapshot,
9032 visible_row_range,
9033 target_display_point,
9034 line_height,
9035 scroll_pixel_position,
9036 content_origin,
9037 editor_width,
9038 window,
9039 cx,
9040 )
9041 }
9042 }
9043
9044 fn render_edit_prediction_end_of_line_popover(
9045 self: &mut Editor,
9046 label: &'static str,
9047 editor_snapshot: &EditorSnapshot,
9048 visible_row_range: Range<DisplayRow>,
9049 target_display_point: DisplayPoint,
9050 line_height: Pixels,
9051 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9052 content_origin: gpui::Point<Pixels>,
9053 editor_width: Pixels,
9054 window: &mut Window,
9055 cx: &mut App,
9056 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9057 let target_line_end = DisplayPoint::new(
9058 target_display_point.row(),
9059 editor_snapshot.line_len(target_display_point.row()),
9060 );
9061
9062 let mut element = self
9063 .render_edit_prediction_line_popover(label, None, window, cx)
9064 .into_any();
9065
9066 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9067
9068 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9069
9070 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9071 let mut origin = start_point
9072 + line_origin
9073 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9074 origin.x = origin.x.max(content_origin.x);
9075
9076 let max_x = content_origin.x + editor_width - size.width;
9077
9078 if origin.x > max_x {
9079 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9080
9081 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9082 origin.y += offset;
9083 IconName::ArrowUp
9084 } else {
9085 origin.y -= offset;
9086 IconName::ArrowDown
9087 };
9088
9089 element = self
9090 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9091 .into_any();
9092
9093 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9094
9095 origin.x = content_origin.x + editor_width - size.width - px(2.);
9096 }
9097
9098 element.prepaint_at(origin, window, cx);
9099 Some((element, origin))
9100 }
9101
9102 fn render_edit_prediction_diff_popover(
9103 self: &Editor,
9104 text_bounds: &Bounds<Pixels>,
9105 content_origin: gpui::Point<Pixels>,
9106 right_margin: Pixels,
9107 editor_snapshot: &EditorSnapshot,
9108 visible_row_range: Range<DisplayRow>,
9109 line_layouts: &[LineWithInvisibles],
9110 line_height: Pixels,
9111 scroll_position: gpui::Point<ScrollOffset>,
9112 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9113 newest_selection_head: Option<DisplayPoint>,
9114 editor_width: Pixels,
9115 style: &EditorStyle,
9116 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9117 edit_preview: &Option<language::EditPreview>,
9118 snapshot: &language::BufferSnapshot,
9119 window: &mut Window,
9120 cx: &mut App,
9121 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9122 let edit_start = edits
9123 .first()
9124 .unwrap()
9125 .0
9126 .start
9127 .to_display_point(editor_snapshot);
9128 let edit_end = edits
9129 .last()
9130 .unwrap()
9131 .0
9132 .end
9133 .to_display_point(editor_snapshot);
9134
9135 let is_visible = visible_row_range.contains(&edit_start.row())
9136 || visible_row_range.contains(&edit_end.row());
9137 if !is_visible {
9138 return None;
9139 }
9140
9141 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9142 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9143 } else {
9144 // Fallback for providers without edit_preview
9145 crate::edit_prediction_fallback_text(edits, cx)
9146 };
9147
9148 let styled_text = highlighted_edits.to_styled_text(&style.text);
9149 let line_count = highlighted_edits.text.lines().count();
9150
9151 const BORDER_WIDTH: Pixels = px(1.);
9152
9153 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9154 let has_keybind = keybind.is_some();
9155
9156 let mut element = h_flex()
9157 .items_start()
9158 .child(
9159 h_flex()
9160 .bg(cx.theme().colors().editor_background)
9161 .border(BORDER_WIDTH)
9162 .shadow_xs()
9163 .border_color(cx.theme().colors().border)
9164 .rounded_l_lg()
9165 .when(line_count > 1, |el| el.rounded_br_lg())
9166 .pr_1()
9167 .child(styled_text),
9168 )
9169 .child(
9170 h_flex()
9171 .h(line_height + BORDER_WIDTH * 2.)
9172 .px_1p5()
9173 .gap_1()
9174 // Workaround: For some reason, there's a gap if we don't do this
9175 .ml(-BORDER_WIDTH)
9176 .shadow(vec![gpui::BoxShadow {
9177 color: gpui::black().opacity(0.05),
9178 offset: point(px(1.), px(1.)),
9179 blur_radius: px(2.),
9180 spread_radius: px(0.),
9181 }])
9182 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9183 .border(BORDER_WIDTH)
9184 .border_color(cx.theme().colors().border)
9185 .rounded_r_lg()
9186 .id("edit_prediction_diff_popover_keybind")
9187 .when(!has_keybind, |el| {
9188 let status_colors = cx.theme().status();
9189
9190 el.bg(status_colors.error_background)
9191 .border_color(status_colors.error.opacity(0.6))
9192 .child(Icon::new(IconName::Info).color(Color::Error))
9193 .cursor_default()
9194 .hoverable_tooltip(move |_window, cx| {
9195 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9196 })
9197 })
9198 .children(keybind),
9199 )
9200 .into_any();
9201
9202 let longest_row =
9203 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9204 let longest_line_width = if visible_row_range.contains(&longest_row) {
9205 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9206 } else {
9207 layout_line(
9208 longest_row,
9209 editor_snapshot,
9210 style,
9211 editor_width,
9212 |_| false,
9213 window,
9214 cx,
9215 )
9216 .width
9217 };
9218
9219 let viewport_bounds =
9220 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9221 right: -right_margin,
9222 ..Default::default()
9223 });
9224
9225 let x_after_longest = Pixels::from(
9226 ScrollPixelOffset::from(
9227 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9228 ) - scroll_pixel_position.x,
9229 );
9230
9231 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9232
9233 // Fully visible if it can be displayed within the window (allow overlapping other
9234 // panes). However, this is only allowed if the popover starts within text_bounds.
9235 let can_position_to_the_right = x_after_longest < text_bounds.right()
9236 && x_after_longest + element_bounds.width < viewport_bounds.right();
9237
9238 let mut origin = if can_position_to_the_right {
9239 point(
9240 x_after_longest,
9241 text_bounds.origin.y
9242 + Pixels::from(
9243 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9244 - scroll_pixel_position.y,
9245 ),
9246 )
9247 } else {
9248 let cursor_row = newest_selection_head.map(|head| head.row());
9249 let above_edit = edit_start
9250 .row()
9251 .0
9252 .checked_sub(line_count as u32)
9253 .map(DisplayRow);
9254 let below_edit = Some(edit_end.row() + 1);
9255 let above_cursor =
9256 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9257 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9258
9259 // Place the edit popover adjacent to the edit if there is a location
9260 // available that is onscreen and does not obscure the cursor. Otherwise,
9261 // place it adjacent to the cursor.
9262 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9263 .into_iter()
9264 .flatten()
9265 .find(|&start_row| {
9266 let end_row = start_row + line_count as u32;
9267 visible_row_range.contains(&start_row)
9268 && visible_row_range.contains(&end_row)
9269 && cursor_row
9270 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9271 })?;
9272
9273 content_origin
9274 + point(
9275 Pixels::from(-scroll_pixel_position.x),
9276 Pixels::from(
9277 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9278 ),
9279 )
9280 };
9281
9282 origin.x -= BORDER_WIDTH;
9283
9284 window.defer_draw(element, origin, 1);
9285
9286 // Do not return an element, since it will already be drawn due to defer_draw.
9287 None
9288 }
9289
9290 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9291 px(30.)
9292 }
9293
9294 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9295 if self.read_only(cx) {
9296 cx.theme().players().read_only()
9297 } else {
9298 self.style.as_ref().unwrap().local_player
9299 }
9300 }
9301
9302 fn render_edit_prediction_accept_keybind(
9303 &self,
9304 window: &mut Window,
9305 cx: &mut App,
9306 ) -> Option<AnyElement> {
9307 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9308 let accept_keystroke = accept_binding.keystroke()?;
9309
9310 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9311
9312 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9313 Color::Accent
9314 } else {
9315 Color::Muted
9316 };
9317
9318 h_flex()
9319 .px_0p5()
9320 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9321 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9322 .text_size(TextSize::XSmall.rems(cx))
9323 .child(h_flex().children(ui::render_modifiers(
9324 accept_keystroke.modifiers(),
9325 PlatformStyle::platform(),
9326 Some(modifiers_color),
9327 Some(IconSize::XSmall.rems().into()),
9328 true,
9329 )))
9330 .when(is_platform_style_mac, |parent| {
9331 parent.child(accept_keystroke.key().to_string())
9332 })
9333 .when(!is_platform_style_mac, |parent| {
9334 parent.child(
9335 Key::new(
9336 util::capitalize(accept_keystroke.key()),
9337 Some(Color::Default),
9338 )
9339 .size(Some(IconSize::XSmall.rems().into())),
9340 )
9341 })
9342 .into_any()
9343 .into()
9344 }
9345
9346 fn render_edit_prediction_line_popover(
9347 &self,
9348 label: impl Into<SharedString>,
9349 icon: Option<IconName>,
9350 window: &mut Window,
9351 cx: &mut App,
9352 ) -> Stateful<Div> {
9353 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9354
9355 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9356 let has_keybind = keybind.is_some();
9357
9358 h_flex()
9359 .id("ep-line-popover")
9360 .py_0p5()
9361 .pl_1()
9362 .pr(padding_right)
9363 .gap_1()
9364 .rounded_md()
9365 .border_1()
9366 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9367 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9368 .shadow_xs()
9369 .when(!has_keybind, |el| {
9370 let status_colors = cx.theme().status();
9371
9372 el.bg(status_colors.error_background)
9373 .border_color(status_colors.error.opacity(0.6))
9374 .pl_2()
9375 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9376 .cursor_default()
9377 .hoverable_tooltip(move |_window, cx| {
9378 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9379 })
9380 })
9381 .children(keybind)
9382 .child(
9383 Label::new(label)
9384 .size(LabelSize::Small)
9385 .when(!has_keybind, |el| {
9386 el.color(cx.theme().status().error.into()).strikethrough()
9387 }),
9388 )
9389 .when(!has_keybind, |el| {
9390 el.child(
9391 h_flex().ml_1().child(
9392 Icon::new(IconName::Info)
9393 .size(IconSize::Small)
9394 .color(cx.theme().status().error.into()),
9395 ),
9396 )
9397 })
9398 .when_some(icon, |element, icon| {
9399 element.child(
9400 div()
9401 .mt(px(1.5))
9402 .child(Icon::new(icon).size(IconSize::Small)),
9403 )
9404 })
9405 }
9406
9407 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9408 let accent_color = cx.theme().colors().text_accent;
9409 let editor_bg_color = cx.theme().colors().editor_background;
9410 editor_bg_color.blend(accent_color.opacity(0.1))
9411 }
9412
9413 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9414 let accent_color = cx.theme().colors().text_accent;
9415 let editor_bg_color = cx.theme().colors().editor_background;
9416 editor_bg_color.blend(accent_color.opacity(0.6))
9417 }
9418 fn get_prediction_provider_icon_name(
9419 provider: &Option<RegisteredEditPredictionProvider>,
9420 ) -> IconName {
9421 match provider {
9422 Some(provider) => match provider.provider.name() {
9423 "copilot" => IconName::Copilot,
9424 "supermaven" => IconName::Supermaven,
9425 _ => IconName::ZedPredict,
9426 },
9427 None => IconName::ZedPredict,
9428 }
9429 }
9430
9431 fn render_edit_prediction_cursor_popover(
9432 &self,
9433 min_width: Pixels,
9434 max_width: Pixels,
9435 cursor_point: Point,
9436 style: &EditorStyle,
9437 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9438 _window: &Window,
9439 cx: &mut Context<Editor>,
9440 ) -> Option<AnyElement> {
9441 let provider = self.edit_prediction_provider.as_ref()?;
9442 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9443
9444 let is_refreshing = provider.provider.is_refreshing(cx);
9445
9446 fn pending_completion_container(icon: IconName) -> Div {
9447 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9448 }
9449
9450 let completion = match &self.active_edit_prediction {
9451 Some(prediction) => {
9452 if !self.has_visible_completions_menu() {
9453 const RADIUS: Pixels = px(6.);
9454 const BORDER_WIDTH: Pixels = px(1.);
9455
9456 return Some(
9457 h_flex()
9458 .elevation_2(cx)
9459 .border(BORDER_WIDTH)
9460 .border_color(cx.theme().colors().border)
9461 .when(accept_keystroke.is_none(), |el| {
9462 el.border_color(cx.theme().status().error)
9463 })
9464 .rounded(RADIUS)
9465 .rounded_tl(px(0.))
9466 .overflow_hidden()
9467 .child(div().px_1p5().child(match &prediction.completion {
9468 EditPrediction::MoveWithin { target, snapshot } => {
9469 use text::ToPoint as _;
9470 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9471 {
9472 Icon::new(IconName::ZedPredictDown)
9473 } else {
9474 Icon::new(IconName::ZedPredictUp)
9475 }
9476 }
9477 EditPrediction::MoveOutside { .. } => {
9478 // TODO [zeta2] custom icon for external jump?
9479 Icon::new(provider_icon)
9480 }
9481 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9482 }))
9483 .child(
9484 h_flex()
9485 .gap_1()
9486 .py_1()
9487 .px_2()
9488 .rounded_r(RADIUS - BORDER_WIDTH)
9489 .border_l_1()
9490 .border_color(cx.theme().colors().border)
9491 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9492 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9493 el.child(
9494 Label::new("Hold")
9495 .size(LabelSize::Small)
9496 .when(accept_keystroke.is_none(), |el| {
9497 el.strikethrough()
9498 })
9499 .line_height_style(LineHeightStyle::UiLabel),
9500 )
9501 })
9502 .id("edit_prediction_cursor_popover_keybind")
9503 .when(accept_keystroke.is_none(), |el| {
9504 let status_colors = cx.theme().status();
9505
9506 el.bg(status_colors.error_background)
9507 .border_color(status_colors.error.opacity(0.6))
9508 .child(Icon::new(IconName::Info).color(Color::Error))
9509 .cursor_default()
9510 .hoverable_tooltip(move |_window, cx| {
9511 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9512 .into()
9513 })
9514 })
9515 .when_some(
9516 accept_keystroke.as_ref(),
9517 |el, accept_keystroke| {
9518 el.child(h_flex().children(ui::render_modifiers(
9519 accept_keystroke.modifiers(),
9520 PlatformStyle::platform(),
9521 Some(Color::Default),
9522 Some(IconSize::XSmall.rems().into()),
9523 false,
9524 )))
9525 },
9526 ),
9527 )
9528 .into_any(),
9529 );
9530 }
9531
9532 self.render_edit_prediction_cursor_popover_preview(
9533 prediction,
9534 cursor_point,
9535 style,
9536 cx,
9537 )?
9538 }
9539
9540 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9541 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9542 stale_completion,
9543 cursor_point,
9544 style,
9545 cx,
9546 )?,
9547
9548 None => pending_completion_container(provider_icon)
9549 .child(Label::new("...").size(LabelSize::Small)),
9550 },
9551
9552 None => pending_completion_container(provider_icon)
9553 .child(Label::new("...").size(LabelSize::Small)),
9554 };
9555
9556 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9557 completion
9558 .with_animation(
9559 "loading-completion",
9560 Animation::new(Duration::from_secs(2))
9561 .repeat()
9562 .with_easing(pulsating_between(0.4, 0.8)),
9563 |label, delta| label.opacity(delta),
9564 )
9565 .into_any_element()
9566 } else {
9567 completion.into_any_element()
9568 };
9569
9570 let has_completion = self.active_edit_prediction.is_some();
9571
9572 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9573 Some(
9574 h_flex()
9575 .min_w(min_width)
9576 .max_w(max_width)
9577 .flex_1()
9578 .elevation_2(cx)
9579 .border_color(cx.theme().colors().border)
9580 .child(
9581 div()
9582 .flex_1()
9583 .py_1()
9584 .px_2()
9585 .overflow_hidden()
9586 .child(completion),
9587 )
9588 .when_some(accept_keystroke, |el, accept_keystroke| {
9589 if !accept_keystroke.modifiers().modified() {
9590 return el;
9591 }
9592
9593 el.child(
9594 h_flex()
9595 .h_full()
9596 .border_l_1()
9597 .rounded_r_lg()
9598 .border_color(cx.theme().colors().border)
9599 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9600 .gap_1()
9601 .py_1()
9602 .px_2()
9603 .child(
9604 h_flex()
9605 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9606 .when(is_platform_style_mac, |parent| parent.gap_1())
9607 .child(h_flex().children(ui::render_modifiers(
9608 accept_keystroke.modifiers(),
9609 PlatformStyle::platform(),
9610 Some(if !has_completion {
9611 Color::Muted
9612 } else {
9613 Color::Default
9614 }),
9615 None,
9616 false,
9617 ))),
9618 )
9619 .child(Label::new("Preview").into_any_element())
9620 .opacity(if has_completion { 1.0 } else { 0.4 }),
9621 )
9622 })
9623 .into_any(),
9624 )
9625 }
9626
9627 fn render_edit_prediction_cursor_popover_preview(
9628 &self,
9629 completion: &EditPredictionState,
9630 cursor_point: Point,
9631 style: &EditorStyle,
9632 cx: &mut Context<Editor>,
9633 ) -> Option<Div> {
9634 use text::ToPoint as _;
9635
9636 fn render_relative_row_jump(
9637 prefix: impl Into<String>,
9638 current_row: u32,
9639 target_row: u32,
9640 ) -> Div {
9641 let (row_diff, arrow) = if target_row < current_row {
9642 (current_row - target_row, IconName::ArrowUp)
9643 } else {
9644 (target_row - current_row, IconName::ArrowDown)
9645 };
9646
9647 h_flex()
9648 .child(
9649 Label::new(format!("{}{}", prefix.into(), row_diff))
9650 .color(Color::Muted)
9651 .size(LabelSize::Small),
9652 )
9653 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9654 }
9655
9656 let supports_jump = self
9657 .edit_prediction_provider
9658 .as_ref()
9659 .map(|provider| provider.provider.supports_jump_to_edit())
9660 .unwrap_or(true);
9661
9662 match &completion.completion {
9663 EditPrediction::MoveWithin {
9664 target, snapshot, ..
9665 } => {
9666 if !supports_jump {
9667 return None;
9668 }
9669
9670 Some(
9671 h_flex()
9672 .px_2()
9673 .gap_2()
9674 .flex_1()
9675 .child(
9676 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9677 Icon::new(IconName::ZedPredictDown)
9678 } else {
9679 Icon::new(IconName::ZedPredictUp)
9680 },
9681 )
9682 .child(Label::new("Jump to Edit")),
9683 )
9684 }
9685 EditPrediction::MoveOutside { snapshot, .. } => {
9686 let file_name = snapshot
9687 .file()
9688 .map(|file| file.file_name(cx))
9689 .unwrap_or("untitled");
9690 Some(
9691 h_flex()
9692 .px_2()
9693 .gap_2()
9694 .flex_1()
9695 .child(Icon::new(IconName::ZedPredict))
9696 .child(Label::new(format!("Jump to {file_name}"))),
9697 )
9698 }
9699 EditPrediction::Edit {
9700 edits,
9701 edit_preview,
9702 snapshot,
9703 display_mode: _,
9704 } => {
9705 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9706
9707 let (highlighted_edits, has_more_lines) =
9708 if let Some(edit_preview) = edit_preview.as_ref() {
9709 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9710 .first_line_preview()
9711 } else {
9712 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9713 };
9714
9715 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9716 .with_default_highlights(&style.text, highlighted_edits.highlights);
9717
9718 let preview = h_flex()
9719 .gap_1()
9720 .min_w_16()
9721 .child(styled_text)
9722 .when(has_more_lines, |parent| parent.child("…"));
9723
9724 let left = if supports_jump && first_edit_row != cursor_point.row {
9725 render_relative_row_jump("", cursor_point.row, first_edit_row)
9726 .into_any_element()
9727 } else {
9728 let icon_name =
9729 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9730 Icon::new(icon_name).into_any_element()
9731 };
9732
9733 Some(
9734 h_flex()
9735 .h_full()
9736 .flex_1()
9737 .gap_2()
9738 .pr_1()
9739 .overflow_x_hidden()
9740 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9741 .child(left)
9742 .child(preview),
9743 )
9744 }
9745 }
9746 }
9747
9748 pub fn render_context_menu(
9749 &self,
9750 style: &EditorStyle,
9751 max_height_in_lines: u32,
9752 window: &mut Window,
9753 cx: &mut Context<Editor>,
9754 ) -> Option<AnyElement> {
9755 let menu = self.context_menu.borrow();
9756 let menu = menu.as_ref()?;
9757 if !menu.visible() {
9758 return None;
9759 };
9760 Some(menu.render(style, max_height_in_lines, window, cx))
9761 }
9762
9763 fn render_context_menu_aside(
9764 &mut self,
9765 max_size: Size<Pixels>,
9766 window: &mut Window,
9767 cx: &mut Context<Editor>,
9768 ) -> Option<AnyElement> {
9769 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9770 if menu.visible() {
9771 menu.render_aside(max_size, window, cx)
9772 } else {
9773 None
9774 }
9775 })
9776 }
9777
9778 fn hide_context_menu(
9779 &mut self,
9780 window: &mut Window,
9781 cx: &mut Context<Self>,
9782 ) -> Option<CodeContextMenu> {
9783 cx.notify();
9784 self.completion_tasks.clear();
9785 let context_menu = self.context_menu.borrow_mut().take();
9786 self.stale_edit_prediction_in_menu.take();
9787 self.update_visible_edit_prediction(window, cx);
9788 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9789 && let Some(completion_provider) = &self.completion_provider
9790 {
9791 completion_provider.selection_changed(None, window, cx);
9792 }
9793 context_menu
9794 }
9795
9796 fn show_snippet_choices(
9797 &mut self,
9798 choices: &Vec<String>,
9799 selection: Range<Anchor>,
9800 cx: &mut Context<Self>,
9801 ) {
9802 let Some((_, buffer, _)) = self
9803 .buffer()
9804 .read(cx)
9805 .excerpt_containing(selection.start, cx)
9806 else {
9807 return;
9808 };
9809 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9810 else {
9811 return;
9812 };
9813 if buffer != end_buffer {
9814 log::error!("expected anchor range to have matching buffer IDs");
9815 return;
9816 }
9817
9818 let id = post_inc(&mut self.next_completion_id);
9819 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9820 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9821 CompletionsMenu::new_snippet_choices(
9822 id,
9823 true,
9824 choices,
9825 selection,
9826 buffer,
9827 snippet_sort_order,
9828 ),
9829 ));
9830 }
9831
9832 pub fn insert_snippet(
9833 &mut self,
9834 insertion_ranges: &[Range<MultiBufferOffset>],
9835 snippet: Snippet,
9836 window: &mut Window,
9837 cx: &mut Context<Self>,
9838 ) -> Result<()> {
9839 struct Tabstop<T> {
9840 is_end_tabstop: bool,
9841 ranges: Vec<Range<T>>,
9842 choices: Option<Vec<String>>,
9843 }
9844
9845 let tabstops = self.buffer.update(cx, |buffer, cx| {
9846 let snippet_text: Arc<str> = snippet.text.clone().into();
9847 let edits = insertion_ranges
9848 .iter()
9849 .cloned()
9850 .map(|range| (range, snippet_text.clone()));
9851 let autoindent_mode = AutoindentMode::Block {
9852 original_indent_columns: Vec::new(),
9853 };
9854 buffer.edit(edits, Some(autoindent_mode), cx);
9855
9856 let snapshot = &*buffer.read(cx);
9857 let snippet = &snippet;
9858 snippet
9859 .tabstops
9860 .iter()
9861 .map(|tabstop| {
9862 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9863 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9864 });
9865 let mut tabstop_ranges = tabstop
9866 .ranges
9867 .iter()
9868 .flat_map(|tabstop_range| {
9869 let mut delta = 0_isize;
9870 insertion_ranges.iter().map(move |insertion_range| {
9871 let insertion_start = insertion_range.start + delta;
9872 delta += snippet.text.len() as isize
9873 - (insertion_range.end - insertion_range.start) as isize;
9874
9875 let start =
9876 (insertion_start + tabstop_range.start).min(snapshot.len());
9877 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
9878 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9879 })
9880 })
9881 .collect::<Vec<_>>();
9882 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9883
9884 Tabstop {
9885 is_end_tabstop,
9886 ranges: tabstop_ranges,
9887 choices: tabstop.choices.clone(),
9888 }
9889 })
9890 .collect::<Vec<_>>()
9891 });
9892 if let Some(tabstop) = tabstops.first() {
9893 self.change_selections(Default::default(), window, cx, |s| {
9894 // Reverse order so that the first range is the newest created selection.
9895 // Completions will use it and autoscroll will prioritize it.
9896 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9897 });
9898
9899 if let Some(choices) = &tabstop.choices
9900 && let Some(selection) = tabstop.ranges.first()
9901 {
9902 self.show_snippet_choices(choices, selection.clone(), cx)
9903 }
9904
9905 // If we're already at the last tabstop and it's at the end of the snippet,
9906 // we're done, we don't need to keep the state around.
9907 if !tabstop.is_end_tabstop {
9908 let choices = tabstops
9909 .iter()
9910 .map(|tabstop| tabstop.choices.clone())
9911 .collect();
9912
9913 let ranges = tabstops
9914 .into_iter()
9915 .map(|tabstop| tabstop.ranges)
9916 .collect::<Vec<_>>();
9917
9918 self.snippet_stack.push(SnippetState {
9919 active_index: 0,
9920 ranges,
9921 choices,
9922 });
9923 }
9924
9925 // Check whether the just-entered snippet ends with an auto-closable bracket.
9926 if self.autoclose_regions.is_empty() {
9927 let snapshot = self.buffer.read(cx).snapshot(cx);
9928 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9929 let selection_head = selection.head();
9930 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9931 continue;
9932 };
9933
9934 let mut bracket_pair = None;
9935 let max_lookup_length = scope
9936 .brackets()
9937 .map(|(pair, _)| {
9938 pair.start
9939 .as_str()
9940 .chars()
9941 .count()
9942 .max(pair.end.as_str().chars().count())
9943 })
9944 .max();
9945 if let Some(max_lookup_length) = max_lookup_length {
9946 let next_text = snapshot
9947 .chars_at(selection_head)
9948 .take(max_lookup_length)
9949 .collect::<String>();
9950 let prev_text = snapshot
9951 .reversed_chars_at(selection_head)
9952 .take(max_lookup_length)
9953 .collect::<String>();
9954
9955 for (pair, enabled) in scope.brackets() {
9956 if enabled
9957 && pair.close
9958 && prev_text.starts_with(pair.start.as_str())
9959 && next_text.starts_with(pair.end.as_str())
9960 {
9961 bracket_pair = Some(pair.clone());
9962 break;
9963 }
9964 }
9965 }
9966
9967 if let Some(pair) = bracket_pair {
9968 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9969 let autoclose_enabled =
9970 self.use_autoclose && snapshot_settings.use_autoclose;
9971 if autoclose_enabled {
9972 let start = snapshot.anchor_after(selection_head);
9973 let end = snapshot.anchor_after(selection_head);
9974 self.autoclose_regions.push(AutocloseRegion {
9975 selection_id: selection.id,
9976 range: start..end,
9977 pair,
9978 });
9979 }
9980 }
9981 }
9982 }
9983 }
9984 Ok(())
9985 }
9986
9987 pub fn move_to_next_snippet_tabstop(
9988 &mut self,
9989 window: &mut Window,
9990 cx: &mut Context<Self>,
9991 ) -> bool {
9992 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9993 }
9994
9995 pub fn move_to_prev_snippet_tabstop(
9996 &mut self,
9997 window: &mut Window,
9998 cx: &mut Context<Self>,
9999 ) -> bool {
10000 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10001 }
10002
10003 pub fn move_to_snippet_tabstop(
10004 &mut self,
10005 bias: Bias,
10006 window: &mut Window,
10007 cx: &mut Context<Self>,
10008 ) -> bool {
10009 if let Some(mut snippet) = self.snippet_stack.pop() {
10010 match bias {
10011 Bias::Left => {
10012 if snippet.active_index > 0 {
10013 snippet.active_index -= 1;
10014 } else {
10015 self.snippet_stack.push(snippet);
10016 return false;
10017 }
10018 }
10019 Bias::Right => {
10020 if snippet.active_index + 1 < snippet.ranges.len() {
10021 snippet.active_index += 1;
10022 } else {
10023 self.snippet_stack.push(snippet);
10024 return false;
10025 }
10026 }
10027 }
10028 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10029 self.change_selections(Default::default(), window, cx, |s| {
10030 // Reverse order so that the first range is the newest created selection.
10031 // Completions will use it and autoscroll will prioritize it.
10032 s.select_ranges(current_ranges.iter().rev().cloned())
10033 });
10034
10035 if let Some(choices) = &snippet.choices[snippet.active_index]
10036 && let Some(selection) = current_ranges.first()
10037 {
10038 self.show_snippet_choices(choices, selection.clone(), cx);
10039 }
10040
10041 // If snippet state is not at the last tabstop, push it back on the stack
10042 if snippet.active_index + 1 < snippet.ranges.len() {
10043 self.snippet_stack.push(snippet);
10044 }
10045 return true;
10046 }
10047 }
10048
10049 false
10050 }
10051
10052 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10053 self.transact(window, cx, |this, window, cx| {
10054 this.select_all(&SelectAll, window, cx);
10055 this.insert("", window, cx);
10056 });
10057 }
10058
10059 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10060 if self.read_only(cx) {
10061 return;
10062 }
10063 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10064 self.transact(window, cx, |this, window, cx| {
10065 this.select_autoclose_pair(window, cx);
10066
10067 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10068
10069 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10070 if !this.linked_edit_ranges.is_empty() {
10071 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10072 let snapshot = this.buffer.read(cx).snapshot(cx);
10073
10074 for selection in selections.iter() {
10075 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10076 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10077 if selection_start.buffer_id != selection_end.buffer_id {
10078 continue;
10079 }
10080 if let Some(ranges) =
10081 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10082 {
10083 for (buffer, entries) in ranges {
10084 linked_ranges.entry(buffer).or_default().extend(entries);
10085 }
10086 }
10087 }
10088 }
10089
10090 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10091 for selection in &mut selections {
10092 if selection.is_empty() {
10093 let old_head = selection.head();
10094 let mut new_head =
10095 movement::left(&display_map, old_head.to_display_point(&display_map))
10096 .to_point(&display_map);
10097 if let Some((buffer, line_buffer_range)) = display_map
10098 .buffer_snapshot()
10099 .buffer_line_for_row(MultiBufferRow(old_head.row))
10100 {
10101 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10102 let indent_len = match indent_size.kind {
10103 IndentKind::Space => {
10104 buffer.settings_at(line_buffer_range.start, cx).tab_size
10105 }
10106 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10107 };
10108 if old_head.column <= indent_size.len && old_head.column > 0 {
10109 let indent_len = indent_len.get();
10110 new_head = cmp::min(
10111 new_head,
10112 MultiBufferPoint::new(
10113 old_head.row,
10114 ((old_head.column - 1) / indent_len) * indent_len,
10115 ),
10116 );
10117 }
10118 }
10119
10120 selection.set_head(new_head, SelectionGoal::None);
10121 }
10122 }
10123
10124 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10125 this.insert("", window, cx);
10126 let empty_str: Arc<str> = Arc::from("");
10127 for (buffer, edits) in linked_ranges {
10128 let snapshot = buffer.read(cx).snapshot();
10129 use text::ToPoint as TP;
10130
10131 let edits = edits
10132 .into_iter()
10133 .map(|range| {
10134 let end_point = TP::to_point(&range.end, &snapshot);
10135 let mut start_point = TP::to_point(&range.start, &snapshot);
10136
10137 if end_point == start_point {
10138 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10139 .saturating_sub(1);
10140 start_point =
10141 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10142 };
10143
10144 (start_point..end_point, empty_str.clone())
10145 })
10146 .sorted_by_key(|(range, _)| range.start)
10147 .collect::<Vec<_>>();
10148 buffer.update(cx, |this, cx| {
10149 this.edit(edits, None, cx);
10150 })
10151 }
10152 this.refresh_edit_prediction(true, false, window, cx);
10153 refresh_linked_ranges(this, window, cx);
10154 });
10155 }
10156
10157 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10158 if self.read_only(cx) {
10159 return;
10160 }
10161 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10162 self.transact(window, cx, |this, window, cx| {
10163 this.change_selections(Default::default(), window, cx, |s| {
10164 s.move_with(|map, selection| {
10165 if selection.is_empty() {
10166 let cursor = movement::right(map, selection.head());
10167 selection.end = cursor;
10168 selection.reversed = true;
10169 selection.goal = SelectionGoal::None;
10170 }
10171 })
10172 });
10173 this.insert("", window, cx);
10174 this.refresh_edit_prediction(true, false, window, cx);
10175 });
10176 }
10177
10178 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10179 if self.mode.is_single_line() {
10180 cx.propagate();
10181 return;
10182 }
10183
10184 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10185 if self.move_to_prev_snippet_tabstop(window, cx) {
10186 return;
10187 }
10188 self.outdent(&Outdent, window, cx);
10189 }
10190
10191 pub fn next_snippet_tabstop(
10192 &mut self,
10193 _: &NextSnippetTabstop,
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_next_snippet_tabstop(window, cx) {
10203 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10204 return;
10205 }
10206 cx.propagate();
10207 }
10208
10209 pub fn previous_snippet_tabstop(
10210 &mut self,
10211 _: &PreviousSnippetTabstop,
10212 window: &mut Window,
10213 cx: &mut Context<Self>,
10214 ) {
10215 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10216 cx.propagate();
10217 return;
10218 }
10219
10220 if self.move_to_prev_snippet_tabstop(window, cx) {
10221 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10222 return;
10223 }
10224 cx.propagate();
10225 }
10226
10227 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10228 if self.mode.is_single_line() {
10229 cx.propagate();
10230 return;
10231 }
10232
10233 if self.move_to_next_snippet_tabstop(window, cx) {
10234 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10235 return;
10236 }
10237 if self.read_only(cx) {
10238 return;
10239 }
10240 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10241 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10242 let buffer = self.buffer.read(cx);
10243 let snapshot = buffer.snapshot(cx);
10244 let rows_iter = selections.iter().map(|s| s.head().row);
10245 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10246
10247 let has_some_cursor_in_whitespace = selections
10248 .iter()
10249 .filter(|selection| selection.is_empty())
10250 .any(|selection| {
10251 let cursor = selection.head();
10252 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10253 cursor.column < current_indent.len
10254 });
10255
10256 let mut edits = Vec::new();
10257 let mut prev_edited_row = 0;
10258 let mut row_delta = 0;
10259 for selection in &mut selections {
10260 if selection.start.row != prev_edited_row {
10261 row_delta = 0;
10262 }
10263 prev_edited_row = selection.end.row;
10264
10265 // If the selection is non-empty, then increase the indentation of the selected lines.
10266 if !selection.is_empty() {
10267 row_delta =
10268 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10269 continue;
10270 }
10271
10272 let cursor = selection.head();
10273 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10274 if let Some(suggested_indent) =
10275 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10276 {
10277 // Don't do anything if already at suggested indent
10278 // and there is any other cursor which is not
10279 if has_some_cursor_in_whitespace
10280 && cursor.column == current_indent.len
10281 && current_indent.len == suggested_indent.len
10282 {
10283 continue;
10284 }
10285
10286 // Adjust line and move cursor to suggested indent
10287 // if cursor is not at suggested indent
10288 if cursor.column < suggested_indent.len
10289 && cursor.column <= current_indent.len
10290 && current_indent.len <= suggested_indent.len
10291 {
10292 selection.start = Point::new(cursor.row, suggested_indent.len);
10293 selection.end = selection.start;
10294 if row_delta == 0 {
10295 edits.extend(Buffer::edit_for_indent_size_adjustment(
10296 cursor.row,
10297 current_indent,
10298 suggested_indent,
10299 ));
10300 row_delta = suggested_indent.len - current_indent.len;
10301 }
10302 continue;
10303 }
10304
10305 // If current indent is more than suggested indent
10306 // only move cursor to current indent and skip indent
10307 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10308 selection.start = Point::new(cursor.row, current_indent.len);
10309 selection.end = selection.start;
10310 continue;
10311 }
10312 }
10313
10314 // Otherwise, insert a hard or soft tab.
10315 let settings = buffer.language_settings_at(cursor, cx);
10316 let tab_size = if settings.hard_tabs {
10317 IndentSize::tab()
10318 } else {
10319 let tab_size = settings.tab_size.get();
10320 let indent_remainder = snapshot
10321 .text_for_range(Point::new(cursor.row, 0)..cursor)
10322 .flat_map(str::chars)
10323 .fold(row_delta % tab_size, |counter: u32, c| {
10324 if c == '\t' {
10325 0
10326 } else {
10327 (counter + 1) % tab_size
10328 }
10329 });
10330
10331 let chars_to_next_tab_stop = tab_size - indent_remainder;
10332 IndentSize::spaces(chars_to_next_tab_stop)
10333 };
10334 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10335 selection.end = selection.start;
10336 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10337 row_delta += tab_size.len;
10338 }
10339
10340 self.transact(window, cx, |this, window, cx| {
10341 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10342 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10343 this.refresh_edit_prediction(true, false, window, cx);
10344 });
10345 }
10346
10347 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10348 if self.read_only(cx) {
10349 return;
10350 }
10351 if self.mode.is_single_line() {
10352 cx.propagate();
10353 return;
10354 }
10355
10356 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10357 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10358 let mut prev_edited_row = 0;
10359 let mut row_delta = 0;
10360 let mut edits = Vec::new();
10361 let buffer = self.buffer.read(cx);
10362 let snapshot = buffer.snapshot(cx);
10363 for selection in &mut selections {
10364 if selection.start.row != prev_edited_row {
10365 row_delta = 0;
10366 }
10367 prev_edited_row = selection.end.row;
10368
10369 row_delta =
10370 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10371 }
10372
10373 self.transact(window, cx, |this, window, cx| {
10374 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10375 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10376 });
10377 }
10378
10379 fn indent_selection(
10380 buffer: &MultiBuffer,
10381 snapshot: &MultiBufferSnapshot,
10382 selection: &mut Selection<Point>,
10383 edits: &mut Vec<(Range<Point>, String)>,
10384 delta_for_start_row: u32,
10385 cx: &App,
10386 ) -> u32 {
10387 let settings = buffer.language_settings_at(selection.start, cx);
10388 let tab_size = settings.tab_size.get();
10389 let indent_kind = if settings.hard_tabs {
10390 IndentKind::Tab
10391 } else {
10392 IndentKind::Space
10393 };
10394 let mut start_row = selection.start.row;
10395 let mut end_row = selection.end.row + 1;
10396
10397 // If a selection ends at the beginning of a line, don't indent
10398 // that last line.
10399 if selection.end.column == 0 && selection.end.row > selection.start.row {
10400 end_row -= 1;
10401 }
10402
10403 // Avoid re-indenting a row that has already been indented by a
10404 // previous selection, but still update this selection's column
10405 // to reflect that indentation.
10406 if delta_for_start_row > 0 {
10407 start_row += 1;
10408 selection.start.column += delta_for_start_row;
10409 if selection.end.row == selection.start.row {
10410 selection.end.column += delta_for_start_row;
10411 }
10412 }
10413
10414 let mut delta_for_end_row = 0;
10415 let has_multiple_rows = start_row + 1 != end_row;
10416 for row in start_row..end_row {
10417 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10418 let indent_delta = match (current_indent.kind, indent_kind) {
10419 (IndentKind::Space, IndentKind::Space) => {
10420 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10421 IndentSize::spaces(columns_to_next_tab_stop)
10422 }
10423 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10424 (_, IndentKind::Tab) => IndentSize::tab(),
10425 };
10426
10427 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10428 0
10429 } else {
10430 selection.start.column
10431 };
10432 let row_start = Point::new(row, start);
10433 edits.push((
10434 row_start..row_start,
10435 indent_delta.chars().collect::<String>(),
10436 ));
10437
10438 // Update this selection's endpoints to reflect the indentation.
10439 if row == selection.start.row {
10440 selection.start.column += indent_delta.len;
10441 }
10442 if row == selection.end.row {
10443 selection.end.column += indent_delta.len;
10444 delta_for_end_row = indent_delta.len;
10445 }
10446 }
10447
10448 if selection.start.row == selection.end.row {
10449 delta_for_start_row + delta_for_end_row
10450 } else {
10451 delta_for_end_row
10452 }
10453 }
10454
10455 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10456 if self.read_only(cx) {
10457 return;
10458 }
10459 if self.mode.is_single_line() {
10460 cx.propagate();
10461 return;
10462 }
10463
10464 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10465 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10466 let selections = self.selections.all::<Point>(&display_map);
10467 let mut deletion_ranges = Vec::new();
10468 let mut last_outdent = None;
10469 {
10470 let buffer = self.buffer.read(cx);
10471 let snapshot = buffer.snapshot(cx);
10472 for selection in &selections {
10473 let settings = buffer.language_settings_at(selection.start, cx);
10474 let tab_size = settings.tab_size.get();
10475 let mut rows = selection.spanned_rows(false, &display_map);
10476
10477 // Avoid re-outdenting a row that has already been outdented by a
10478 // previous selection.
10479 if let Some(last_row) = last_outdent
10480 && last_row == rows.start
10481 {
10482 rows.start = rows.start.next_row();
10483 }
10484 let has_multiple_rows = rows.len() > 1;
10485 for row in rows.iter_rows() {
10486 let indent_size = snapshot.indent_size_for_line(row);
10487 if indent_size.len > 0 {
10488 let deletion_len = match indent_size.kind {
10489 IndentKind::Space => {
10490 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10491 if columns_to_prev_tab_stop == 0 {
10492 tab_size
10493 } else {
10494 columns_to_prev_tab_stop
10495 }
10496 }
10497 IndentKind::Tab => 1,
10498 };
10499 let start = if has_multiple_rows
10500 || deletion_len > selection.start.column
10501 || indent_size.len < selection.start.column
10502 {
10503 0
10504 } else {
10505 selection.start.column - deletion_len
10506 };
10507 deletion_ranges.push(
10508 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10509 );
10510 last_outdent = Some(row);
10511 }
10512 }
10513 }
10514 }
10515
10516 self.transact(window, cx, |this, window, cx| {
10517 this.buffer.update(cx, |buffer, cx| {
10518 let empty_str: Arc<str> = Arc::default();
10519 buffer.edit(
10520 deletion_ranges
10521 .into_iter()
10522 .map(|range| (range, empty_str.clone())),
10523 None,
10524 cx,
10525 );
10526 });
10527 let selections = this
10528 .selections
10529 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10530 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10531 });
10532 }
10533
10534 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10535 if self.read_only(cx) {
10536 return;
10537 }
10538 if self.mode.is_single_line() {
10539 cx.propagate();
10540 return;
10541 }
10542
10543 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10544 let selections = self
10545 .selections
10546 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10547 .into_iter()
10548 .map(|s| s.range());
10549
10550 self.transact(window, cx, |this, window, cx| {
10551 this.buffer.update(cx, |buffer, cx| {
10552 buffer.autoindent_ranges(selections, cx);
10553 });
10554 let selections = this
10555 .selections
10556 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10557 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10558 });
10559 }
10560
10561 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10562 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10563 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10564 let selections = self.selections.all::<Point>(&display_map);
10565
10566 let mut new_cursors = Vec::new();
10567 let mut edit_ranges = Vec::new();
10568 let mut selections = selections.iter().peekable();
10569 while let Some(selection) = selections.next() {
10570 let mut rows = selection.spanned_rows(false, &display_map);
10571
10572 // Accumulate contiguous regions of rows that we want to delete.
10573 while let Some(next_selection) = selections.peek() {
10574 let next_rows = next_selection.spanned_rows(false, &display_map);
10575 if next_rows.start <= rows.end {
10576 rows.end = next_rows.end;
10577 selections.next().unwrap();
10578 } else {
10579 break;
10580 }
10581 }
10582
10583 let buffer = display_map.buffer_snapshot();
10584 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10585 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10586 // If there's a line after the range, delete the \n from the end of the row range
10587 (
10588 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10589 rows.end,
10590 )
10591 } else {
10592 // If there isn't a line after the range, delete the \n from the line before the
10593 // start of the row range
10594 edit_start = edit_start.saturating_sub_usize(1);
10595 (buffer.len(), rows.start.previous_row())
10596 };
10597
10598 let text_layout_details = self.text_layout_details(window);
10599 let x = display_map.x_for_display_point(
10600 selection.head().to_display_point(&display_map),
10601 &text_layout_details,
10602 );
10603 let row = Point::new(target_row.0, 0)
10604 .to_display_point(&display_map)
10605 .row();
10606 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10607
10608 new_cursors.push((
10609 selection.id,
10610 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10611 SelectionGoal::None,
10612 ));
10613 edit_ranges.push(edit_start..edit_end);
10614 }
10615
10616 self.transact(window, cx, |this, window, cx| {
10617 let buffer = this.buffer.update(cx, |buffer, cx| {
10618 let empty_str: Arc<str> = Arc::default();
10619 buffer.edit(
10620 edit_ranges
10621 .into_iter()
10622 .map(|range| (range, empty_str.clone())),
10623 None,
10624 cx,
10625 );
10626 buffer.snapshot(cx)
10627 });
10628 let new_selections = new_cursors
10629 .into_iter()
10630 .map(|(id, cursor, goal)| {
10631 let cursor = cursor.to_point(&buffer);
10632 Selection {
10633 id,
10634 start: cursor,
10635 end: cursor,
10636 reversed: false,
10637 goal,
10638 }
10639 })
10640 .collect();
10641
10642 this.change_selections(Default::default(), window, cx, |s| {
10643 s.select(new_selections);
10644 });
10645 });
10646 }
10647
10648 pub fn join_lines_impl(
10649 &mut self,
10650 insert_whitespace: bool,
10651 window: &mut Window,
10652 cx: &mut Context<Self>,
10653 ) {
10654 if self.read_only(cx) {
10655 return;
10656 }
10657 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10658 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10659 let start = MultiBufferRow(selection.start.row);
10660 // Treat single line selections as if they include the next line. Otherwise this action
10661 // would do nothing for single line selections individual cursors.
10662 let end = if selection.start.row == selection.end.row {
10663 MultiBufferRow(selection.start.row + 1)
10664 } else {
10665 MultiBufferRow(selection.end.row)
10666 };
10667
10668 if let Some(last_row_range) = row_ranges.last_mut()
10669 && start <= last_row_range.end
10670 {
10671 last_row_range.end = end;
10672 continue;
10673 }
10674 row_ranges.push(start..end);
10675 }
10676
10677 let snapshot = self.buffer.read(cx).snapshot(cx);
10678 let mut cursor_positions = Vec::new();
10679 for row_range in &row_ranges {
10680 let anchor = snapshot.anchor_before(Point::new(
10681 row_range.end.previous_row().0,
10682 snapshot.line_len(row_range.end.previous_row()),
10683 ));
10684 cursor_positions.push(anchor..anchor);
10685 }
10686
10687 self.transact(window, cx, |this, window, cx| {
10688 for row_range in row_ranges.into_iter().rev() {
10689 for row in row_range.iter_rows().rev() {
10690 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10691 let next_line_row = row.next_row();
10692 let indent = snapshot.indent_size_for_line(next_line_row);
10693 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10694
10695 let replace =
10696 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10697 " "
10698 } else {
10699 ""
10700 };
10701
10702 this.buffer.update(cx, |buffer, cx| {
10703 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10704 });
10705 }
10706 }
10707
10708 this.change_selections(Default::default(), window, cx, |s| {
10709 s.select_anchor_ranges(cursor_positions)
10710 });
10711 });
10712 }
10713
10714 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10715 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10716 self.join_lines_impl(true, window, cx);
10717 }
10718
10719 pub fn sort_lines_case_sensitive(
10720 &mut self,
10721 _: &SortLinesCaseSensitive,
10722 window: &mut Window,
10723 cx: &mut Context<Self>,
10724 ) {
10725 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10726 }
10727
10728 pub fn sort_lines_by_length(
10729 &mut self,
10730 _: &SortLinesByLength,
10731 window: &mut Window,
10732 cx: &mut Context<Self>,
10733 ) {
10734 self.manipulate_immutable_lines(window, cx, |lines| {
10735 lines.sort_by_key(|&line| line.chars().count())
10736 })
10737 }
10738
10739 pub fn sort_lines_case_insensitive(
10740 &mut self,
10741 _: &SortLinesCaseInsensitive,
10742 window: &mut Window,
10743 cx: &mut Context<Self>,
10744 ) {
10745 self.manipulate_immutable_lines(window, cx, |lines| {
10746 lines.sort_by_key(|line| line.to_lowercase())
10747 })
10748 }
10749
10750 pub fn unique_lines_case_insensitive(
10751 &mut self,
10752 _: &UniqueLinesCaseInsensitive,
10753 window: &mut Window,
10754 cx: &mut Context<Self>,
10755 ) {
10756 self.manipulate_immutable_lines(window, cx, |lines| {
10757 let mut seen = HashSet::default();
10758 lines.retain(|line| seen.insert(line.to_lowercase()));
10759 })
10760 }
10761
10762 pub fn unique_lines_case_sensitive(
10763 &mut self,
10764 _: &UniqueLinesCaseSensitive,
10765 window: &mut Window,
10766 cx: &mut Context<Self>,
10767 ) {
10768 self.manipulate_immutable_lines(window, cx, |lines| {
10769 let mut seen = HashSet::default();
10770 lines.retain(|line| seen.insert(*line));
10771 })
10772 }
10773
10774 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10775 let snapshot = self.buffer.read(cx).snapshot(cx);
10776 for selection in self.selections.disjoint_anchors_arc().iter() {
10777 if snapshot
10778 .language_at(selection.start)
10779 .and_then(|lang| lang.config().wrap_characters.as_ref())
10780 .is_some()
10781 {
10782 return true;
10783 }
10784 }
10785 false
10786 }
10787
10788 fn wrap_selections_in_tag(
10789 &mut self,
10790 _: &WrapSelectionsInTag,
10791 window: &mut Window,
10792 cx: &mut Context<Self>,
10793 ) {
10794 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10795
10796 let snapshot = self.buffer.read(cx).snapshot(cx);
10797
10798 let mut edits = Vec::new();
10799 let mut boundaries = Vec::new();
10800
10801 for selection in self
10802 .selections
10803 .all_adjusted(&self.display_snapshot(cx))
10804 .iter()
10805 {
10806 let Some(wrap_config) = snapshot
10807 .language_at(selection.start)
10808 .and_then(|lang| lang.config().wrap_characters.clone())
10809 else {
10810 continue;
10811 };
10812
10813 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10814 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10815
10816 let start_before = snapshot.anchor_before(selection.start);
10817 let end_after = snapshot.anchor_after(selection.end);
10818
10819 edits.push((start_before..start_before, open_tag));
10820 edits.push((end_after..end_after, close_tag));
10821
10822 boundaries.push((
10823 start_before,
10824 end_after,
10825 wrap_config.start_prefix.len(),
10826 wrap_config.end_suffix.len(),
10827 ));
10828 }
10829
10830 if edits.is_empty() {
10831 return;
10832 }
10833
10834 self.transact(window, cx, |this, window, cx| {
10835 let buffer = this.buffer.update(cx, |buffer, cx| {
10836 buffer.edit(edits, None, cx);
10837 buffer.snapshot(cx)
10838 });
10839
10840 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10841 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10842 boundaries.into_iter()
10843 {
10844 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10845 let close_offset = end_after
10846 .to_offset(&buffer)
10847 .saturating_sub_usize(end_suffix_len);
10848 new_selections.push(open_offset..open_offset);
10849 new_selections.push(close_offset..close_offset);
10850 }
10851
10852 this.change_selections(Default::default(), window, cx, |s| {
10853 s.select_ranges(new_selections);
10854 });
10855
10856 this.request_autoscroll(Autoscroll::fit(), cx);
10857 });
10858 }
10859
10860 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10861 let Some(project) = self.project.clone() else {
10862 return;
10863 };
10864 self.reload(project, window, cx)
10865 .detach_and_notify_err(window, cx);
10866 }
10867
10868 pub fn restore_file(
10869 &mut self,
10870 _: &::git::RestoreFile,
10871 window: &mut Window,
10872 cx: &mut Context<Self>,
10873 ) {
10874 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10875 let mut buffer_ids = HashSet::default();
10876 let snapshot = self.buffer().read(cx).snapshot(cx);
10877 for selection in self
10878 .selections
10879 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10880 {
10881 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10882 }
10883
10884 let buffer = self.buffer().read(cx);
10885 let ranges = buffer_ids
10886 .into_iter()
10887 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10888 .collect::<Vec<_>>();
10889
10890 self.restore_hunks_in_ranges(ranges, window, cx);
10891 }
10892
10893 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10894 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10895 let selections = self
10896 .selections
10897 .all(&self.display_snapshot(cx))
10898 .into_iter()
10899 .map(|s| s.range())
10900 .collect();
10901 self.restore_hunks_in_ranges(selections, window, cx);
10902 }
10903
10904 pub fn restore_hunks_in_ranges(
10905 &mut self,
10906 ranges: Vec<Range<Point>>,
10907 window: &mut Window,
10908 cx: &mut Context<Editor>,
10909 ) {
10910 let mut revert_changes = HashMap::default();
10911 let chunk_by = self
10912 .snapshot(window, cx)
10913 .hunks_for_ranges(ranges)
10914 .into_iter()
10915 .chunk_by(|hunk| hunk.buffer_id);
10916 for (buffer_id, hunks) in &chunk_by {
10917 let hunks = hunks.collect::<Vec<_>>();
10918 for hunk in &hunks {
10919 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10920 }
10921 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10922 }
10923 drop(chunk_by);
10924 if !revert_changes.is_empty() {
10925 self.transact(window, cx, |editor, window, cx| {
10926 editor.restore(revert_changes, window, cx);
10927 });
10928 }
10929 }
10930
10931 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10932 if let Some(status) = self
10933 .addons
10934 .iter()
10935 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10936 {
10937 return Some(status);
10938 }
10939 self.project
10940 .as_ref()?
10941 .read(cx)
10942 .status_for_buffer_id(buffer_id, cx)
10943 }
10944
10945 pub fn open_active_item_in_terminal(
10946 &mut self,
10947 _: &OpenInTerminal,
10948 window: &mut Window,
10949 cx: &mut Context<Self>,
10950 ) {
10951 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10952 let project_path = buffer.read(cx).project_path(cx)?;
10953 let project = self.project()?.read(cx);
10954 let entry = project.entry_for_path(&project_path, cx)?;
10955 let parent = match &entry.canonical_path {
10956 Some(canonical_path) => canonical_path.to_path_buf(),
10957 None => project.absolute_path(&project_path, cx)?,
10958 }
10959 .parent()?
10960 .to_path_buf();
10961 Some(parent)
10962 }) {
10963 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10964 }
10965 }
10966
10967 fn set_breakpoint_context_menu(
10968 &mut self,
10969 display_row: DisplayRow,
10970 position: Option<Anchor>,
10971 clicked_point: gpui::Point<Pixels>,
10972 window: &mut Window,
10973 cx: &mut Context<Self>,
10974 ) {
10975 let source = self
10976 .buffer
10977 .read(cx)
10978 .snapshot(cx)
10979 .anchor_before(Point::new(display_row.0, 0u32));
10980
10981 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10982
10983 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10984 self,
10985 source,
10986 clicked_point,
10987 context_menu,
10988 window,
10989 cx,
10990 );
10991 }
10992
10993 fn add_edit_breakpoint_block(
10994 &mut self,
10995 anchor: Anchor,
10996 breakpoint: &Breakpoint,
10997 edit_action: BreakpointPromptEditAction,
10998 window: &mut Window,
10999 cx: &mut Context<Self>,
11000 ) {
11001 let weak_editor = cx.weak_entity();
11002 let bp_prompt = cx.new(|cx| {
11003 BreakpointPromptEditor::new(
11004 weak_editor,
11005 anchor,
11006 breakpoint.clone(),
11007 edit_action,
11008 window,
11009 cx,
11010 )
11011 });
11012
11013 let height = bp_prompt.update(cx, |this, cx| {
11014 this.prompt
11015 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11016 });
11017 let cloned_prompt = bp_prompt.clone();
11018 let blocks = vec![BlockProperties {
11019 style: BlockStyle::Sticky,
11020 placement: BlockPlacement::Above(anchor),
11021 height: Some(height),
11022 render: Arc::new(move |cx| {
11023 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11024 cloned_prompt.clone().into_any_element()
11025 }),
11026 priority: 0,
11027 }];
11028
11029 let focus_handle = bp_prompt.focus_handle(cx);
11030 window.focus(&focus_handle);
11031
11032 let block_ids = self.insert_blocks(blocks, None, cx);
11033 bp_prompt.update(cx, |prompt, _| {
11034 prompt.add_block_ids(block_ids);
11035 });
11036 }
11037
11038 pub(crate) fn breakpoint_at_row(
11039 &self,
11040 row: u32,
11041 window: &mut Window,
11042 cx: &mut Context<Self>,
11043 ) -> Option<(Anchor, Breakpoint)> {
11044 let snapshot = self.snapshot(window, cx);
11045 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11046
11047 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11048 }
11049
11050 pub(crate) fn breakpoint_at_anchor(
11051 &self,
11052 breakpoint_position: Anchor,
11053 snapshot: &EditorSnapshot,
11054 cx: &mut Context<Self>,
11055 ) -> Option<(Anchor, Breakpoint)> {
11056 let buffer = self
11057 .buffer
11058 .read(cx)
11059 .buffer_for_anchor(breakpoint_position, cx)?;
11060
11061 let enclosing_excerpt = breakpoint_position.excerpt_id;
11062 let buffer_snapshot = buffer.read(cx).snapshot();
11063
11064 let row = buffer_snapshot
11065 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11066 .row;
11067
11068 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11069 let anchor_end = snapshot
11070 .buffer_snapshot()
11071 .anchor_after(Point::new(row, line_len));
11072
11073 self.breakpoint_store
11074 .as_ref()?
11075 .read_with(cx, |breakpoint_store, cx| {
11076 breakpoint_store
11077 .breakpoints(
11078 &buffer,
11079 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11080 &buffer_snapshot,
11081 cx,
11082 )
11083 .next()
11084 .and_then(|(bp, _)| {
11085 let breakpoint_row = buffer_snapshot
11086 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11087 .row;
11088
11089 if breakpoint_row == row {
11090 snapshot
11091 .buffer_snapshot()
11092 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11093 .map(|position| (position, bp.bp.clone()))
11094 } else {
11095 None
11096 }
11097 })
11098 })
11099 }
11100
11101 pub fn edit_log_breakpoint(
11102 &mut self,
11103 _: &EditLogBreakpoint,
11104 window: &mut Window,
11105 cx: &mut Context<Self>,
11106 ) {
11107 if self.breakpoint_store.is_none() {
11108 return;
11109 }
11110
11111 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11112 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11113 message: None,
11114 state: BreakpointState::Enabled,
11115 condition: None,
11116 hit_condition: None,
11117 });
11118
11119 self.add_edit_breakpoint_block(
11120 anchor,
11121 &breakpoint,
11122 BreakpointPromptEditAction::Log,
11123 window,
11124 cx,
11125 );
11126 }
11127 }
11128
11129 fn breakpoints_at_cursors(
11130 &self,
11131 window: &mut Window,
11132 cx: &mut Context<Self>,
11133 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11134 let snapshot = self.snapshot(window, cx);
11135 let cursors = self
11136 .selections
11137 .disjoint_anchors_arc()
11138 .iter()
11139 .map(|selection| {
11140 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11141
11142 let breakpoint_position = self
11143 .breakpoint_at_row(cursor_position.row, window, cx)
11144 .map(|bp| bp.0)
11145 .unwrap_or_else(|| {
11146 snapshot
11147 .display_snapshot
11148 .buffer_snapshot()
11149 .anchor_after(Point::new(cursor_position.row, 0))
11150 });
11151
11152 let breakpoint = self
11153 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11154 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11155
11156 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11157 })
11158 // 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.
11159 .collect::<HashMap<Anchor, _>>();
11160
11161 cursors.into_iter().collect()
11162 }
11163
11164 pub fn enable_breakpoint(
11165 &mut self,
11166 _: &crate::actions::EnableBreakpoint,
11167 window: &mut Window,
11168 cx: &mut Context<Self>,
11169 ) {
11170 if self.breakpoint_store.is_none() {
11171 return;
11172 }
11173
11174 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11175 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11176 continue;
11177 };
11178 self.edit_breakpoint_at_anchor(
11179 anchor,
11180 breakpoint,
11181 BreakpointEditAction::InvertState,
11182 cx,
11183 );
11184 }
11185 }
11186
11187 pub fn disable_breakpoint(
11188 &mut self,
11189 _: &crate::actions::DisableBreakpoint,
11190 window: &mut Window,
11191 cx: &mut Context<Self>,
11192 ) {
11193 if self.breakpoint_store.is_none() {
11194 return;
11195 }
11196
11197 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11198 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11199 continue;
11200 };
11201 self.edit_breakpoint_at_anchor(
11202 anchor,
11203 breakpoint,
11204 BreakpointEditAction::InvertState,
11205 cx,
11206 );
11207 }
11208 }
11209
11210 pub fn toggle_breakpoint(
11211 &mut self,
11212 _: &crate::actions::ToggleBreakpoint,
11213 window: &mut Window,
11214 cx: &mut Context<Self>,
11215 ) {
11216 if self.breakpoint_store.is_none() {
11217 return;
11218 }
11219
11220 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11221 if let Some(breakpoint) = breakpoint {
11222 self.edit_breakpoint_at_anchor(
11223 anchor,
11224 breakpoint,
11225 BreakpointEditAction::Toggle,
11226 cx,
11227 );
11228 } else {
11229 self.edit_breakpoint_at_anchor(
11230 anchor,
11231 Breakpoint::new_standard(),
11232 BreakpointEditAction::Toggle,
11233 cx,
11234 );
11235 }
11236 }
11237 }
11238
11239 pub fn edit_breakpoint_at_anchor(
11240 &mut self,
11241 breakpoint_position: Anchor,
11242 breakpoint: Breakpoint,
11243 edit_action: BreakpointEditAction,
11244 cx: &mut Context<Self>,
11245 ) {
11246 let Some(breakpoint_store) = &self.breakpoint_store else {
11247 return;
11248 };
11249
11250 let Some(buffer) = self
11251 .buffer
11252 .read(cx)
11253 .buffer_for_anchor(breakpoint_position, cx)
11254 else {
11255 return;
11256 };
11257
11258 breakpoint_store.update(cx, |breakpoint_store, cx| {
11259 breakpoint_store.toggle_breakpoint(
11260 buffer,
11261 BreakpointWithPosition {
11262 position: breakpoint_position.text_anchor,
11263 bp: breakpoint,
11264 },
11265 edit_action,
11266 cx,
11267 );
11268 });
11269
11270 cx.notify();
11271 }
11272
11273 #[cfg(any(test, feature = "test-support"))]
11274 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11275 self.breakpoint_store.clone()
11276 }
11277
11278 pub fn prepare_restore_change(
11279 &self,
11280 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11281 hunk: &MultiBufferDiffHunk,
11282 cx: &mut App,
11283 ) -> Option<()> {
11284 if hunk.is_created_file() {
11285 return None;
11286 }
11287 let buffer = self.buffer.read(cx);
11288 let diff = buffer.diff_for(hunk.buffer_id)?;
11289 let buffer = buffer.buffer(hunk.buffer_id)?;
11290 let buffer = buffer.read(cx);
11291 let original_text = diff
11292 .read(cx)
11293 .base_text()
11294 .as_rope()
11295 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11296 let buffer_snapshot = buffer.snapshot();
11297 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11298 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11299 probe
11300 .0
11301 .start
11302 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11303 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11304 }) {
11305 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11306 Some(())
11307 } else {
11308 None
11309 }
11310 }
11311
11312 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11313 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11314 }
11315
11316 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11317 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11318 }
11319
11320 fn manipulate_lines<M>(
11321 &mut self,
11322 window: &mut Window,
11323 cx: &mut Context<Self>,
11324 mut manipulate: M,
11325 ) where
11326 M: FnMut(&str) -> LineManipulationResult,
11327 {
11328 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11329
11330 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11331 let buffer = self.buffer.read(cx).snapshot(cx);
11332
11333 let mut edits = Vec::new();
11334
11335 let selections = self.selections.all::<Point>(&display_map);
11336 let mut selections = selections.iter().peekable();
11337 let mut contiguous_row_selections = Vec::new();
11338 let mut new_selections = Vec::new();
11339 let mut added_lines = 0;
11340 let mut removed_lines = 0;
11341
11342 while let Some(selection) = selections.next() {
11343 let (start_row, end_row) = consume_contiguous_rows(
11344 &mut contiguous_row_selections,
11345 selection,
11346 &display_map,
11347 &mut selections,
11348 );
11349
11350 let start_point = Point::new(start_row.0, 0);
11351 let end_point = Point::new(
11352 end_row.previous_row().0,
11353 buffer.line_len(end_row.previous_row()),
11354 );
11355 let text = buffer
11356 .text_for_range(start_point..end_point)
11357 .collect::<String>();
11358
11359 let LineManipulationResult {
11360 new_text,
11361 line_count_before,
11362 line_count_after,
11363 } = manipulate(&text);
11364
11365 edits.push((start_point..end_point, new_text));
11366
11367 // Selections must change based on added and removed line count
11368 let start_row =
11369 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11370 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11371 new_selections.push(Selection {
11372 id: selection.id,
11373 start: start_row,
11374 end: end_row,
11375 goal: SelectionGoal::None,
11376 reversed: selection.reversed,
11377 });
11378
11379 if line_count_after > line_count_before {
11380 added_lines += line_count_after - line_count_before;
11381 } else if line_count_before > line_count_after {
11382 removed_lines += line_count_before - line_count_after;
11383 }
11384 }
11385
11386 self.transact(window, cx, |this, window, cx| {
11387 let buffer = this.buffer.update(cx, |buffer, cx| {
11388 buffer.edit(edits, None, cx);
11389 buffer.snapshot(cx)
11390 });
11391
11392 // Recalculate offsets on newly edited buffer
11393 let new_selections = new_selections
11394 .iter()
11395 .map(|s| {
11396 let start_point = Point::new(s.start.0, 0);
11397 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11398 Selection {
11399 id: s.id,
11400 start: buffer.point_to_offset(start_point),
11401 end: buffer.point_to_offset(end_point),
11402 goal: s.goal,
11403 reversed: s.reversed,
11404 }
11405 })
11406 .collect();
11407
11408 this.change_selections(Default::default(), window, cx, |s| {
11409 s.select(new_selections);
11410 });
11411
11412 this.request_autoscroll(Autoscroll::fit(), cx);
11413 });
11414 }
11415
11416 fn manipulate_immutable_lines<Fn>(
11417 &mut self,
11418 window: &mut Window,
11419 cx: &mut Context<Self>,
11420 mut callback: Fn,
11421 ) where
11422 Fn: FnMut(&mut Vec<&str>),
11423 {
11424 self.manipulate_lines(window, cx, |text| {
11425 let mut lines: Vec<&str> = text.split('\n').collect();
11426 let line_count_before = lines.len();
11427
11428 callback(&mut lines);
11429
11430 LineManipulationResult {
11431 new_text: lines.join("\n"),
11432 line_count_before,
11433 line_count_after: lines.len(),
11434 }
11435 });
11436 }
11437
11438 fn manipulate_mutable_lines<Fn>(
11439 &mut self,
11440 window: &mut Window,
11441 cx: &mut Context<Self>,
11442 mut callback: Fn,
11443 ) where
11444 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11445 {
11446 self.manipulate_lines(window, cx, |text| {
11447 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11448 let line_count_before = lines.len();
11449
11450 callback(&mut lines);
11451
11452 LineManipulationResult {
11453 new_text: lines.join("\n"),
11454 line_count_before,
11455 line_count_after: lines.len(),
11456 }
11457 });
11458 }
11459
11460 pub fn convert_indentation_to_spaces(
11461 &mut self,
11462 _: &ConvertIndentationToSpaces,
11463 window: &mut Window,
11464 cx: &mut Context<Self>,
11465 ) {
11466 let settings = self.buffer.read(cx).language_settings(cx);
11467 let tab_size = settings.tab_size.get() as usize;
11468
11469 self.manipulate_mutable_lines(window, cx, |lines| {
11470 // Allocates a reasonably sized scratch buffer once for the whole loop
11471 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11472 // Avoids recomputing spaces that could be inserted many times
11473 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11474 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11475 .collect();
11476
11477 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11478 let mut chars = line.as_ref().chars();
11479 let mut col = 0;
11480 let mut changed = false;
11481
11482 for ch in chars.by_ref() {
11483 match ch {
11484 ' ' => {
11485 reindented_line.push(' ');
11486 col += 1;
11487 }
11488 '\t' => {
11489 // \t are converted to spaces depending on the current column
11490 let spaces_len = tab_size - (col % tab_size);
11491 reindented_line.extend(&space_cache[spaces_len - 1]);
11492 col += spaces_len;
11493 changed = true;
11494 }
11495 _ => {
11496 // If we dont append before break, the character is consumed
11497 reindented_line.push(ch);
11498 break;
11499 }
11500 }
11501 }
11502
11503 if !changed {
11504 reindented_line.clear();
11505 continue;
11506 }
11507 // Append the rest of the line and replace old reference with new one
11508 reindented_line.extend(chars);
11509 *line = Cow::Owned(reindented_line.clone());
11510 reindented_line.clear();
11511 }
11512 });
11513 }
11514
11515 pub fn convert_indentation_to_tabs(
11516 &mut self,
11517 _: &ConvertIndentationToTabs,
11518 window: &mut Window,
11519 cx: &mut Context<Self>,
11520 ) {
11521 let settings = self.buffer.read(cx).language_settings(cx);
11522 let tab_size = settings.tab_size.get() as usize;
11523
11524 self.manipulate_mutable_lines(window, cx, |lines| {
11525 // Allocates a reasonably sized buffer once for the whole loop
11526 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11527 // Avoids recomputing spaces that could be inserted many times
11528 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11529 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11530 .collect();
11531
11532 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11533 let mut chars = line.chars();
11534 let mut spaces_count = 0;
11535 let mut first_non_indent_char = None;
11536 let mut changed = false;
11537
11538 for ch in chars.by_ref() {
11539 match ch {
11540 ' ' => {
11541 // Keep track of spaces. Append \t when we reach tab_size
11542 spaces_count += 1;
11543 changed = true;
11544 if spaces_count == tab_size {
11545 reindented_line.push('\t');
11546 spaces_count = 0;
11547 }
11548 }
11549 '\t' => {
11550 reindented_line.push('\t');
11551 spaces_count = 0;
11552 }
11553 _ => {
11554 // Dont append it yet, we might have remaining spaces
11555 first_non_indent_char = Some(ch);
11556 break;
11557 }
11558 }
11559 }
11560
11561 if !changed {
11562 reindented_line.clear();
11563 continue;
11564 }
11565 // Remaining spaces that didn't make a full tab stop
11566 if spaces_count > 0 {
11567 reindented_line.extend(&space_cache[spaces_count - 1]);
11568 }
11569 // If we consume an extra character that was not indentation, add it back
11570 if let Some(extra_char) = first_non_indent_char {
11571 reindented_line.push(extra_char);
11572 }
11573 // Append the rest of the line and replace old reference with new one
11574 reindented_line.extend(chars);
11575 *line = Cow::Owned(reindented_line.clone());
11576 reindented_line.clear();
11577 }
11578 });
11579 }
11580
11581 pub fn convert_to_upper_case(
11582 &mut self,
11583 _: &ConvertToUpperCase,
11584 window: &mut Window,
11585 cx: &mut Context<Self>,
11586 ) {
11587 self.manipulate_text(window, cx, |text| text.to_uppercase())
11588 }
11589
11590 pub fn convert_to_lower_case(
11591 &mut self,
11592 _: &ConvertToLowerCase,
11593 window: &mut Window,
11594 cx: &mut Context<Self>,
11595 ) {
11596 self.manipulate_text(window, cx, |text| text.to_lowercase())
11597 }
11598
11599 pub fn convert_to_title_case(
11600 &mut self,
11601 _: &ConvertToTitleCase,
11602 window: &mut Window,
11603 cx: &mut Context<Self>,
11604 ) {
11605 self.manipulate_text(window, cx, |text| {
11606 text.split('\n')
11607 .map(|line| line.to_case(Case::Title))
11608 .join("\n")
11609 })
11610 }
11611
11612 pub fn convert_to_snake_case(
11613 &mut self,
11614 _: &ConvertToSnakeCase,
11615 window: &mut Window,
11616 cx: &mut Context<Self>,
11617 ) {
11618 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11619 }
11620
11621 pub fn convert_to_kebab_case(
11622 &mut self,
11623 _: &ConvertToKebabCase,
11624 window: &mut Window,
11625 cx: &mut Context<Self>,
11626 ) {
11627 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11628 }
11629
11630 pub fn convert_to_upper_camel_case(
11631 &mut self,
11632 _: &ConvertToUpperCamelCase,
11633 window: &mut Window,
11634 cx: &mut Context<Self>,
11635 ) {
11636 self.manipulate_text(window, cx, |text| {
11637 text.split('\n')
11638 .map(|line| line.to_case(Case::UpperCamel))
11639 .join("\n")
11640 })
11641 }
11642
11643 pub fn convert_to_lower_camel_case(
11644 &mut self,
11645 _: &ConvertToLowerCamelCase,
11646 window: &mut Window,
11647 cx: &mut Context<Self>,
11648 ) {
11649 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11650 }
11651
11652 pub fn convert_to_opposite_case(
11653 &mut self,
11654 _: &ConvertToOppositeCase,
11655 window: &mut Window,
11656 cx: &mut Context<Self>,
11657 ) {
11658 self.manipulate_text(window, cx, |text| {
11659 text.chars()
11660 .fold(String::with_capacity(text.len()), |mut t, c| {
11661 if c.is_uppercase() {
11662 t.extend(c.to_lowercase());
11663 } else {
11664 t.extend(c.to_uppercase());
11665 }
11666 t
11667 })
11668 })
11669 }
11670
11671 pub fn convert_to_sentence_case(
11672 &mut self,
11673 _: &ConvertToSentenceCase,
11674 window: &mut Window,
11675 cx: &mut Context<Self>,
11676 ) {
11677 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11678 }
11679
11680 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11681 self.manipulate_text(window, cx, |text| {
11682 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11683 if has_upper_case_characters {
11684 text.to_lowercase()
11685 } else {
11686 text.to_uppercase()
11687 }
11688 })
11689 }
11690
11691 pub fn convert_to_rot13(
11692 &mut self,
11693 _: &ConvertToRot13,
11694 window: &mut Window,
11695 cx: &mut Context<Self>,
11696 ) {
11697 self.manipulate_text(window, cx, |text| {
11698 text.chars()
11699 .map(|c| match c {
11700 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11701 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11702 _ => c,
11703 })
11704 .collect()
11705 })
11706 }
11707
11708 pub fn convert_to_rot47(
11709 &mut self,
11710 _: &ConvertToRot47,
11711 window: &mut Window,
11712 cx: &mut Context<Self>,
11713 ) {
11714 self.manipulate_text(window, cx, |text| {
11715 text.chars()
11716 .map(|c| {
11717 let code_point = c as u32;
11718 if code_point >= 33 && code_point <= 126 {
11719 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11720 }
11721 c
11722 })
11723 .collect()
11724 })
11725 }
11726
11727 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11728 where
11729 Fn: FnMut(&str) -> String,
11730 {
11731 let buffer = self.buffer.read(cx).snapshot(cx);
11732
11733 let mut new_selections = Vec::new();
11734 let mut edits = Vec::new();
11735 let mut selection_adjustment = 0isize;
11736
11737 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11738 let selection_is_empty = selection.is_empty();
11739
11740 let (start, end) = if selection_is_empty {
11741 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11742 (word_range.start, word_range.end)
11743 } else {
11744 (
11745 buffer.point_to_offset(selection.start),
11746 buffer.point_to_offset(selection.end),
11747 )
11748 };
11749
11750 let text = buffer.text_for_range(start..end).collect::<String>();
11751 let old_length = text.len() as isize;
11752 let text = callback(&text);
11753
11754 new_selections.push(Selection {
11755 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
11756 end: MultiBufferOffset(
11757 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
11758 ),
11759 goal: SelectionGoal::None,
11760 id: selection.id,
11761 reversed: selection.reversed,
11762 });
11763
11764 selection_adjustment += old_length - text.len() as isize;
11765
11766 edits.push((start..end, text));
11767 }
11768
11769 self.transact(window, cx, |this, window, cx| {
11770 this.buffer.update(cx, |buffer, cx| {
11771 buffer.edit(edits, None, cx);
11772 });
11773
11774 this.change_selections(Default::default(), window, cx, |s| {
11775 s.select(new_selections);
11776 });
11777
11778 this.request_autoscroll(Autoscroll::fit(), cx);
11779 });
11780 }
11781
11782 pub fn move_selection_on_drop(
11783 &mut self,
11784 selection: &Selection<Anchor>,
11785 target: DisplayPoint,
11786 is_cut: bool,
11787 window: &mut Window,
11788 cx: &mut Context<Self>,
11789 ) {
11790 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11791 let buffer = display_map.buffer_snapshot();
11792 let mut edits = Vec::new();
11793 let insert_point = display_map
11794 .clip_point(target, Bias::Left)
11795 .to_point(&display_map);
11796 let text = buffer
11797 .text_for_range(selection.start..selection.end)
11798 .collect::<String>();
11799 if is_cut {
11800 edits.push(((selection.start..selection.end), String::new()));
11801 }
11802 let insert_anchor = buffer.anchor_before(insert_point);
11803 edits.push(((insert_anchor..insert_anchor), text));
11804 let last_edit_start = insert_anchor.bias_left(buffer);
11805 let last_edit_end = insert_anchor.bias_right(buffer);
11806 self.transact(window, cx, |this, window, cx| {
11807 this.buffer.update(cx, |buffer, cx| {
11808 buffer.edit(edits, None, cx);
11809 });
11810 this.change_selections(Default::default(), window, cx, |s| {
11811 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11812 });
11813 });
11814 }
11815
11816 pub fn clear_selection_drag_state(&mut self) {
11817 self.selection_drag_state = SelectionDragState::None;
11818 }
11819
11820 pub fn duplicate(
11821 &mut self,
11822 upwards: bool,
11823 whole_lines: bool,
11824 window: &mut Window,
11825 cx: &mut Context<Self>,
11826 ) {
11827 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11828
11829 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11830 let buffer = display_map.buffer_snapshot();
11831 let selections = self.selections.all::<Point>(&display_map);
11832
11833 let mut edits = Vec::new();
11834 let mut selections_iter = selections.iter().peekable();
11835 while let Some(selection) = selections_iter.next() {
11836 let mut rows = selection.spanned_rows(false, &display_map);
11837 // duplicate line-wise
11838 if whole_lines || selection.start == selection.end {
11839 // Avoid duplicating the same lines twice.
11840 while let Some(next_selection) = selections_iter.peek() {
11841 let next_rows = next_selection.spanned_rows(false, &display_map);
11842 if next_rows.start < rows.end {
11843 rows.end = next_rows.end;
11844 selections_iter.next().unwrap();
11845 } else {
11846 break;
11847 }
11848 }
11849
11850 // Copy the text from the selected row region and splice it either at the start
11851 // or end of the region.
11852 let start = Point::new(rows.start.0, 0);
11853 let end = Point::new(
11854 rows.end.previous_row().0,
11855 buffer.line_len(rows.end.previous_row()),
11856 );
11857
11858 let mut text = buffer.text_for_range(start..end).collect::<String>();
11859
11860 let insert_location = if upwards {
11861 // When duplicating upward, we need to insert before the current line.
11862 // If we're on the last line and it doesn't end with a newline,
11863 // we need to add a newline before the duplicated content.
11864 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11865 && buffer.max_point().column > 0
11866 && !text.ends_with('\n');
11867
11868 if needs_leading_newline {
11869 text.insert(0, '\n');
11870 end
11871 } else {
11872 text.push('\n');
11873 Point::new(rows.start.0, 0)
11874 }
11875 } else {
11876 text.push('\n');
11877 start
11878 };
11879 edits.push((insert_location..insert_location, text));
11880 } else {
11881 // duplicate character-wise
11882 let start = selection.start;
11883 let end = selection.end;
11884 let text = buffer.text_for_range(start..end).collect::<String>();
11885 edits.push((selection.end..selection.end, text));
11886 }
11887 }
11888
11889 self.transact(window, cx, |this, window, cx| {
11890 this.buffer.update(cx, |buffer, cx| {
11891 buffer.edit(edits, None, cx);
11892 });
11893
11894 // When duplicating upward with whole lines, move the cursor to the duplicated line
11895 if upwards && whole_lines {
11896 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11897
11898 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11899 let mut new_ranges = Vec::new();
11900 let selections = s.all::<Point>(&display_map);
11901 let mut selections_iter = selections.iter().peekable();
11902
11903 while let Some(first_selection) = selections_iter.next() {
11904 // Group contiguous selections together to find the total row span
11905 let mut group_selections = vec![first_selection];
11906 let mut rows = first_selection.spanned_rows(false, &display_map);
11907
11908 while let Some(next_selection) = selections_iter.peek() {
11909 let next_rows = next_selection.spanned_rows(false, &display_map);
11910 if next_rows.start < rows.end {
11911 rows.end = next_rows.end;
11912 group_selections.push(selections_iter.next().unwrap());
11913 } else {
11914 break;
11915 }
11916 }
11917
11918 let row_count = rows.end.0 - rows.start.0;
11919
11920 // Move all selections in this group up by the total number of duplicated rows
11921 for selection in group_selections {
11922 let new_start = Point::new(
11923 selection.start.row.saturating_sub(row_count),
11924 selection.start.column,
11925 );
11926
11927 let new_end = Point::new(
11928 selection.end.row.saturating_sub(row_count),
11929 selection.end.column,
11930 );
11931
11932 new_ranges.push(new_start..new_end);
11933 }
11934 }
11935
11936 s.select_ranges(new_ranges);
11937 });
11938 }
11939
11940 this.request_autoscroll(Autoscroll::fit(), cx);
11941 });
11942 }
11943
11944 pub fn duplicate_line_up(
11945 &mut self,
11946 _: &DuplicateLineUp,
11947 window: &mut Window,
11948 cx: &mut Context<Self>,
11949 ) {
11950 self.duplicate(true, true, window, cx);
11951 }
11952
11953 pub fn duplicate_line_down(
11954 &mut self,
11955 _: &DuplicateLineDown,
11956 window: &mut Window,
11957 cx: &mut Context<Self>,
11958 ) {
11959 self.duplicate(false, true, window, cx);
11960 }
11961
11962 pub fn duplicate_selection(
11963 &mut self,
11964 _: &DuplicateSelection,
11965 window: &mut Window,
11966 cx: &mut Context<Self>,
11967 ) {
11968 self.duplicate(false, false, window, cx);
11969 }
11970
11971 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11972 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11973 if self.mode.is_single_line() {
11974 cx.propagate();
11975 return;
11976 }
11977
11978 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11979 let buffer = self.buffer.read(cx).snapshot(cx);
11980
11981 let mut edits = Vec::new();
11982 let mut unfold_ranges = Vec::new();
11983 let mut refold_creases = Vec::new();
11984
11985 let selections = self.selections.all::<Point>(&display_map);
11986 let mut selections = selections.iter().peekable();
11987 let mut contiguous_row_selections = Vec::new();
11988 let mut new_selections = Vec::new();
11989
11990 while let Some(selection) = selections.next() {
11991 // Find all the selections that span a contiguous row range
11992 let (start_row, end_row) = consume_contiguous_rows(
11993 &mut contiguous_row_selections,
11994 selection,
11995 &display_map,
11996 &mut selections,
11997 );
11998
11999 // Move the text spanned by the row range to be before the line preceding the row range
12000 if start_row.0 > 0 {
12001 let range_to_move = Point::new(
12002 start_row.previous_row().0,
12003 buffer.line_len(start_row.previous_row()),
12004 )
12005 ..Point::new(
12006 end_row.previous_row().0,
12007 buffer.line_len(end_row.previous_row()),
12008 );
12009 let insertion_point = display_map
12010 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12011 .0;
12012
12013 // Don't move lines across excerpts
12014 if buffer
12015 .excerpt_containing(insertion_point..range_to_move.end)
12016 .is_some()
12017 {
12018 let text = buffer
12019 .text_for_range(range_to_move.clone())
12020 .flat_map(|s| s.chars())
12021 .skip(1)
12022 .chain(['\n'])
12023 .collect::<String>();
12024
12025 edits.push((
12026 buffer.anchor_after(range_to_move.start)
12027 ..buffer.anchor_before(range_to_move.end),
12028 String::new(),
12029 ));
12030 let insertion_anchor = buffer.anchor_after(insertion_point);
12031 edits.push((insertion_anchor..insertion_anchor, text));
12032
12033 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12034
12035 // Move selections up
12036 new_selections.extend(contiguous_row_selections.drain(..).map(
12037 |mut selection| {
12038 selection.start.row -= row_delta;
12039 selection.end.row -= row_delta;
12040 selection
12041 },
12042 ));
12043
12044 // Move folds up
12045 unfold_ranges.push(range_to_move.clone());
12046 for fold in display_map.folds_in_range(
12047 buffer.anchor_before(range_to_move.start)
12048 ..buffer.anchor_after(range_to_move.end),
12049 ) {
12050 let mut start = fold.range.start.to_point(&buffer);
12051 let mut end = fold.range.end.to_point(&buffer);
12052 start.row -= row_delta;
12053 end.row -= row_delta;
12054 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12055 }
12056 }
12057 }
12058
12059 // If we didn't move line(s), preserve the existing selections
12060 new_selections.append(&mut contiguous_row_selections);
12061 }
12062
12063 self.transact(window, cx, |this, window, cx| {
12064 this.unfold_ranges(&unfold_ranges, true, true, cx);
12065 this.buffer.update(cx, |buffer, cx| {
12066 for (range, text) in edits {
12067 buffer.edit([(range, text)], None, cx);
12068 }
12069 });
12070 this.fold_creases(refold_creases, true, window, cx);
12071 this.change_selections(Default::default(), window, cx, |s| {
12072 s.select(new_selections);
12073 })
12074 });
12075 }
12076
12077 pub fn move_line_down(
12078 &mut self,
12079 _: &MoveLineDown,
12080 window: &mut Window,
12081 cx: &mut Context<Self>,
12082 ) {
12083 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12084 if self.mode.is_single_line() {
12085 cx.propagate();
12086 return;
12087 }
12088
12089 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12090 let buffer = self.buffer.read(cx).snapshot(cx);
12091
12092 let mut edits = Vec::new();
12093 let mut unfold_ranges = Vec::new();
12094 let mut refold_creases = Vec::new();
12095
12096 let selections = self.selections.all::<Point>(&display_map);
12097 let mut selections = selections.iter().peekable();
12098 let mut contiguous_row_selections = Vec::new();
12099 let mut new_selections = Vec::new();
12100
12101 while let Some(selection) = selections.next() {
12102 // Find all the selections that span a contiguous row range
12103 let (start_row, end_row) = consume_contiguous_rows(
12104 &mut contiguous_row_selections,
12105 selection,
12106 &display_map,
12107 &mut selections,
12108 );
12109
12110 // Move the text spanned by the row range to be after the last line of the row range
12111 if end_row.0 <= buffer.max_point().row {
12112 let range_to_move =
12113 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12114 let insertion_point = display_map
12115 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12116 .0;
12117
12118 // Don't move lines across excerpt boundaries
12119 if buffer
12120 .excerpt_containing(range_to_move.start..insertion_point)
12121 .is_some()
12122 {
12123 let mut text = String::from("\n");
12124 text.extend(buffer.text_for_range(range_to_move.clone()));
12125 text.pop(); // Drop trailing newline
12126 edits.push((
12127 buffer.anchor_after(range_to_move.start)
12128 ..buffer.anchor_before(range_to_move.end),
12129 String::new(),
12130 ));
12131 let insertion_anchor = buffer.anchor_after(insertion_point);
12132 edits.push((insertion_anchor..insertion_anchor, text));
12133
12134 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12135
12136 // Move selections down
12137 new_selections.extend(contiguous_row_selections.drain(..).map(
12138 |mut selection| {
12139 selection.start.row += row_delta;
12140 selection.end.row += row_delta;
12141 selection
12142 },
12143 ));
12144
12145 // Move folds down
12146 unfold_ranges.push(range_to_move.clone());
12147 for fold in display_map.folds_in_range(
12148 buffer.anchor_before(range_to_move.start)
12149 ..buffer.anchor_after(range_to_move.end),
12150 ) {
12151 let mut start = fold.range.start.to_point(&buffer);
12152 let mut end = fold.range.end.to_point(&buffer);
12153 start.row += row_delta;
12154 end.row += row_delta;
12155 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12156 }
12157 }
12158 }
12159
12160 // If we didn't move line(s), preserve the existing selections
12161 new_selections.append(&mut contiguous_row_selections);
12162 }
12163
12164 self.transact(window, cx, |this, window, cx| {
12165 this.unfold_ranges(&unfold_ranges, true, true, cx);
12166 this.buffer.update(cx, |buffer, cx| {
12167 for (range, text) in edits {
12168 buffer.edit([(range, text)], None, cx);
12169 }
12170 });
12171 this.fold_creases(refold_creases, true, window, cx);
12172 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12173 });
12174 }
12175
12176 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12177 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12178 let text_layout_details = &self.text_layout_details(window);
12179 self.transact(window, cx, |this, window, cx| {
12180 let edits = this.change_selections(Default::default(), window, cx, |s| {
12181 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12182 s.move_with(|display_map, selection| {
12183 if !selection.is_empty() {
12184 return;
12185 }
12186
12187 let mut head = selection.head();
12188 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12189 if head.column() == display_map.line_len(head.row()) {
12190 transpose_offset = display_map
12191 .buffer_snapshot()
12192 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12193 }
12194
12195 if transpose_offset == MultiBufferOffset(0) {
12196 return;
12197 }
12198
12199 *head.column_mut() += 1;
12200 head = display_map.clip_point(head, Bias::Right);
12201 let goal = SelectionGoal::HorizontalPosition(
12202 display_map
12203 .x_for_display_point(head, text_layout_details)
12204 .into(),
12205 );
12206 selection.collapse_to(head, goal);
12207
12208 let transpose_start = display_map
12209 .buffer_snapshot()
12210 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12211 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12212 let transpose_end = display_map
12213 .buffer_snapshot()
12214 .clip_offset(transpose_offset + 1usize, Bias::Right);
12215 if let Some(ch) = display_map
12216 .buffer_snapshot()
12217 .chars_at(transpose_start)
12218 .next()
12219 {
12220 edits.push((transpose_start..transpose_offset, String::new()));
12221 edits.push((transpose_end..transpose_end, ch.to_string()));
12222 }
12223 }
12224 });
12225 edits
12226 });
12227 this.buffer
12228 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12229 let selections = this
12230 .selections
12231 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12232 this.change_selections(Default::default(), window, cx, |s| {
12233 s.select(selections);
12234 });
12235 });
12236 }
12237
12238 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12239 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12240 if self.mode.is_single_line() {
12241 cx.propagate();
12242 return;
12243 }
12244
12245 self.rewrap_impl(RewrapOptions::default(), cx)
12246 }
12247
12248 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12249 let buffer = self.buffer.read(cx).snapshot(cx);
12250 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12251
12252 #[derive(Clone, Debug, PartialEq)]
12253 enum CommentFormat {
12254 /// single line comment, with prefix for line
12255 Line(String),
12256 /// single line within a block comment, with prefix for line
12257 BlockLine(String),
12258 /// a single line of a block comment that includes the initial delimiter
12259 BlockCommentWithStart(BlockCommentConfig),
12260 /// a single line of a block comment that includes the ending delimiter
12261 BlockCommentWithEnd(BlockCommentConfig),
12262 }
12263
12264 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12265 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12266 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12267 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12268 .peekable();
12269
12270 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12271 row
12272 } else {
12273 return Vec::new();
12274 };
12275
12276 let language_settings = buffer.language_settings_at(selection.head(), cx);
12277 let language_scope = buffer.language_scope_at(selection.head());
12278
12279 let indent_and_prefix_for_row =
12280 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12281 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12282 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12283 &language_scope
12284 {
12285 let indent_end = Point::new(row, indent.len);
12286 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12287 let line_text_after_indent = buffer
12288 .text_for_range(indent_end..line_end)
12289 .collect::<String>();
12290
12291 let is_within_comment_override = buffer
12292 .language_scope_at(indent_end)
12293 .is_some_and(|scope| scope.override_name() == Some("comment"));
12294 let comment_delimiters = if is_within_comment_override {
12295 // we are within a comment syntax node, but we don't
12296 // yet know what kind of comment: block, doc or line
12297 match (
12298 language_scope.documentation_comment(),
12299 language_scope.block_comment(),
12300 ) {
12301 (Some(config), _) | (_, Some(config))
12302 if buffer.contains_str_at(indent_end, &config.start) =>
12303 {
12304 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12305 }
12306 (Some(config), _) | (_, Some(config))
12307 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12308 {
12309 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12310 }
12311 (Some(config), _) | (_, Some(config))
12312 if buffer.contains_str_at(indent_end, &config.prefix) =>
12313 {
12314 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12315 }
12316 (_, _) => language_scope
12317 .line_comment_prefixes()
12318 .iter()
12319 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12320 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12321 }
12322 } else {
12323 // we not in an overridden comment node, but we may
12324 // be within a non-overridden line comment node
12325 language_scope
12326 .line_comment_prefixes()
12327 .iter()
12328 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12329 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12330 };
12331
12332 let rewrap_prefix = language_scope
12333 .rewrap_prefixes()
12334 .iter()
12335 .find_map(|prefix_regex| {
12336 prefix_regex.find(&line_text_after_indent).map(|mat| {
12337 if mat.start() == 0 {
12338 Some(mat.as_str().to_string())
12339 } else {
12340 None
12341 }
12342 })
12343 })
12344 .flatten();
12345 (comment_delimiters, rewrap_prefix)
12346 } else {
12347 (None, None)
12348 };
12349 (indent, comment_prefix, rewrap_prefix)
12350 };
12351
12352 let mut ranges = Vec::new();
12353 let from_empty_selection = selection.is_empty();
12354
12355 let mut current_range_start = first_row;
12356 let mut prev_row = first_row;
12357 let (
12358 mut current_range_indent,
12359 mut current_range_comment_delimiters,
12360 mut current_range_rewrap_prefix,
12361 ) = indent_and_prefix_for_row(first_row);
12362
12363 for row in non_blank_rows_iter.skip(1) {
12364 let has_paragraph_break = row > prev_row + 1;
12365
12366 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12367 indent_and_prefix_for_row(row);
12368
12369 let has_indent_change = row_indent != current_range_indent;
12370 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12371
12372 let has_boundary_change = has_comment_change
12373 || row_rewrap_prefix.is_some()
12374 || (has_indent_change && current_range_comment_delimiters.is_some());
12375
12376 if has_paragraph_break || has_boundary_change {
12377 ranges.push((
12378 language_settings.clone(),
12379 Point::new(current_range_start, 0)
12380 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12381 current_range_indent,
12382 current_range_comment_delimiters.clone(),
12383 current_range_rewrap_prefix.clone(),
12384 from_empty_selection,
12385 ));
12386 current_range_start = row;
12387 current_range_indent = row_indent;
12388 current_range_comment_delimiters = row_comment_delimiters;
12389 current_range_rewrap_prefix = row_rewrap_prefix;
12390 }
12391 prev_row = row;
12392 }
12393
12394 ranges.push((
12395 language_settings.clone(),
12396 Point::new(current_range_start, 0)
12397 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12398 current_range_indent,
12399 current_range_comment_delimiters,
12400 current_range_rewrap_prefix,
12401 from_empty_selection,
12402 ));
12403
12404 ranges
12405 });
12406
12407 let mut edits = Vec::new();
12408 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12409
12410 for (
12411 language_settings,
12412 wrap_range,
12413 mut indent_size,
12414 comment_prefix,
12415 rewrap_prefix,
12416 from_empty_selection,
12417 ) in wrap_ranges
12418 {
12419 let mut start_row = wrap_range.start.row;
12420 let mut end_row = wrap_range.end.row;
12421
12422 // Skip selections that overlap with a range that has already been rewrapped.
12423 let selection_range = start_row..end_row;
12424 if rewrapped_row_ranges
12425 .iter()
12426 .any(|range| range.overlaps(&selection_range))
12427 {
12428 continue;
12429 }
12430
12431 let tab_size = language_settings.tab_size;
12432
12433 let (line_prefix, inside_comment) = match &comment_prefix {
12434 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12435 (Some(prefix.as_str()), true)
12436 }
12437 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12438 (Some(prefix.as_ref()), true)
12439 }
12440 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12441 start: _,
12442 end: _,
12443 prefix,
12444 tab_size,
12445 })) => {
12446 indent_size.len += tab_size;
12447 (Some(prefix.as_ref()), true)
12448 }
12449 None => (None, false),
12450 };
12451 let indent_prefix = indent_size.chars().collect::<String>();
12452 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12453
12454 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12455 RewrapBehavior::InComments => inside_comment,
12456 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12457 RewrapBehavior::Anywhere => true,
12458 };
12459
12460 let should_rewrap = options.override_language_settings
12461 || allow_rewrap_based_on_language
12462 || self.hard_wrap.is_some();
12463 if !should_rewrap {
12464 continue;
12465 }
12466
12467 if from_empty_selection {
12468 'expand_upwards: while start_row > 0 {
12469 let prev_row = start_row - 1;
12470 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12471 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12472 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12473 {
12474 start_row = prev_row;
12475 } else {
12476 break 'expand_upwards;
12477 }
12478 }
12479
12480 'expand_downwards: while end_row < buffer.max_point().row {
12481 let next_row = end_row + 1;
12482 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12483 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12484 && !buffer.is_line_blank(MultiBufferRow(next_row))
12485 {
12486 end_row = next_row;
12487 } else {
12488 break 'expand_downwards;
12489 }
12490 }
12491 }
12492
12493 let start = Point::new(start_row, 0);
12494 let start_offset = ToOffset::to_offset(&start, &buffer);
12495 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12496 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12497 let mut first_line_delimiter = None;
12498 let mut last_line_delimiter = None;
12499 let Some(lines_without_prefixes) = selection_text
12500 .lines()
12501 .enumerate()
12502 .map(|(ix, line)| {
12503 let line_trimmed = line.trim_start();
12504 if rewrap_prefix.is_some() && ix > 0 {
12505 Ok(line_trimmed)
12506 } else if let Some(
12507 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12508 start,
12509 prefix,
12510 end,
12511 tab_size,
12512 })
12513 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12514 start,
12515 prefix,
12516 end,
12517 tab_size,
12518 }),
12519 ) = &comment_prefix
12520 {
12521 let line_trimmed = line_trimmed
12522 .strip_prefix(start.as_ref())
12523 .map(|s| {
12524 let mut indent_size = indent_size;
12525 indent_size.len -= tab_size;
12526 let indent_prefix: String = indent_size.chars().collect();
12527 first_line_delimiter = Some((indent_prefix, start));
12528 s.trim_start()
12529 })
12530 .unwrap_or(line_trimmed);
12531 let line_trimmed = line_trimmed
12532 .strip_suffix(end.as_ref())
12533 .map(|s| {
12534 last_line_delimiter = Some(end);
12535 s.trim_end()
12536 })
12537 .unwrap_or(line_trimmed);
12538 let line_trimmed = line_trimmed
12539 .strip_prefix(prefix.as_ref())
12540 .unwrap_or(line_trimmed);
12541 Ok(line_trimmed)
12542 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12543 line_trimmed.strip_prefix(prefix).with_context(|| {
12544 format!("line did not start with prefix {prefix:?}: {line:?}")
12545 })
12546 } else {
12547 line_trimmed
12548 .strip_prefix(&line_prefix.trim_start())
12549 .with_context(|| {
12550 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12551 })
12552 }
12553 })
12554 .collect::<Result<Vec<_>, _>>()
12555 .log_err()
12556 else {
12557 continue;
12558 };
12559
12560 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12561 buffer
12562 .language_settings_at(Point::new(start_row, 0), cx)
12563 .preferred_line_length as usize
12564 });
12565
12566 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12567 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12568 } else {
12569 line_prefix.clone()
12570 };
12571
12572 let wrapped_text = {
12573 let mut wrapped_text = wrap_with_prefix(
12574 line_prefix,
12575 subsequent_lines_prefix,
12576 lines_without_prefixes.join("\n"),
12577 wrap_column,
12578 tab_size,
12579 options.preserve_existing_whitespace,
12580 );
12581
12582 if let Some((indent, delimiter)) = first_line_delimiter {
12583 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12584 }
12585 if let Some(last_line) = last_line_delimiter {
12586 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12587 }
12588
12589 wrapped_text
12590 };
12591
12592 // TODO: should always use char-based diff while still supporting cursor behavior that
12593 // matches vim.
12594 let mut diff_options = DiffOptions::default();
12595 if options.override_language_settings {
12596 diff_options.max_word_diff_len = 0;
12597 diff_options.max_word_diff_line_count = 0;
12598 } else {
12599 diff_options.max_word_diff_len = usize::MAX;
12600 diff_options.max_word_diff_line_count = usize::MAX;
12601 }
12602
12603 for (old_range, new_text) in
12604 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12605 {
12606 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12607 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12608 edits.push((edit_start..edit_end, new_text));
12609 }
12610
12611 rewrapped_row_ranges.push(start_row..=end_row);
12612 }
12613
12614 self.buffer
12615 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12616 }
12617
12618 pub fn cut_common(
12619 &mut self,
12620 cut_no_selection_line: bool,
12621 window: &mut Window,
12622 cx: &mut Context<Self>,
12623 ) -> ClipboardItem {
12624 let mut text = String::new();
12625 let buffer = self.buffer.read(cx).snapshot(cx);
12626 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12627 let mut clipboard_selections = Vec::with_capacity(selections.len());
12628 {
12629 let max_point = buffer.max_point();
12630 let mut is_first = true;
12631 let mut prev_selection_was_entire_line = false;
12632 for selection in &mut selections {
12633 let is_entire_line =
12634 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12635 if is_entire_line {
12636 selection.start = Point::new(selection.start.row, 0);
12637 if !selection.is_empty() && selection.end.column == 0 {
12638 selection.end = cmp::min(max_point, selection.end);
12639 } else {
12640 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12641 }
12642 selection.goal = SelectionGoal::None;
12643 }
12644 if is_first {
12645 is_first = false;
12646 } else if !prev_selection_was_entire_line {
12647 text += "\n";
12648 }
12649 prev_selection_was_entire_line = is_entire_line;
12650 let mut len = 0;
12651 for chunk in buffer.text_for_range(selection.start..selection.end) {
12652 text.push_str(chunk);
12653 len += chunk.len();
12654 }
12655 clipboard_selections.push(ClipboardSelection {
12656 len,
12657 is_entire_line,
12658 first_line_indent: buffer
12659 .indent_size_for_line(MultiBufferRow(selection.start.row))
12660 .len,
12661 });
12662 }
12663 }
12664
12665 self.transact(window, cx, |this, window, cx| {
12666 this.change_selections(Default::default(), window, cx, |s| {
12667 s.select(selections);
12668 });
12669 this.insert("", window, cx);
12670 });
12671 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12672 }
12673
12674 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12675 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12676 let item = self.cut_common(true, window, cx);
12677 cx.write_to_clipboard(item);
12678 }
12679
12680 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12681 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12682 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12683 s.move_with(|snapshot, sel| {
12684 if sel.is_empty() {
12685 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12686 }
12687 if sel.is_empty() {
12688 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12689 }
12690 });
12691 });
12692 let item = self.cut_common(false, window, cx);
12693 cx.set_global(KillRing(item))
12694 }
12695
12696 pub fn kill_ring_yank(
12697 &mut self,
12698 _: &KillRingYank,
12699 window: &mut Window,
12700 cx: &mut Context<Self>,
12701 ) {
12702 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12703 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12704 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12705 (kill_ring.text().to_string(), kill_ring.metadata_json())
12706 } else {
12707 return;
12708 }
12709 } else {
12710 return;
12711 };
12712 self.do_paste(&text, metadata, false, window, cx);
12713 }
12714
12715 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12716 self.do_copy(true, cx);
12717 }
12718
12719 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12720 self.do_copy(false, cx);
12721 }
12722
12723 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12724 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12725 let buffer = self.buffer.read(cx).read(cx);
12726 let mut text = String::new();
12727
12728 let mut clipboard_selections = Vec::with_capacity(selections.len());
12729 {
12730 let max_point = buffer.max_point();
12731 let mut is_first = true;
12732 let mut prev_selection_was_entire_line = false;
12733 for selection in &selections {
12734 let mut start = selection.start;
12735 let mut end = selection.end;
12736 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12737 let mut add_trailing_newline = false;
12738 if is_entire_line {
12739 start = Point::new(start.row, 0);
12740 let next_line_start = Point::new(end.row + 1, 0);
12741 if next_line_start <= max_point {
12742 end = next_line_start;
12743 } else {
12744 // We're on the last line without a trailing newline.
12745 // Copy to the end of the line and add a newline afterwards.
12746 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12747 add_trailing_newline = true;
12748 }
12749 }
12750
12751 let mut trimmed_selections = Vec::new();
12752 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12753 let row = MultiBufferRow(start.row);
12754 let first_indent = buffer.indent_size_for_line(row);
12755 if first_indent.len == 0 || start.column > first_indent.len {
12756 trimmed_selections.push(start..end);
12757 } else {
12758 trimmed_selections.push(
12759 Point::new(row.0, first_indent.len)
12760 ..Point::new(row.0, buffer.line_len(row)),
12761 );
12762 for row in start.row + 1..=end.row {
12763 let mut line_len = buffer.line_len(MultiBufferRow(row));
12764 if row == end.row {
12765 line_len = end.column;
12766 }
12767 if line_len == 0 {
12768 trimmed_selections
12769 .push(Point::new(row, 0)..Point::new(row, line_len));
12770 continue;
12771 }
12772 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12773 if row_indent_size.len >= first_indent.len {
12774 trimmed_selections.push(
12775 Point::new(row, first_indent.len)..Point::new(row, line_len),
12776 );
12777 } else {
12778 trimmed_selections.clear();
12779 trimmed_selections.push(start..end);
12780 break;
12781 }
12782 }
12783 }
12784 } else {
12785 trimmed_selections.push(start..end);
12786 }
12787
12788 for trimmed_range in trimmed_selections {
12789 if is_first {
12790 is_first = false;
12791 } else if !prev_selection_was_entire_line {
12792 text += "\n";
12793 }
12794 prev_selection_was_entire_line = is_entire_line;
12795 let mut len = 0;
12796 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12797 text.push_str(chunk);
12798 len += chunk.len();
12799 }
12800 if add_trailing_newline {
12801 text.push('\n');
12802 len += 1;
12803 }
12804 clipboard_selections.push(ClipboardSelection {
12805 len,
12806 is_entire_line,
12807 first_line_indent: buffer
12808 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12809 .len,
12810 });
12811 }
12812 }
12813 }
12814
12815 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12816 text,
12817 clipboard_selections,
12818 ));
12819 }
12820
12821 pub fn do_paste(
12822 &mut self,
12823 text: &String,
12824 clipboard_selections: Option<Vec<ClipboardSelection>>,
12825 handle_entire_lines: bool,
12826 window: &mut Window,
12827 cx: &mut Context<Self>,
12828 ) {
12829 if self.read_only(cx) {
12830 return;
12831 }
12832
12833 let clipboard_text = Cow::Borrowed(text.as_str());
12834
12835 self.transact(window, cx, |this, window, cx| {
12836 let had_active_edit_prediction = this.has_active_edit_prediction();
12837 let display_map = this.display_snapshot(cx);
12838 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
12839 let cursor_offset = this
12840 .selections
12841 .last::<MultiBufferOffset>(&display_map)
12842 .head();
12843
12844 if let Some(mut clipboard_selections) = clipboard_selections {
12845 let all_selections_were_entire_line =
12846 clipboard_selections.iter().all(|s| s.is_entire_line);
12847 let first_selection_indent_column =
12848 clipboard_selections.first().map(|s| s.first_line_indent);
12849 if clipboard_selections.len() != old_selections.len() {
12850 clipboard_selections.drain(..);
12851 }
12852 let mut auto_indent_on_paste = true;
12853
12854 this.buffer.update(cx, |buffer, cx| {
12855 let snapshot = buffer.read(cx);
12856 auto_indent_on_paste = snapshot
12857 .language_settings_at(cursor_offset, cx)
12858 .auto_indent_on_paste;
12859
12860 let mut start_offset = 0;
12861 let mut edits = Vec::new();
12862 let mut original_indent_columns = Vec::new();
12863 for (ix, selection) in old_selections.iter().enumerate() {
12864 let to_insert;
12865 let entire_line;
12866 let original_indent_column;
12867 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12868 let end_offset = start_offset + clipboard_selection.len;
12869 to_insert = &clipboard_text[start_offset..end_offset];
12870 entire_line = clipboard_selection.is_entire_line;
12871 start_offset = if entire_line {
12872 end_offset
12873 } else {
12874 end_offset + 1
12875 };
12876 original_indent_column = Some(clipboard_selection.first_line_indent);
12877 } else {
12878 to_insert = &*clipboard_text;
12879 entire_line = all_selections_were_entire_line;
12880 original_indent_column = first_selection_indent_column
12881 }
12882
12883 let (range, to_insert) =
12884 if selection.is_empty() && handle_entire_lines && entire_line {
12885 // If the corresponding selection was empty when this slice of the
12886 // clipboard text was written, then the entire line containing the
12887 // selection was copied. If this selection is also currently empty,
12888 // then paste the line before the current line of the buffer.
12889 let column = selection.start.to_point(&snapshot).column as usize;
12890 let line_start = selection.start - column;
12891 (line_start..line_start, Cow::Borrowed(to_insert))
12892 } else {
12893 let language = snapshot.language_at(selection.head());
12894 let range = selection.range();
12895 if let Some(language) = language
12896 && language.name() == "Markdown".into()
12897 {
12898 edit_for_markdown_paste(
12899 &snapshot,
12900 range,
12901 to_insert,
12902 url::Url::parse(to_insert).ok(),
12903 )
12904 } else {
12905 (range, Cow::Borrowed(to_insert))
12906 }
12907 };
12908
12909 edits.push((range, to_insert));
12910 original_indent_columns.push(original_indent_column);
12911 }
12912 drop(snapshot);
12913
12914 buffer.edit(
12915 edits,
12916 if auto_indent_on_paste {
12917 Some(AutoindentMode::Block {
12918 original_indent_columns,
12919 })
12920 } else {
12921 None
12922 },
12923 cx,
12924 );
12925 });
12926
12927 let selections = this
12928 .selections
12929 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12930 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12931 } else {
12932 let url = url::Url::parse(&clipboard_text).ok();
12933
12934 let auto_indent_mode = if !clipboard_text.is_empty() {
12935 Some(AutoindentMode::Block {
12936 original_indent_columns: Vec::new(),
12937 })
12938 } else {
12939 None
12940 };
12941
12942 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12943 let snapshot = buffer.snapshot(cx);
12944
12945 let anchors = old_selections
12946 .iter()
12947 .map(|s| {
12948 let anchor = snapshot.anchor_after(s.head());
12949 s.map(|_| anchor)
12950 })
12951 .collect::<Vec<_>>();
12952
12953 let mut edits = Vec::new();
12954
12955 for selection in old_selections.iter() {
12956 let language = snapshot.language_at(selection.head());
12957 let range = selection.range();
12958
12959 let (edit_range, edit_text) = if let Some(language) = language
12960 && language.name() == "Markdown".into()
12961 {
12962 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12963 } else {
12964 (range, clipboard_text.clone())
12965 };
12966
12967 edits.push((edit_range, edit_text));
12968 }
12969
12970 drop(snapshot);
12971 buffer.edit(edits, auto_indent_mode, cx);
12972
12973 anchors
12974 });
12975
12976 this.change_selections(Default::default(), window, cx, |s| {
12977 s.select_anchors(selection_anchors);
12978 });
12979 }
12980
12981 // 🤔 | .. | show_in_menu |
12982 // | .. | true true
12983 // | had_edit_prediction | false true
12984
12985 let trigger_in_words =
12986 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12987
12988 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12989 });
12990 }
12991
12992 pub fn diff_clipboard_with_selection(
12993 &mut self,
12994 _: &DiffClipboardWithSelection,
12995 window: &mut Window,
12996 cx: &mut Context<Self>,
12997 ) {
12998 let selections = self
12999 .selections
13000 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13001
13002 if selections.is_empty() {
13003 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13004 return;
13005 };
13006
13007 let clipboard_text = match cx.read_from_clipboard() {
13008 Some(item) => match item.entries().first() {
13009 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13010 _ => None,
13011 },
13012 None => None,
13013 };
13014
13015 let Some(clipboard_text) = clipboard_text else {
13016 log::warn!("Clipboard doesn't contain text.");
13017 return;
13018 };
13019
13020 window.dispatch_action(
13021 Box::new(DiffClipboardWithSelectionData {
13022 clipboard_text,
13023 editor: cx.entity(),
13024 }),
13025 cx,
13026 );
13027 }
13028
13029 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13030 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13031 if let Some(item) = cx.read_from_clipboard() {
13032 let entries = item.entries();
13033
13034 match entries.first() {
13035 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13036 // of all the pasted entries.
13037 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13038 .do_paste(
13039 clipboard_string.text(),
13040 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13041 true,
13042 window,
13043 cx,
13044 ),
13045 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13046 }
13047 }
13048 }
13049
13050 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13051 if self.read_only(cx) {
13052 return;
13053 }
13054
13055 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13056
13057 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13058 if let Some((selections, _)) =
13059 self.selection_history.transaction(transaction_id).cloned()
13060 {
13061 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13062 s.select_anchors(selections.to_vec());
13063 });
13064 } else {
13065 log::error!(
13066 "No entry in selection_history found for undo. \
13067 This may correspond to a bug where undo does not update the selection. \
13068 If this is occurring, please add details to \
13069 https://github.com/zed-industries/zed/issues/22692"
13070 );
13071 }
13072 self.request_autoscroll(Autoscroll::fit(), cx);
13073 self.unmark_text(window, cx);
13074 self.refresh_edit_prediction(true, false, window, cx);
13075 cx.emit(EditorEvent::Edited { transaction_id });
13076 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13077 }
13078 }
13079
13080 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13081 if self.read_only(cx) {
13082 return;
13083 }
13084
13085 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13086
13087 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13088 if let Some((_, Some(selections))) =
13089 self.selection_history.transaction(transaction_id).cloned()
13090 {
13091 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13092 s.select_anchors(selections.to_vec());
13093 });
13094 } else {
13095 log::error!(
13096 "No entry in selection_history found for redo. \
13097 This may correspond to a bug where undo does not update the selection. \
13098 If this is occurring, please add details to \
13099 https://github.com/zed-industries/zed/issues/22692"
13100 );
13101 }
13102 self.request_autoscroll(Autoscroll::fit(), cx);
13103 self.unmark_text(window, cx);
13104 self.refresh_edit_prediction(true, false, window, cx);
13105 cx.emit(EditorEvent::Edited { transaction_id });
13106 }
13107 }
13108
13109 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13110 self.buffer
13111 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13112 }
13113
13114 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13115 self.buffer
13116 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13117 }
13118
13119 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13120 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13121 self.change_selections(Default::default(), window, cx, |s| {
13122 s.move_with(|map, selection| {
13123 let cursor = if selection.is_empty() {
13124 movement::left(map, selection.start)
13125 } else {
13126 selection.start
13127 };
13128 selection.collapse_to(cursor, SelectionGoal::None);
13129 });
13130 })
13131 }
13132
13133 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13134 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13135 self.change_selections(Default::default(), window, cx, |s| {
13136 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13137 })
13138 }
13139
13140 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13141 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13142 self.change_selections(Default::default(), window, cx, |s| {
13143 s.move_with(|map, selection| {
13144 let cursor = if selection.is_empty() {
13145 movement::right(map, selection.end)
13146 } else {
13147 selection.end
13148 };
13149 selection.collapse_to(cursor, SelectionGoal::None)
13150 });
13151 })
13152 }
13153
13154 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13155 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13156 self.change_selections(Default::default(), window, cx, |s| {
13157 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13158 });
13159 }
13160
13161 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13162 if self.take_rename(true, window, cx).is_some() {
13163 return;
13164 }
13165
13166 if self.mode.is_single_line() {
13167 cx.propagate();
13168 return;
13169 }
13170
13171 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13172
13173 let text_layout_details = &self.text_layout_details(window);
13174 let selection_count = self.selections.count();
13175 let first_selection = self.selections.first_anchor();
13176
13177 self.change_selections(Default::default(), window, cx, |s| {
13178 s.move_with(|map, selection| {
13179 if !selection.is_empty() {
13180 selection.goal = SelectionGoal::None;
13181 }
13182 let (cursor, goal) = movement::up(
13183 map,
13184 selection.start,
13185 selection.goal,
13186 false,
13187 text_layout_details,
13188 );
13189 selection.collapse_to(cursor, goal);
13190 });
13191 });
13192
13193 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13194 {
13195 cx.propagate();
13196 }
13197 }
13198
13199 pub fn move_up_by_lines(
13200 &mut self,
13201 action: &MoveUpByLines,
13202 window: &mut Window,
13203 cx: &mut Context<Self>,
13204 ) {
13205 if self.take_rename(true, window, cx).is_some() {
13206 return;
13207 }
13208
13209 if self.mode.is_single_line() {
13210 cx.propagate();
13211 return;
13212 }
13213
13214 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13215
13216 let text_layout_details = &self.text_layout_details(window);
13217
13218 self.change_selections(Default::default(), window, cx, |s| {
13219 s.move_with(|map, selection| {
13220 if !selection.is_empty() {
13221 selection.goal = SelectionGoal::None;
13222 }
13223 let (cursor, goal) = movement::up_by_rows(
13224 map,
13225 selection.start,
13226 action.lines,
13227 selection.goal,
13228 false,
13229 text_layout_details,
13230 );
13231 selection.collapse_to(cursor, goal);
13232 });
13233 })
13234 }
13235
13236 pub fn move_down_by_lines(
13237 &mut self,
13238 action: &MoveDownByLines,
13239 window: &mut Window,
13240 cx: &mut Context<Self>,
13241 ) {
13242 if self.take_rename(true, window, cx).is_some() {
13243 return;
13244 }
13245
13246 if self.mode.is_single_line() {
13247 cx.propagate();
13248 return;
13249 }
13250
13251 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13252
13253 let text_layout_details = &self.text_layout_details(window);
13254
13255 self.change_selections(Default::default(), window, cx, |s| {
13256 s.move_with(|map, selection| {
13257 if !selection.is_empty() {
13258 selection.goal = SelectionGoal::None;
13259 }
13260 let (cursor, goal) = movement::down_by_rows(
13261 map,
13262 selection.start,
13263 action.lines,
13264 selection.goal,
13265 false,
13266 text_layout_details,
13267 );
13268 selection.collapse_to(cursor, goal);
13269 });
13270 })
13271 }
13272
13273 pub fn select_down_by_lines(
13274 &mut self,
13275 action: &SelectDownByLines,
13276 window: &mut Window,
13277 cx: &mut Context<Self>,
13278 ) {
13279 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13280 let text_layout_details = &self.text_layout_details(window);
13281 self.change_selections(Default::default(), window, cx, |s| {
13282 s.move_heads_with(|map, head, goal| {
13283 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13284 })
13285 })
13286 }
13287
13288 pub fn select_up_by_lines(
13289 &mut self,
13290 action: &SelectUpByLines,
13291 window: &mut Window,
13292 cx: &mut Context<Self>,
13293 ) {
13294 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13295 let text_layout_details = &self.text_layout_details(window);
13296 self.change_selections(Default::default(), window, cx, |s| {
13297 s.move_heads_with(|map, head, goal| {
13298 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13299 })
13300 })
13301 }
13302
13303 pub fn select_page_up(
13304 &mut self,
13305 _: &SelectPageUp,
13306 window: &mut Window,
13307 cx: &mut Context<Self>,
13308 ) {
13309 let Some(row_count) = self.visible_row_count() else {
13310 return;
13311 };
13312
13313 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13314
13315 let text_layout_details = &self.text_layout_details(window);
13316
13317 self.change_selections(Default::default(), window, cx, |s| {
13318 s.move_heads_with(|map, head, goal| {
13319 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13320 })
13321 })
13322 }
13323
13324 pub fn move_page_up(
13325 &mut self,
13326 action: &MovePageUp,
13327 window: &mut Window,
13328 cx: &mut Context<Self>,
13329 ) {
13330 if self.take_rename(true, window, cx).is_some() {
13331 return;
13332 }
13333
13334 if self
13335 .context_menu
13336 .borrow_mut()
13337 .as_mut()
13338 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13339 .unwrap_or(false)
13340 {
13341 return;
13342 }
13343
13344 if matches!(self.mode, EditorMode::SingleLine) {
13345 cx.propagate();
13346 return;
13347 }
13348
13349 let Some(row_count) = self.visible_row_count() else {
13350 return;
13351 };
13352
13353 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13354
13355 let effects = if action.center_cursor {
13356 SelectionEffects::scroll(Autoscroll::center())
13357 } else {
13358 SelectionEffects::default()
13359 };
13360
13361 let text_layout_details = &self.text_layout_details(window);
13362
13363 self.change_selections(effects, window, cx, |s| {
13364 s.move_with(|map, selection| {
13365 if !selection.is_empty() {
13366 selection.goal = SelectionGoal::None;
13367 }
13368 let (cursor, goal) = movement::up_by_rows(
13369 map,
13370 selection.end,
13371 row_count,
13372 selection.goal,
13373 false,
13374 text_layout_details,
13375 );
13376 selection.collapse_to(cursor, goal);
13377 });
13378 });
13379 }
13380
13381 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13382 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13383 let text_layout_details = &self.text_layout_details(window);
13384 self.change_selections(Default::default(), window, cx, |s| {
13385 s.move_heads_with(|map, head, goal| {
13386 movement::up(map, head, goal, false, text_layout_details)
13387 })
13388 })
13389 }
13390
13391 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13392 self.take_rename(true, window, cx);
13393
13394 if self.mode.is_single_line() {
13395 cx.propagate();
13396 return;
13397 }
13398
13399 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13400
13401 let text_layout_details = &self.text_layout_details(window);
13402 let selection_count = self.selections.count();
13403 let first_selection = self.selections.first_anchor();
13404
13405 self.change_selections(Default::default(), window, cx, |s| {
13406 s.move_with(|map, selection| {
13407 if !selection.is_empty() {
13408 selection.goal = SelectionGoal::None;
13409 }
13410 let (cursor, goal) = movement::down(
13411 map,
13412 selection.end,
13413 selection.goal,
13414 false,
13415 text_layout_details,
13416 );
13417 selection.collapse_to(cursor, goal);
13418 });
13419 });
13420
13421 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13422 {
13423 cx.propagate();
13424 }
13425 }
13426
13427 pub fn select_page_down(
13428 &mut self,
13429 _: &SelectPageDown,
13430 window: &mut Window,
13431 cx: &mut Context<Self>,
13432 ) {
13433 let Some(row_count) = self.visible_row_count() else {
13434 return;
13435 };
13436
13437 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13438
13439 let text_layout_details = &self.text_layout_details(window);
13440
13441 self.change_selections(Default::default(), window, cx, |s| {
13442 s.move_heads_with(|map, head, goal| {
13443 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13444 })
13445 })
13446 }
13447
13448 pub fn move_page_down(
13449 &mut self,
13450 action: &MovePageDown,
13451 window: &mut Window,
13452 cx: &mut Context<Self>,
13453 ) {
13454 if self.take_rename(true, window, cx).is_some() {
13455 return;
13456 }
13457
13458 if self
13459 .context_menu
13460 .borrow_mut()
13461 .as_mut()
13462 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13463 .unwrap_or(false)
13464 {
13465 return;
13466 }
13467
13468 if matches!(self.mode, EditorMode::SingleLine) {
13469 cx.propagate();
13470 return;
13471 }
13472
13473 let Some(row_count) = self.visible_row_count() else {
13474 return;
13475 };
13476
13477 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13478
13479 let effects = if action.center_cursor {
13480 SelectionEffects::scroll(Autoscroll::center())
13481 } else {
13482 SelectionEffects::default()
13483 };
13484
13485 let text_layout_details = &self.text_layout_details(window);
13486 self.change_selections(effects, window, cx, |s| {
13487 s.move_with(|map, selection| {
13488 if !selection.is_empty() {
13489 selection.goal = SelectionGoal::None;
13490 }
13491 let (cursor, goal) = movement::down_by_rows(
13492 map,
13493 selection.end,
13494 row_count,
13495 selection.goal,
13496 false,
13497 text_layout_details,
13498 );
13499 selection.collapse_to(cursor, goal);
13500 });
13501 });
13502 }
13503
13504 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13505 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13506 let text_layout_details = &self.text_layout_details(window);
13507 self.change_selections(Default::default(), window, cx, |s| {
13508 s.move_heads_with(|map, head, goal| {
13509 movement::down(map, head, goal, false, text_layout_details)
13510 })
13511 });
13512 }
13513
13514 pub fn context_menu_first(
13515 &mut self,
13516 _: &ContextMenuFirst,
13517 window: &mut Window,
13518 cx: &mut Context<Self>,
13519 ) {
13520 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13521 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13522 }
13523 }
13524
13525 pub fn context_menu_prev(
13526 &mut self,
13527 _: &ContextMenuPrevious,
13528 window: &mut Window,
13529 cx: &mut Context<Self>,
13530 ) {
13531 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13532 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13533 }
13534 }
13535
13536 pub fn context_menu_next(
13537 &mut self,
13538 _: &ContextMenuNext,
13539 window: &mut Window,
13540 cx: &mut Context<Self>,
13541 ) {
13542 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13543 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13544 }
13545 }
13546
13547 pub fn context_menu_last(
13548 &mut self,
13549 _: &ContextMenuLast,
13550 window: &mut Window,
13551 cx: &mut Context<Self>,
13552 ) {
13553 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13554 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13555 }
13556 }
13557
13558 pub fn signature_help_prev(
13559 &mut self,
13560 _: &SignatureHelpPrevious,
13561 _: &mut Window,
13562 cx: &mut Context<Self>,
13563 ) {
13564 if let Some(popover) = self.signature_help_state.popover_mut() {
13565 if popover.current_signature == 0 {
13566 popover.current_signature = popover.signatures.len() - 1;
13567 } else {
13568 popover.current_signature -= 1;
13569 }
13570 cx.notify();
13571 }
13572 }
13573
13574 pub fn signature_help_next(
13575 &mut self,
13576 _: &SignatureHelpNext,
13577 _: &mut Window,
13578 cx: &mut Context<Self>,
13579 ) {
13580 if let Some(popover) = self.signature_help_state.popover_mut() {
13581 if popover.current_signature + 1 == popover.signatures.len() {
13582 popover.current_signature = 0;
13583 } else {
13584 popover.current_signature += 1;
13585 }
13586 cx.notify();
13587 }
13588 }
13589
13590 pub fn move_to_previous_word_start(
13591 &mut self,
13592 _: &MoveToPreviousWordStart,
13593 window: &mut Window,
13594 cx: &mut Context<Self>,
13595 ) {
13596 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13597 self.change_selections(Default::default(), window, cx, |s| {
13598 s.move_cursors_with(|map, head, _| {
13599 (
13600 movement::previous_word_start(map, head),
13601 SelectionGoal::None,
13602 )
13603 });
13604 })
13605 }
13606
13607 pub fn move_to_previous_subword_start(
13608 &mut self,
13609 _: &MoveToPreviousSubwordStart,
13610 window: &mut Window,
13611 cx: &mut Context<Self>,
13612 ) {
13613 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13614 self.change_selections(Default::default(), window, cx, |s| {
13615 s.move_cursors_with(|map, head, _| {
13616 (
13617 movement::previous_subword_start(map, head),
13618 SelectionGoal::None,
13619 )
13620 });
13621 })
13622 }
13623
13624 pub fn select_to_previous_word_start(
13625 &mut self,
13626 _: &SelectToPreviousWordStart,
13627 window: &mut Window,
13628 cx: &mut Context<Self>,
13629 ) {
13630 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13631 self.change_selections(Default::default(), window, cx, |s| {
13632 s.move_heads_with(|map, head, _| {
13633 (
13634 movement::previous_word_start(map, head),
13635 SelectionGoal::None,
13636 )
13637 });
13638 })
13639 }
13640
13641 pub fn select_to_previous_subword_start(
13642 &mut self,
13643 _: &SelectToPreviousSubwordStart,
13644 window: &mut Window,
13645 cx: &mut Context<Self>,
13646 ) {
13647 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13648 self.change_selections(Default::default(), window, cx, |s| {
13649 s.move_heads_with(|map, head, _| {
13650 (
13651 movement::previous_subword_start(map, head),
13652 SelectionGoal::None,
13653 )
13654 });
13655 })
13656 }
13657
13658 pub fn delete_to_previous_word_start(
13659 &mut self,
13660 action: &DeleteToPreviousWordStart,
13661 window: &mut Window,
13662 cx: &mut Context<Self>,
13663 ) {
13664 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13665 self.transact(window, cx, |this, window, cx| {
13666 this.select_autoclose_pair(window, cx);
13667 this.change_selections(Default::default(), window, cx, |s| {
13668 s.move_with(|map, selection| {
13669 if selection.is_empty() {
13670 let mut cursor = if action.ignore_newlines {
13671 movement::previous_word_start(map, selection.head())
13672 } else {
13673 movement::previous_word_start_or_newline(map, selection.head())
13674 };
13675 cursor = movement::adjust_greedy_deletion(
13676 map,
13677 selection.head(),
13678 cursor,
13679 action.ignore_brackets,
13680 );
13681 selection.set_head(cursor, SelectionGoal::None);
13682 }
13683 });
13684 });
13685 this.insert("", window, cx);
13686 });
13687 }
13688
13689 pub fn delete_to_previous_subword_start(
13690 &mut self,
13691 _: &DeleteToPreviousSubwordStart,
13692 window: &mut Window,
13693 cx: &mut Context<Self>,
13694 ) {
13695 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13696 self.transact(window, cx, |this, window, cx| {
13697 this.select_autoclose_pair(window, cx);
13698 this.change_selections(Default::default(), window, cx, |s| {
13699 s.move_with(|map, selection| {
13700 if selection.is_empty() {
13701 let mut cursor = movement::previous_subword_start(map, selection.head());
13702 cursor =
13703 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13704 selection.set_head(cursor, SelectionGoal::None);
13705 }
13706 });
13707 });
13708 this.insert("", window, cx);
13709 });
13710 }
13711
13712 pub fn move_to_next_word_end(
13713 &mut self,
13714 _: &MoveToNextWordEnd,
13715 window: &mut Window,
13716 cx: &mut Context<Self>,
13717 ) {
13718 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13719 self.change_selections(Default::default(), window, cx, |s| {
13720 s.move_cursors_with(|map, head, _| {
13721 (movement::next_word_end(map, head), SelectionGoal::None)
13722 });
13723 })
13724 }
13725
13726 pub fn move_to_next_subword_end(
13727 &mut self,
13728 _: &MoveToNextSubwordEnd,
13729 window: &mut Window,
13730 cx: &mut Context<Self>,
13731 ) {
13732 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13733 self.change_selections(Default::default(), window, cx, |s| {
13734 s.move_cursors_with(|map, head, _| {
13735 (movement::next_subword_end(map, head), SelectionGoal::None)
13736 });
13737 })
13738 }
13739
13740 pub fn select_to_next_word_end(
13741 &mut self,
13742 _: &SelectToNextWordEnd,
13743 window: &mut Window,
13744 cx: &mut Context<Self>,
13745 ) {
13746 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13747 self.change_selections(Default::default(), window, cx, |s| {
13748 s.move_heads_with(|map, head, _| {
13749 (movement::next_word_end(map, head), SelectionGoal::None)
13750 });
13751 })
13752 }
13753
13754 pub fn select_to_next_subword_end(
13755 &mut self,
13756 _: &SelectToNextSubwordEnd,
13757 window: &mut Window,
13758 cx: &mut Context<Self>,
13759 ) {
13760 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13761 self.change_selections(Default::default(), window, cx, |s| {
13762 s.move_heads_with(|map, head, _| {
13763 (movement::next_subword_end(map, head), SelectionGoal::None)
13764 });
13765 })
13766 }
13767
13768 pub fn delete_to_next_word_end(
13769 &mut self,
13770 action: &DeleteToNextWordEnd,
13771 window: &mut Window,
13772 cx: &mut Context<Self>,
13773 ) {
13774 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13775 self.transact(window, cx, |this, window, cx| {
13776 this.change_selections(Default::default(), window, cx, |s| {
13777 s.move_with(|map, selection| {
13778 if selection.is_empty() {
13779 let mut cursor = if action.ignore_newlines {
13780 movement::next_word_end(map, selection.head())
13781 } else {
13782 movement::next_word_end_or_newline(map, selection.head())
13783 };
13784 cursor = movement::adjust_greedy_deletion(
13785 map,
13786 selection.head(),
13787 cursor,
13788 action.ignore_brackets,
13789 );
13790 selection.set_head(cursor, SelectionGoal::None);
13791 }
13792 });
13793 });
13794 this.insert("", window, cx);
13795 });
13796 }
13797
13798 pub fn delete_to_next_subword_end(
13799 &mut self,
13800 _: &DeleteToNextSubwordEnd,
13801 window: &mut Window,
13802 cx: &mut Context<Self>,
13803 ) {
13804 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13805 self.transact(window, cx, |this, window, cx| {
13806 this.change_selections(Default::default(), window, cx, |s| {
13807 s.move_with(|map, selection| {
13808 if selection.is_empty() {
13809 let mut cursor = movement::next_subword_end(map, selection.head());
13810 cursor =
13811 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13812 selection.set_head(cursor, SelectionGoal::None);
13813 }
13814 });
13815 });
13816 this.insert("", window, cx);
13817 });
13818 }
13819
13820 pub fn move_to_beginning_of_line(
13821 &mut self,
13822 action: &MoveToBeginningOfLine,
13823 window: &mut Window,
13824 cx: &mut Context<Self>,
13825 ) {
13826 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13827 self.change_selections(Default::default(), window, cx, |s| {
13828 s.move_cursors_with(|map, head, _| {
13829 (
13830 movement::indented_line_beginning(
13831 map,
13832 head,
13833 action.stop_at_soft_wraps,
13834 action.stop_at_indent,
13835 ),
13836 SelectionGoal::None,
13837 )
13838 });
13839 })
13840 }
13841
13842 pub fn select_to_beginning_of_line(
13843 &mut self,
13844 action: &SelectToBeginningOfLine,
13845 window: &mut Window,
13846 cx: &mut Context<Self>,
13847 ) {
13848 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13849 self.change_selections(Default::default(), window, cx, |s| {
13850 s.move_heads_with(|map, head, _| {
13851 (
13852 movement::indented_line_beginning(
13853 map,
13854 head,
13855 action.stop_at_soft_wraps,
13856 action.stop_at_indent,
13857 ),
13858 SelectionGoal::None,
13859 )
13860 });
13861 });
13862 }
13863
13864 pub fn delete_to_beginning_of_line(
13865 &mut self,
13866 action: &DeleteToBeginningOfLine,
13867 window: &mut Window,
13868 cx: &mut Context<Self>,
13869 ) {
13870 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13871 self.transact(window, cx, |this, window, cx| {
13872 this.change_selections(Default::default(), window, cx, |s| {
13873 s.move_with(|_, selection| {
13874 selection.reversed = true;
13875 });
13876 });
13877
13878 this.select_to_beginning_of_line(
13879 &SelectToBeginningOfLine {
13880 stop_at_soft_wraps: false,
13881 stop_at_indent: action.stop_at_indent,
13882 },
13883 window,
13884 cx,
13885 );
13886 this.backspace(&Backspace, window, cx);
13887 });
13888 }
13889
13890 pub fn move_to_end_of_line(
13891 &mut self,
13892 action: &MoveToEndOfLine,
13893 window: &mut Window,
13894 cx: &mut Context<Self>,
13895 ) {
13896 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13897 self.change_selections(Default::default(), window, cx, |s| {
13898 s.move_cursors_with(|map, head, _| {
13899 (
13900 movement::line_end(map, head, action.stop_at_soft_wraps),
13901 SelectionGoal::None,
13902 )
13903 });
13904 })
13905 }
13906
13907 pub fn select_to_end_of_line(
13908 &mut self,
13909 action: &SelectToEndOfLine,
13910 window: &mut Window,
13911 cx: &mut Context<Self>,
13912 ) {
13913 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13914 self.change_selections(Default::default(), window, cx, |s| {
13915 s.move_heads_with(|map, head, _| {
13916 (
13917 movement::line_end(map, head, action.stop_at_soft_wraps),
13918 SelectionGoal::None,
13919 )
13920 });
13921 })
13922 }
13923
13924 pub fn delete_to_end_of_line(
13925 &mut self,
13926 _: &DeleteToEndOfLine,
13927 window: &mut Window,
13928 cx: &mut Context<Self>,
13929 ) {
13930 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13931 self.transact(window, cx, |this, window, cx| {
13932 this.select_to_end_of_line(
13933 &SelectToEndOfLine {
13934 stop_at_soft_wraps: false,
13935 },
13936 window,
13937 cx,
13938 );
13939 this.delete(&Delete, window, cx);
13940 });
13941 }
13942
13943 pub fn cut_to_end_of_line(
13944 &mut self,
13945 action: &CutToEndOfLine,
13946 window: &mut Window,
13947 cx: &mut Context<Self>,
13948 ) {
13949 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13950 self.transact(window, cx, |this, window, cx| {
13951 this.select_to_end_of_line(
13952 &SelectToEndOfLine {
13953 stop_at_soft_wraps: false,
13954 },
13955 window,
13956 cx,
13957 );
13958 if !action.stop_at_newlines {
13959 this.change_selections(Default::default(), window, cx, |s| {
13960 s.move_with(|_, sel| {
13961 if sel.is_empty() {
13962 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13963 }
13964 });
13965 });
13966 }
13967 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13968 let item = this.cut_common(false, window, cx);
13969 cx.write_to_clipboard(item);
13970 });
13971 }
13972
13973 pub fn move_to_start_of_paragraph(
13974 &mut self,
13975 _: &MoveToStartOfParagraph,
13976 window: &mut Window,
13977 cx: &mut Context<Self>,
13978 ) {
13979 if matches!(self.mode, EditorMode::SingleLine) {
13980 cx.propagate();
13981 return;
13982 }
13983 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13984 self.change_selections(Default::default(), window, cx, |s| {
13985 s.move_with(|map, selection| {
13986 selection.collapse_to(
13987 movement::start_of_paragraph(map, selection.head(), 1),
13988 SelectionGoal::None,
13989 )
13990 });
13991 })
13992 }
13993
13994 pub fn move_to_end_of_paragraph(
13995 &mut self,
13996 _: &MoveToEndOfParagraph,
13997 window: &mut Window,
13998 cx: &mut Context<Self>,
13999 ) {
14000 if matches!(self.mode, EditorMode::SingleLine) {
14001 cx.propagate();
14002 return;
14003 }
14004 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14005 self.change_selections(Default::default(), window, cx, |s| {
14006 s.move_with(|map, selection| {
14007 selection.collapse_to(
14008 movement::end_of_paragraph(map, selection.head(), 1),
14009 SelectionGoal::None,
14010 )
14011 });
14012 })
14013 }
14014
14015 pub fn select_to_start_of_paragraph(
14016 &mut self,
14017 _: &SelectToStartOfParagraph,
14018 window: &mut Window,
14019 cx: &mut Context<Self>,
14020 ) {
14021 if matches!(self.mode, EditorMode::SingleLine) {
14022 cx.propagate();
14023 return;
14024 }
14025 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14026 self.change_selections(Default::default(), window, cx, |s| {
14027 s.move_heads_with(|map, head, _| {
14028 (
14029 movement::start_of_paragraph(map, head, 1),
14030 SelectionGoal::None,
14031 )
14032 });
14033 })
14034 }
14035
14036 pub fn select_to_end_of_paragraph(
14037 &mut self,
14038 _: &SelectToEndOfParagraph,
14039 window: &mut Window,
14040 cx: &mut Context<Self>,
14041 ) {
14042 if matches!(self.mode, EditorMode::SingleLine) {
14043 cx.propagate();
14044 return;
14045 }
14046 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14047 self.change_selections(Default::default(), window, cx, |s| {
14048 s.move_heads_with(|map, head, _| {
14049 (
14050 movement::end_of_paragraph(map, head, 1),
14051 SelectionGoal::None,
14052 )
14053 });
14054 })
14055 }
14056
14057 pub fn move_to_start_of_excerpt(
14058 &mut self,
14059 _: &MoveToStartOfExcerpt,
14060 window: &mut Window,
14061 cx: &mut Context<Self>,
14062 ) {
14063 if matches!(self.mode, EditorMode::SingleLine) {
14064 cx.propagate();
14065 return;
14066 }
14067 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14068 self.change_selections(Default::default(), window, cx, |s| {
14069 s.move_with(|map, selection| {
14070 selection.collapse_to(
14071 movement::start_of_excerpt(
14072 map,
14073 selection.head(),
14074 workspace::searchable::Direction::Prev,
14075 ),
14076 SelectionGoal::None,
14077 )
14078 });
14079 })
14080 }
14081
14082 pub fn move_to_start_of_next_excerpt(
14083 &mut self,
14084 _: &MoveToStartOfNextExcerpt,
14085 window: &mut Window,
14086 cx: &mut Context<Self>,
14087 ) {
14088 if matches!(self.mode, EditorMode::SingleLine) {
14089 cx.propagate();
14090 return;
14091 }
14092
14093 self.change_selections(Default::default(), window, cx, |s| {
14094 s.move_with(|map, selection| {
14095 selection.collapse_to(
14096 movement::start_of_excerpt(
14097 map,
14098 selection.head(),
14099 workspace::searchable::Direction::Next,
14100 ),
14101 SelectionGoal::None,
14102 )
14103 });
14104 })
14105 }
14106
14107 pub fn move_to_end_of_excerpt(
14108 &mut self,
14109 _: &MoveToEndOfExcerpt,
14110 window: &mut Window,
14111 cx: &mut Context<Self>,
14112 ) {
14113 if matches!(self.mode, EditorMode::SingleLine) {
14114 cx.propagate();
14115 return;
14116 }
14117 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14118 self.change_selections(Default::default(), window, cx, |s| {
14119 s.move_with(|map, selection| {
14120 selection.collapse_to(
14121 movement::end_of_excerpt(
14122 map,
14123 selection.head(),
14124 workspace::searchable::Direction::Next,
14125 ),
14126 SelectionGoal::None,
14127 )
14128 });
14129 })
14130 }
14131
14132 pub fn move_to_end_of_previous_excerpt(
14133 &mut self,
14134 _: &MoveToEndOfPreviousExcerpt,
14135 window: &mut Window,
14136 cx: &mut Context<Self>,
14137 ) {
14138 if matches!(self.mode, EditorMode::SingleLine) {
14139 cx.propagate();
14140 return;
14141 }
14142 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14143 self.change_selections(Default::default(), window, cx, |s| {
14144 s.move_with(|map, selection| {
14145 selection.collapse_to(
14146 movement::end_of_excerpt(
14147 map,
14148 selection.head(),
14149 workspace::searchable::Direction::Prev,
14150 ),
14151 SelectionGoal::None,
14152 )
14153 });
14154 })
14155 }
14156
14157 pub fn select_to_start_of_excerpt(
14158 &mut self,
14159 _: &SelectToStartOfExcerpt,
14160 window: &mut Window,
14161 cx: &mut Context<Self>,
14162 ) {
14163 if matches!(self.mode, EditorMode::SingleLine) {
14164 cx.propagate();
14165 return;
14166 }
14167 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14168 self.change_selections(Default::default(), window, cx, |s| {
14169 s.move_heads_with(|map, head, _| {
14170 (
14171 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14172 SelectionGoal::None,
14173 )
14174 });
14175 })
14176 }
14177
14178 pub fn select_to_start_of_next_excerpt(
14179 &mut self,
14180 _: &SelectToStartOfNextExcerpt,
14181 window: &mut Window,
14182 cx: &mut Context<Self>,
14183 ) {
14184 if matches!(self.mode, EditorMode::SingleLine) {
14185 cx.propagate();
14186 return;
14187 }
14188 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14189 self.change_selections(Default::default(), window, cx, |s| {
14190 s.move_heads_with(|map, head, _| {
14191 (
14192 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14193 SelectionGoal::None,
14194 )
14195 });
14196 })
14197 }
14198
14199 pub fn select_to_end_of_excerpt(
14200 &mut self,
14201 _: &SelectToEndOfExcerpt,
14202 window: &mut Window,
14203 cx: &mut Context<Self>,
14204 ) {
14205 if matches!(self.mode, EditorMode::SingleLine) {
14206 cx.propagate();
14207 return;
14208 }
14209 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14210 self.change_selections(Default::default(), window, cx, |s| {
14211 s.move_heads_with(|map, head, _| {
14212 (
14213 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14214 SelectionGoal::None,
14215 )
14216 });
14217 })
14218 }
14219
14220 pub fn select_to_end_of_previous_excerpt(
14221 &mut self,
14222 _: &SelectToEndOfPreviousExcerpt,
14223 window: &mut Window,
14224 cx: &mut Context<Self>,
14225 ) {
14226 if matches!(self.mode, EditorMode::SingleLine) {
14227 cx.propagate();
14228 return;
14229 }
14230 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14231 self.change_selections(Default::default(), window, cx, |s| {
14232 s.move_heads_with(|map, head, _| {
14233 (
14234 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14235 SelectionGoal::None,
14236 )
14237 });
14238 })
14239 }
14240
14241 pub fn move_to_beginning(
14242 &mut self,
14243 _: &MoveToBeginning,
14244 window: &mut Window,
14245 cx: &mut Context<Self>,
14246 ) {
14247 if matches!(self.mode, EditorMode::SingleLine) {
14248 cx.propagate();
14249 return;
14250 }
14251 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14252 self.change_selections(Default::default(), window, cx, |s| {
14253 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14254 });
14255 }
14256
14257 pub fn select_to_beginning(
14258 &mut self,
14259 _: &SelectToBeginning,
14260 window: &mut Window,
14261 cx: &mut Context<Self>,
14262 ) {
14263 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14264 selection.set_head(Point::zero(), SelectionGoal::None);
14265 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14266 self.change_selections(Default::default(), window, cx, |s| {
14267 s.select(vec![selection]);
14268 });
14269 }
14270
14271 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14272 if matches!(self.mode, EditorMode::SingleLine) {
14273 cx.propagate();
14274 return;
14275 }
14276 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14277 let cursor = self.buffer.read(cx).read(cx).len();
14278 self.change_selections(Default::default(), window, cx, |s| {
14279 s.select_ranges(vec![cursor..cursor])
14280 });
14281 }
14282
14283 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14284 self.nav_history = nav_history;
14285 }
14286
14287 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14288 self.nav_history.as_ref()
14289 }
14290
14291 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14292 self.push_to_nav_history(
14293 self.selections.newest_anchor().head(),
14294 None,
14295 false,
14296 true,
14297 cx,
14298 );
14299 }
14300
14301 fn push_to_nav_history(
14302 &mut self,
14303 cursor_anchor: Anchor,
14304 new_position: Option<Point>,
14305 is_deactivate: bool,
14306 always: bool,
14307 cx: &mut Context<Self>,
14308 ) {
14309 if let Some(nav_history) = self.nav_history.as_mut() {
14310 let buffer = self.buffer.read(cx).read(cx);
14311 let cursor_position = cursor_anchor.to_point(&buffer);
14312 let scroll_state = self.scroll_manager.anchor();
14313 let scroll_top_row = scroll_state.top_row(&buffer);
14314 drop(buffer);
14315
14316 if let Some(new_position) = new_position {
14317 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14318 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14319 return;
14320 }
14321 }
14322
14323 nav_history.push(
14324 Some(NavigationData {
14325 cursor_anchor,
14326 cursor_position,
14327 scroll_anchor: scroll_state,
14328 scroll_top_row,
14329 }),
14330 cx,
14331 );
14332 cx.emit(EditorEvent::PushedToNavHistory {
14333 anchor: cursor_anchor,
14334 is_deactivate,
14335 })
14336 }
14337 }
14338
14339 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14340 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14341 let buffer = self.buffer.read(cx).snapshot(cx);
14342 let mut selection = self
14343 .selections
14344 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14345 selection.set_head(buffer.len(), SelectionGoal::None);
14346 self.change_selections(Default::default(), window, cx, |s| {
14347 s.select(vec![selection]);
14348 });
14349 }
14350
14351 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14352 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14353 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14354 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14355 });
14356 }
14357
14358 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14359 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14360 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14361 let mut selections = self.selections.all::<Point>(&display_map);
14362 let max_point = display_map.buffer_snapshot().max_point();
14363 for selection in &mut selections {
14364 let rows = selection.spanned_rows(true, &display_map);
14365 selection.start = Point::new(rows.start.0, 0);
14366 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14367 selection.reversed = false;
14368 }
14369 self.change_selections(Default::default(), window, cx, |s| {
14370 s.select(selections);
14371 });
14372 }
14373
14374 pub fn split_selection_into_lines(
14375 &mut self,
14376 action: &SplitSelectionIntoLines,
14377 window: &mut Window,
14378 cx: &mut Context<Self>,
14379 ) {
14380 let selections = self
14381 .selections
14382 .all::<Point>(&self.display_snapshot(cx))
14383 .into_iter()
14384 .map(|selection| selection.start..selection.end)
14385 .collect::<Vec<_>>();
14386 self.unfold_ranges(&selections, true, true, cx);
14387
14388 let mut new_selection_ranges = Vec::new();
14389 {
14390 let buffer = self.buffer.read(cx).read(cx);
14391 for selection in selections {
14392 for row in selection.start.row..selection.end.row {
14393 let line_start = Point::new(row, 0);
14394 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14395
14396 if action.keep_selections {
14397 // Keep the selection range for each line
14398 let selection_start = if row == selection.start.row {
14399 selection.start
14400 } else {
14401 line_start
14402 };
14403 new_selection_ranges.push(selection_start..line_end);
14404 } else {
14405 // Collapse to cursor at end of line
14406 new_selection_ranges.push(line_end..line_end);
14407 }
14408 }
14409
14410 let is_multiline_selection = selection.start.row != selection.end.row;
14411 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14412 // so this action feels more ergonomic when paired with other selection operations
14413 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14414 if !should_skip_last {
14415 if action.keep_selections {
14416 if is_multiline_selection {
14417 let line_start = Point::new(selection.end.row, 0);
14418 new_selection_ranges.push(line_start..selection.end);
14419 } else {
14420 new_selection_ranges.push(selection.start..selection.end);
14421 }
14422 } else {
14423 new_selection_ranges.push(selection.end..selection.end);
14424 }
14425 }
14426 }
14427 }
14428 self.change_selections(Default::default(), window, cx, |s| {
14429 s.select_ranges(new_selection_ranges);
14430 });
14431 }
14432
14433 pub fn add_selection_above(
14434 &mut self,
14435 action: &AddSelectionAbove,
14436 window: &mut Window,
14437 cx: &mut Context<Self>,
14438 ) {
14439 self.add_selection(true, action.skip_soft_wrap, window, cx);
14440 }
14441
14442 pub fn add_selection_below(
14443 &mut self,
14444 action: &AddSelectionBelow,
14445 window: &mut Window,
14446 cx: &mut Context<Self>,
14447 ) {
14448 self.add_selection(false, action.skip_soft_wrap, window, cx);
14449 }
14450
14451 fn add_selection(
14452 &mut self,
14453 above: bool,
14454 skip_soft_wrap: bool,
14455 window: &mut Window,
14456 cx: &mut Context<Self>,
14457 ) {
14458 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14459
14460 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14461 let all_selections = self.selections.all::<Point>(&display_map);
14462 let text_layout_details = self.text_layout_details(window);
14463
14464 let (mut columnar_selections, new_selections_to_columnarize) = {
14465 if let Some(state) = self.add_selections_state.as_ref() {
14466 let columnar_selection_ids: HashSet<_> = state
14467 .groups
14468 .iter()
14469 .flat_map(|group| group.stack.iter())
14470 .copied()
14471 .collect();
14472
14473 all_selections
14474 .into_iter()
14475 .partition(|s| columnar_selection_ids.contains(&s.id))
14476 } else {
14477 (Vec::new(), all_selections)
14478 }
14479 };
14480
14481 let mut state = self
14482 .add_selections_state
14483 .take()
14484 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14485
14486 for selection in new_selections_to_columnarize {
14487 let range = selection.display_range(&display_map).sorted();
14488 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14489 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14490 let positions = start_x.min(end_x)..start_x.max(end_x);
14491 let mut stack = Vec::new();
14492 for row in range.start.row().0..=range.end.row().0 {
14493 if let Some(selection) = self.selections.build_columnar_selection(
14494 &display_map,
14495 DisplayRow(row),
14496 &positions,
14497 selection.reversed,
14498 &text_layout_details,
14499 ) {
14500 stack.push(selection.id);
14501 columnar_selections.push(selection);
14502 }
14503 }
14504 if !stack.is_empty() {
14505 if above {
14506 stack.reverse();
14507 }
14508 state.groups.push(AddSelectionsGroup { above, stack });
14509 }
14510 }
14511
14512 let mut final_selections = Vec::new();
14513 let end_row = if above {
14514 DisplayRow(0)
14515 } else {
14516 display_map.max_point().row()
14517 };
14518
14519 let mut last_added_item_per_group = HashMap::default();
14520 for group in state.groups.iter_mut() {
14521 if let Some(last_id) = group.stack.last() {
14522 last_added_item_per_group.insert(*last_id, group);
14523 }
14524 }
14525
14526 for selection in columnar_selections {
14527 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14528 if above == group.above {
14529 let range = selection.display_range(&display_map).sorted();
14530 debug_assert_eq!(range.start.row(), range.end.row());
14531 let mut row = range.start.row();
14532 let positions =
14533 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14534 Pixels::from(start)..Pixels::from(end)
14535 } else {
14536 let start_x =
14537 display_map.x_for_display_point(range.start, &text_layout_details);
14538 let end_x =
14539 display_map.x_for_display_point(range.end, &text_layout_details);
14540 start_x.min(end_x)..start_x.max(end_x)
14541 };
14542
14543 let mut maybe_new_selection = None;
14544 let direction = if above { -1 } else { 1 };
14545
14546 while row != end_row {
14547 if skip_soft_wrap {
14548 row = display_map
14549 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14550 .row();
14551 } else if above {
14552 row.0 -= 1;
14553 } else {
14554 row.0 += 1;
14555 }
14556
14557 if let Some(new_selection) = self.selections.build_columnar_selection(
14558 &display_map,
14559 row,
14560 &positions,
14561 selection.reversed,
14562 &text_layout_details,
14563 ) {
14564 maybe_new_selection = Some(new_selection);
14565 break;
14566 }
14567 }
14568
14569 if let Some(new_selection) = maybe_new_selection {
14570 group.stack.push(new_selection.id);
14571 if above {
14572 final_selections.push(new_selection);
14573 final_selections.push(selection);
14574 } else {
14575 final_selections.push(selection);
14576 final_selections.push(new_selection);
14577 }
14578 } else {
14579 final_selections.push(selection);
14580 }
14581 } else {
14582 group.stack.pop();
14583 }
14584 } else {
14585 final_selections.push(selection);
14586 }
14587 }
14588
14589 self.change_selections(Default::default(), window, cx, |s| {
14590 s.select(final_selections);
14591 });
14592
14593 let final_selection_ids: HashSet<_> = self
14594 .selections
14595 .all::<Point>(&display_map)
14596 .iter()
14597 .map(|s| s.id)
14598 .collect();
14599 state.groups.retain_mut(|group| {
14600 // selections might get merged above so we remove invalid items from stacks
14601 group.stack.retain(|id| final_selection_ids.contains(id));
14602
14603 // single selection in stack can be treated as initial state
14604 group.stack.len() > 1
14605 });
14606
14607 if !state.groups.is_empty() {
14608 self.add_selections_state = Some(state);
14609 }
14610 }
14611
14612 fn select_match_ranges(
14613 &mut self,
14614 range: Range<MultiBufferOffset>,
14615 reversed: bool,
14616 replace_newest: bool,
14617 auto_scroll: Option<Autoscroll>,
14618 window: &mut Window,
14619 cx: &mut Context<Editor>,
14620 ) {
14621 self.unfold_ranges(
14622 std::slice::from_ref(&range),
14623 false,
14624 auto_scroll.is_some(),
14625 cx,
14626 );
14627 let effects = if let Some(scroll) = auto_scroll {
14628 SelectionEffects::scroll(scroll)
14629 } else {
14630 SelectionEffects::no_scroll()
14631 };
14632 self.change_selections(effects, window, cx, |s| {
14633 if replace_newest {
14634 s.delete(s.newest_anchor().id);
14635 }
14636 if reversed {
14637 s.insert_range(range.end..range.start);
14638 } else {
14639 s.insert_range(range);
14640 }
14641 });
14642 }
14643
14644 pub fn select_next_match_internal(
14645 &mut self,
14646 display_map: &DisplaySnapshot,
14647 replace_newest: bool,
14648 autoscroll: Option<Autoscroll>,
14649 window: &mut Window,
14650 cx: &mut Context<Self>,
14651 ) -> Result<()> {
14652 let buffer = display_map.buffer_snapshot();
14653 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
14654 if let Some(mut select_next_state) = self.select_next_state.take() {
14655 let query = &select_next_state.query;
14656 if !select_next_state.done {
14657 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14658 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14659 let mut next_selected_range = None;
14660
14661 let bytes_after_last_selection =
14662 buffer.bytes_in_range(last_selection.end..buffer.len());
14663 let bytes_before_first_selection =
14664 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
14665 let query_matches = query
14666 .stream_find_iter(bytes_after_last_selection)
14667 .map(|result| (last_selection.end, result))
14668 .chain(
14669 query
14670 .stream_find_iter(bytes_before_first_selection)
14671 .map(|result| (MultiBufferOffset(0), result)),
14672 );
14673
14674 for (start_offset, query_match) in query_matches {
14675 let query_match = query_match.unwrap(); // can only fail due to I/O
14676 let offset_range =
14677 start_offset + query_match.start()..start_offset + query_match.end();
14678
14679 if !select_next_state.wordwise
14680 || (!buffer.is_inside_word(offset_range.start, None)
14681 && !buffer.is_inside_word(offset_range.end, None))
14682 {
14683 let idx = selections
14684 .partition_point(|selection| selection.end <= offset_range.start);
14685 let overlaps = selections
14686 .get(idx)
14687 .map_or(false, |selection| selection.start < offset_range.end);
14688
14689 if !overlaps {
14690 next_selected_range = Some(offset_range);
14691 break;
14692 }
14693 }
14694 }
14695
14696 if let Some(next_selected_range) = next_selected_range {
14697 self.select_match_ranges(
14698 next_selected_range,
14699 last_selection.reversed,
14700 replace_newest,
14701 autoscroll,
14702 window,
14703 cx,
14704 );
14705 } else {
14706 select_next_state.done = true;
14707 }
14708 }
14709
14710 self.select_next_state = Some(select_next_state);
14711 } else {
14712 let mut only_carets = true;
14713 let mut same_text_selected = true;
14714 let mut selected_text = None;
14715
14716 let mut selections_iter = selections.iter().peekable();
14717 while let Some(selection) = selections_iter.next() {
14718 if selection.start != selection.end {
14719 only_carets = false;
14720 }
14721
14722 if same_text_selected {
14723 if selected_text.is_none() {
14724 selected_text =
14725 Some(buffer.text_for_range(selection.range()).collect::<String>());
14726 }
14727
14728 if let Some(next_selection) = selections_iter.peek() {
14729 if next_selection.len() == selection.len() {
14730 let next_selected_text = buffer
14731 .text_for_range(next_selection.range())
14732 .collect::<String>();
14733 if Some(next_selected_text) != selected_text {
14734 same_text_selected = false;
14735 selected_text = None;
14736 }
14737 } else {
14738 same_text_selected = false;
14739 selected_text = None;
14740 }
14741 }
14742 }
14743 }
14744
14745 if only_carets {
14746 for selection in &mut selections {
14747 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14748 selection.start = word_range.start;
14749 selection.end = word_range.end;
14750 selection.goal = SelectionGoal::None;
14751 selection.reversed = false;
14752 self.select_match_ranges(
14753 selection.start..selection.end,
14754 selection.reversed,
14755 replace_newest,
14756 autoscroll,
14757 window,
14758 cx,
14759 );
14760 }
14761
14762 if selections.len() == 1 {
14763 let selection = selections
14764 .last()
14765 .expect("ensured that there's only one selection");
14766 let query = buffer
14767 .text_for_range(selection.start..selection.end)
14768 .collect::<String>();
14769 let is_empty = query.is_empty();
14770 let select_state = SelectNextState {
14771 query: self.build_query(&[query], cx)?,
14772 wordwise: true,
14773 done: is_empty,
14774 };
14775 self.select_next_state = Some(select_state);
14776 } else {
14777 self.select_next_state = None;
14778 }
14779 } else if let Some(selected_text) = selected_text {
14780 self.select_next_state = Some(SelectNextState {
14781 query: self.build_query(&[selected_text], cx)?,
14782 wordwise: false,
14783 done: false,
14784 });
14785 self.select_next_match_internal(
14786 display_map,
14787 replace_newest,
14788 autoscroll,
14789 window,
14790 cx,
14791 )?;
14792 }
14793 }
14794 Ok(())
14795 }
14796
14797 pub fn select_all_matches(
14798 &mut self,
14799 _action: &SelectAllMatches,
14800 window: &mut Window,
14801 cx: &mut Context<Self>,
14802 ) -> Result<()> {
14803 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14804
14805 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14806
14807 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14808 let Some(select_next_state) = self.select_next_state.as_mut() else {
14809 return Ok(());
14810 };
14811 if select_next_state.done {
14812 return Ok(());
14813 }
14814
14815 let mut new_selections = Vec::new();
14816
14817 let reversed = self
14818 .selections
14819 .oldest::<MultiBufferOffset>(&display_map)
14820 .reversed;
14821 let buffer = display_map.buffer_snapshot();
14822 let query_matches = select_next_state
14823 .query
14824 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
14825
14826 for query_match in query_matches.into_iter() {
14827 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14828 let offset_range = if reversed {
14829 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
14830 } else {
14831 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
14832 };
14833
14834 if !select_next_state.wordwise
14835 || (!buffer.is_inside_word(offset_range.start, None)
14836 && !buffer.is_inside_word(offset_range.end, None))
14837 {
14838 new_selections.push(offset_range.start..offset_range.end);
14839 }
14840 }
14841
14842 select_next_state.done = true;
14843
14844 if new_selections.is_empty() {
14845 log::error!("bug: new_selections is empty in select_all_matches");
14846 return Ok(());
14847 }
14848
14849 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14850 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14851 selections.select_ranges(new_selections)
14852 });
14853
14854 Ok(())
14855 }
14856
14857 pub fn select_next(
14858 &mut self,
14859 action: &SelectNext,
14860 window: &mut Window,
14861 cx: &mut Context<Self>,
14862 ) -> Result<()> {
14863 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14864 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14865 self.select_next_match_internal(
14866 &display_map,
14867 action.replace_newest,
14868 Some(Autoscroll::newest()),
14869 window,
14870 cx,
14871 )?;
14872 Ok(())
14873 }
14874
14875 pub fn select_previous(
14876 &mut self,
14877 action: &SelectPrevious,
14878 window: &mut Window,
14879 cx: &mut Context<Self>,
14880 ) -> Result<()> {
14881 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14882 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14883 let buffer = display_map.buffer_snapshot();
14884 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
14885 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14886 let query = &select_prev_state.query;
14887 if !select_prev_state.done {
14888 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14889 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14890 let mut next_selected_range = None;
14891 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14892 let bytes_before_last_selection =
14893 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
14894 let bytes_after_first_selection =
14895 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14896 let query_matches = query
14897 .stream_find_iter(bytes_before_last_selection)
14898 .map(|result| (last_selection.start, result))
14899 .chain(
14900 query
14901 .stream_find_iter(bytes_after_first_selection)
14902 .map(|result| (buffer.len(), result)),
14903 );
14904 for (end_offset, query_match) in query_matches {
14905 let query_match = query_match.unwrap(); // can only fail due to I/O
14906 let offset_range =
14907 end_offset - query_match.end()..end_offset - query_match.start();
14908
14909 if !select_prev_state.wordwise
14910 || (!buffer.is_inside_word(offset_range.start, None)
14911 && !buffer.is_inside_word(offset_range.end, None))
14912 {
14913 next_selected_range = Some(offset_range);
14914 break;
14915 }
14916 }
14917
14918 if let Some(next_selected_range) = next_selected_range {
14919 self.select_match_ranges(
14920 next_selected_range,
14921 last_selection.reversed,
14922 action.replace_newest,
14923 Some(Autoscroll::newest()),
14924 window,
14925 cx,
14926 );
14927 } else {
14928 select_prev_state.done = true;
14929 }
14930 }
14931
14932 self.select_prev_state = Some(select_prev_state);
14933 } else {
14934 let mut only_carets = true;
14935 let mut same_text_selected = true;
14936 let mut selected_text = None;
14937
14938 let mut selections_iter = selections.iter().peekable();
14939 while let Some(selection) = selections_iter.next() {
14940 if selection.start != selection.end {
14941 only_carets = false;
14942 }
14943
14944 if same_text_selected {
14945 if selected_text.is_none() {
14946 selected_text =
14947 Some(buffer.text_for_range(selection.range()).collect::<String>());
14948 }
14949
14950 if let Some(next_selection) = selections_iter.peek() {
14951 if next_selection.len() == selection.len() {
14952 let next_selected_text = buffer
14953 .text_for_range(next_selection.range())
14954 .collect::<String>();
14955 if Some(next_selected_text) != selected_text {
14956 same_text_selected = false;
14957 selected_text = None;
14958 }
14959 } else {
14960 same_text_selected = false;
14961 selected_text = None;
14962 }
14963 }
14964 }
14965 }
14966
14967 if only_carets {
14968 for selection in &mut selections {
14969 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14970 selection.start = word_range.start;
14971 selection.end = word_range.end;
14972 selection.goal = SelectionGoal::None;
14973 selection.reversed = false;
14974 self.select_match_ranges(
14975 selection.start..selection.end,
14976 selection.reversed,
14977 action.replace_newest,
14978 Some(Autoscroll::newest()),
14979 window,
14980 cx,
14981 );
14982 }
14983 if selections.len() == 1 {
14984 let selection = selections
14985 .last()
14986 .expect("ensured that there's only one selection");
14987 let query = buffer
14988 .text_for_range(selection.start..selection.end)
14989 .collect::<String>();
14990 let is_empty = query.is_empty();
14991 let select_state = SelectNextState {
14992 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
14993 wordwise: true,
14994 done: is_empty,
14995 };
14996 self.select_prev_state = Some(select_state);
14997 } else {
14998 self.select_prev_state = None;
14999 }
15000 } else if let Some(selected_text) = selected_text {
15001 self.select_prev_state = Some(SelectNextState {
15002 query: self
15003 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15004 wordwise: false,
15005 done: false,
15006 });
15007 self.select_previous(action, window, cx)?;
15008 }
15009 }
15010 Ok(())
15011 }
15012
15013 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15014 /// setting the case sensitivity based on the global
15015 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15016 /// editor's settings.
15017 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15018 where
15019 I: IntoIterator<Item = P>,
15020 P: AsRef<[u8]>,
15021 {
15022 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
15023 || EditorSettings::get_global(cx).search.case_sensitive,
15024 |value| value,
15025 );
15026
15027 let mut builder = AhoCorasickBuilder::new();
15028 builder.ascii_case_insensitive(!case_sensitive);
15029 builder.build(patterns)
15030 }
15031
15032 pub fn find_next_match(
15033 &mut self,
15034 _: &FindNextMatch,
15035 window: &mut Window,
15036 cx: &mut Context<Self>,
15037 ) -> Result<()> {
15038 let selections = self.selections.disjoint_anchors_arc();
15039 match selections.first() {
15040 Some(first) if selections.len() >= 2 => {
15041 self.change_selections(Default::default(), window, cx, |s| {
15042 s.select_ranges([first.range()]);
15043 });
15044 }
15045 _ => self.select_next(
15046 &SelectNext {
15047 replace_newest: true,
15048 },
15049 window,
15050 cx,
15051 )?,
15052 }
15053 Ok(())
15054 }
15055
15056 pub fn find_previous_match(
15057 &mut self,
15058 _: &FindPreviousMatch,
15059 window: &mut Window,
15060 cx: &mut Context<Self>,
15061 ) -> Result<()> {
15062 let selections = self.selections.disjoint_anchors_arc();
15063 match selections.last() {
15064 Some(last) if selections.len() >= 2 => {
15065 self.change_selections(Default::default(), window, cx, |s| {
15066 s.select_ranges([last.range()]);
15067 });
15068 }
15069 _ => self.select_previous(
15070 &SelectPrevious {
15071 replace_newest: true,
15072 },
15073 window,
15074 cx,
15075 )?,
15076 }
15077 Ok(())
15078 }
15079
15080 pub fn toggle_comments(
15081 &mut self,
15082 action: &ToggleComments,
15083 window: &mut Window,
15084 cx: &mut Context<Self>,
15085 ) {
15086 if self.read_only(cx) {
15087 return;
15088 }
15089 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15090 let text_layout_details = &self.text_layout_details(window);
15091 self.transact(window, cx, |this, window, cx| {
15092 let mut selections = this
15093 .selections
15094 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15095 let mut edits = Vec::new();
15096 let mut selection_edit_ranges = Vec::new();
15097 let mut last_toggled_row = None;
15098 let snapshot = this.buffer.read(cx).read(cx);
15099 let empty_str: Arc<str> = Arc::default();
15100 let mut suffixes_inserted = Vec::new();
15101 let ignore_indent = action.ignore_indent;
15102
15103 fn comment_prefix_range(
15104 snapshot: &MultiBufferSnapshot,
15105 row: MultiBufferRow,
15106 comment_prefix: &str,
15107 comment_prefix_whitespace: &str,
15108 ignore_indent: bool,
15109 ) -> Range<Point> {
15110 let indent_size = if ignore_indent {
15111 0
15112 } else {
15113 snapshot.indent_size_for_line(row).len
15114 };
15115
15116 let start = Point::new(row.0, indent_size);
15117
15118 let mut line_bytes = snapshot
15119 .bytes_in_range(start..snapshot.max_point())
15120 .flatten()
15121 .copied();
15122
15123 // If this line currently begins with the line comment prefix, then record
15124 // the range containing the prefix.
15125 if line_bytes
15126 .by_ref()
15127 .take(comment_prefix.len())
15128 .eq(comment_prefix.bytes())
15129 {
15130 // Include any whitespace that matches the comment prefix.
15131 let matching_whitespace_len = line_bytes
15132 .zip(comment_prefix_whitespace.bytes())
15133 .take_while(|(a, b)| a == b)
15134 .count() as u32;
15135 let end = Point::new(
15136 start.row,
15137 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15138 );
15139 start..end
15140 } else {
15141 start..start
15142 }
15143 }
15144
15145 fn comment_suffix_range(
15146 snapshot: &MultiBufferSnapshot,
15147 row: MultiBufferRow,
15148 comment_suffix: &str,
15149 comment_suffix_has_leading_space: bool,
15150 ) -> Range<Point> {
15151 let end = Point::new(row.0, snapshot.line_len(row));
15152 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15153
15154 let mut line_end_bytes = snapshot
15155 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15156 .flatten()
15157 .copied();
15158
15159 let leading_space_len = if suffix_start_column > 0
15160 && line_end_bytes.next() == Some(b' ')
15161 && comment_suffix_has_leading_space
15162 {
15163 1
15164 } else {
15165 0
15166 };
15167
15168 // If this line currently begins with the line comment prefix, then record
15169 // the range containing the prefix.
15170 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15171 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15172 start..end
15173 } else {
15174 end..end
15175 }
15176 }
15177
15178 // TODO: Handle selections that cross excerpts
15179 for selection in &mut selections {
15180 let start_column = snapshot
15181 .indent_size_for_line(MultiBufferRow(selection.start.row))
15182 .len;
15183 let language = if let Some(language) =
15184 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15185 {
15186 language
15187 } else {
15188 continue;
15189 };
15190
15191 selection_edit_ranges.clear();
15192
15193 // If multiple selections contain a given row, avoid processing that
15194 // row more than once.
15195 let mut start_row = MultiBufferRow(selection.start.row);
15196 if last_toggled_row == Some(start_row) {
15197 start_row = start_row.next_row();
15198 }
15199 let end_row =
15200 if selection.end.row > selection.start.row && selection.end.column == 0 {
15201 MultiBufferRow(selection.end.row - 1)
15202 } else {
15203 MultiBufferRow(selection.end.row)
15204 };
15205 last_toggled_row = Some(end_row);
15206
15207 if start_row > end_row {
15208 continue;
15209 }
15210
15211 // If the language has line comments, toggle those.
15212 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15213
15214 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15215 if ignore_indent {
15216 full_comment_prefixes = full_comment_prefixes
15217 .into_iter()
15218 .map(|s| Arc::from(s.trim_end()))
15219 .collect();
15220 }
15221
15222 if !full_comment_prefixes.is_empty() {
15223 let first_prefix = full_comment_prefixes
15224 .first()
15225 .expect("prefixes is non-empty");
15226 let prefix_trimmed_lengths = full_comment_prefixes
15227 .iter()
15228 .map(|p| p.trim_end_matches(' ').len())
15229 .collect::<SmallVec<[usize; 4]>>();
15230
15231 let mut all_selection_lines_are_comments = true;
15232
15233 for row in start_row.0..=end_row.0 {
15234 let row = MultiBufferRow(row);
15235 if start_row < end_row && snapshot.is_line_blank(row) {
15236 continue;
15237 }
15238
15239 let prefix_range = full_comment_prefixes
15240 .iter()
15241 .zip(prefix_trimmed_lengths.iter().copied())
15242 .map(|(prefix, trimmed_prefix_len)| {
15243 comment_prefix_range(
15244 snapshot.deref(),
15245 row,
15246 &prefix[..trimmed_prefix_len],
15247 &prefix[trimmed_prefix_len..],
15248 ignore_indent,
15249 )
15250 })
15251 .max_by_key(|range| range.end.column - range.start.column)
15252 .expect("prefixes is non-empty");
15253
15254 if prefix_range.is_empty() {
15255 all_selection_lines_are_comments = false;
15256 }
15257
15258 selection_edit_ranges.push(prefix_range);
15259 }
15260
15261 if all_selection_lines_are_comments {
15262 edits.extend(
15263 selection_edit_ranges
15264 .iter()
15265 .cloned()
15266 .map(|range| (range, empty_str.clone())),
15267 );
15268 } else {
15269 let min_column = selection_edit_ranges
15270 .iter()
15271 .map(|range| range.start.column)
15272 .min()
15273 .unwrap_or(0);
15274 edits.extend(selection_edit_ranges.iter().map(|range| {
15275 let position = Point::new(range.start.row, min_column);
15276 (position..position, first_prefix.clone())
15277 }));
15278 }
15279 } else if let Some(BlockCommentConfig {
15280 start: full_comment_prefix,
15281 end: comment_suffix,
15282 ..
15283 }) = language.block_comment()
15284 {
15285 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15286 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15287 let prefix_range = comment_prefix_range(
15288 snapshot.deref(),
15289 start_row,
15290 comment_prefix,
15291 comment_prefix_whitespace,
15292 ignore_indent,
15293 );
15294 let suffix_range = comment_suffix_range(
15295 snapshot.deref(),
15296 end_row,
15297 comment_suffix.trim_start_matches(' '),
15298 comment_suffix.starts_with(' '),
15299 );
15300
15301 if prefix_range.is_empty() || suffix_range.is_empty() {
15302 edits.push((
15303 prefix_range.start..prefix_range.start,
15304 full_comment_prefix.clone(),
15305 ));
15306 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15307 suffixes_inserted.push((end_row, comment_suffix.len()));
15308 } else {
15309 edits.push((prefix_range, empty_str.clone()));
15310 edits.push((suffix_range, empty_str.clone()));
15311 }
15312 } else {
15313 continue;
15314 }
15315 }
15316
15317 drop(snapshot);
15318 this.buffer.update(cx, |buffer, cx| {
15319 buffer.edit(edits, None, cx);
15320 });
15321
15322 // Adjust selections so that they end before any comment suffixes that
15323 // were inserted.
15324 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15325 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15326 let snapshot = this.buffer.read(cx).read(cx);
15327 for selection in &mut selections {
15328 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15329 match row.cmp(&MultiBufferRow(selection.end.row)) {
15330 Ordering::Less => {
15331 suffixes_inserted.next();
15332 continue;
15333 }
15334 Ordering::Greater => break,
15335 Ordering::Equal => {
15336 if selection.end.column == snapshot.line_len(row) {
15337 if selection.is_empty() {
15338 selection.start.column -= suffix_len as u32;
15339 }
15340 selection.end.column -= suffix_len as u32;
15341 }
15342 break;
15343 }
15344 }
15345 }
15346 }
15347
15348 drop(snapshot);
15349 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15350
15351 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15352 let selections_on_single_row = selections.windows(2).all(|selections| {
15353 selections[0].start.row == selections[1].start.row
15354 && selections[0].end.row == selections[1].end.row
15355 && selections[0].start.row == selections[0].end.row
15356 });
15357 let selections_selecting = selections
15358 .iter()
15359 .any(|selection| selection.start != selection.end);
15360 let advance_downwards = action.advance_downwards
15361 && selections_on_single_row
15362 && !selections_selecting
15363 && !matches!(this.mode, EditorMode::SingleLine);
15364
15365 if advance_downwards {
15366 let snapshot = this.buffer.read(cx).snapshot(cx);
15367
15368 this.change_selections(Default::default(), window, cx, |s| {
15369 s.move_cursors_with(|display_snapshot, display_point, _| {
15370 let mut point = display_point.to_point(display_snapshot);
15371 point.row += 1;
15372 point = snapshot.clip_point(point, Bias::Left);
15373 let display_point = point.to_display_point(display_snapshot);
15374 let goal = SelectionGoal::HorizontalPosition(
15375 display_snapshot
15376 .x_for_display_point(display_point, text_layout_details)
15377 .into(),
15378 );
15379 (display_point, goal)
15380 })
15381 });
15382 }
15383 });
15384 }
15385
15386 pub fn select_enclosing_symbol(
15387 &mut self,
15388 _: &SelectEnclosingSymbol,
15389 window: &mut Window,
15390 cx: &mut Context<Self>,
15391 ) {
15392 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15393
15394 let buffer = self.buffer.read(cx).snapshot(cx);
15395 let old_selections = self
15396 .selections
15397 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15398 .into_boxed_slice();
15399
15400 fn update_selection(
15401 selection: &Selection<MultiBufferOffset>,
15402 buffer_snap: &MultiBufferSnapshot,
15403 ) -> Option<Selection<MultiBufferOffset>> {
15404 let cursor = selection.head();
15405 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15406 for symbol in symbols.iter().rev() {
15407 let start = symbol.range.start.to_offset(buffer_snap);
15408 let end = symbol.range.end.to_offset(buffer_snap);
15409 let new_range = start..end;
15410 if start < selection.start || end > selection.end {
15411 return Some(Selection {
15412 id: selection.id,
15413 start: new_range.start,
15414 end: new_range.end,
15415 goal: SelectionGoal::None,
15416 reversed: selection.reversed,
15417 });
15418 }
15419 }
15420 None
15421 }
15422
15423 let mut selected_larger_symbol = false;
15424 let new_selections = old_selections
15425 .iter()
15426 .map(|selection| match update_selection(selection, &buffer) {
15427 Some(new_selection) => {
15428 if new_selection.range() != selection.range() {
15429 selected_larger_symbol = true;
15430 }
15431 new_selection
15432 }
15433 None => selection.clone(),
15434 })
15435 .collect::<Vec<_>>();
15436
15437 if selected_larger_symbol {
15438 self.change_selections(Default::default(), window, cx, |s| {
15439 s.select(new_selections);
15440 });
15441 }
15442 }
15443
15444 pub fn select_larger_syntax_node(
15445 &mut self,
15446 _: &SelectLargerSyntaxNode,
15447 window: &mut Window,
15448 cx: &mut Context<Self>,
15449 ) {
15450 let Some(visible_row_count) = self.visible_row_count() else {
15451 return;
15452 };
15453 let old_selections: Box<[_]> = self
15454 .selections
15455 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15456 .into();
15457 if old_selections.is_empty() {
15458 return;
15459 }
15460
15461 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15462
15463 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15464 let buffer = self.buffer.read(cx).snapshot(cx);
15465
15466 let mut selected_larger_node = false;
15467 let mut new_selections = old_selections
15468 .iter()
15469 .map(|selection| {
15470 let old_range = selection.start..selection.end;
15471
15472 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15473 // manually select word at selection
15474 if ["string_content", "inline"].contains(&node.kind()) {
15475 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15476 // ignore if word is already selected
15477 if !word_range.is_empty() && old_range != word_range {
15478 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15479 // only select word if start and end point belongs to same word
15480 if word_range == last_word_range {
15481 selected_larger_node = true;
15482 return Selection {
15483 id: selection.id,
15484 start: word_range.start,
15485 end: word_range.end,
15486 goal: SelectionGoal::None,
15487 reversed: selection.reversed,
15488 };
15489 }
15490 }
15491 }
15492 }
15493
15494 let mut new_range = old_range.clone();
15495 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15496 new_range = range;
15497 if !node.is_named() {
15498 continue;
15499 }
15500 if !display_map.intersects_fold(new_range.start)
15501 && !display_map.intersects_fold(new_range.end)
15502 {
15503 break;
15504 }
15505 }
15506
15507 selected_larger_node |= new_range != old_range;
15508 Selection {
15509 id: selection.id,
15510 start: new_range.start,
15511 end: new_range.end,
15512 goal: SelectionGoal::None,
15513 reversed: selection.reversed,
15514 }
15515 })
15516 .collect::<Vec<_>>();
15517
15518 if !selected_larger_node {
15519 return; // don't put this call in the history
15520 }
15521
15522 // scroll based on transformation done to the last selection created by the user
15523 let (last_old, last_new) = old_selections
15524 .last()
15525 .zip(new_selections.last().cloned())
15526 .expect("old_selections isn't empty");
15527
15528 // revert selection
15529 let is_selection_reversed = {
15530 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15531 new_selections.last_mut().expect("checked above").reversed =
15532 should_newest_selection_be_reversed;
15533 should_newest_selection_be_reversed
15534 };
15535
15536 if selected_larger_node {
15537 self.select_syntax_node_history.disable_clearing = true;
15538 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15539 s.select(new_selections.clone());
15540 });
15541 self.select_syntax_node_history.disable_clearing = false;
15542 }
15543
15544 let start_row = last_new.start.to_display_point(&display_map).row().0;
15545 let end_row = last_new.end.to_display_point(&display_map).row().0;
15546 let selection_height = end_row - start_row + 1;
15547 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15548
15549 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15550 let scroll_behavior = if fits_on_the_screen {
15551 self.request_autoscroll(Autoscroll::fit(), cx);
15552 SelectSyntaxNodeScrollBehavior::FitSelection
15553 } else if is_selection_reversed {
15554 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15555 SelectSyntaxNodeScrollBehavior::CursorTop
15556 } else {
15557 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15558 SelectSyntaxNodeScrollBehavior::CursorBottom
15559 };
15560
15561 self.select_syntax_node_history.push((
15562 old_selections,
15563 scroll_behavior,
15564 is_selection_reversed,
15565 ));
15566 }
15567
15568 pub fn select_smaller_syntax_node(
15569 &mut self,
15570 _: &SelectSmallerSyntaxNode,
15571 window: &mut Window,
15572 cx: &mut Context<Self>,
15573 ) {
15574 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15575
15576 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15577 self.select_syntax_node_history.pop()
15578 {
15579 if let Some(selection) = selections.last_mut() {
15580 selection.reversed = is_selection_reversed;
15581 }
15582
15583 self.select_syntax_node_history.disable_clearing = true;
15584 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15585 s.select(selections.to_vec());
15586 });
15587 self.select_syntax_node_history.disable_clearing = false;
15588
15589 match scroll_behavior {
15590 SelectSyntaxNodeScrollBehavior::CursorTop => {
15591 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15592 }
15593 SelectSyntaxNodeScrollBehavior::FitSelection => {
15594 self.request_autoscroll(Autoscroll::fit(), cx);
15595 }
15596 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15597 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15598 }
15599 }
15600 }
15601 }
15602
15603 pub fn unwrap_syntax_node(
15604 &mut self,
15605 _: &UnwrapSyntaxNode,
15606 window: &mut Window,
15607 cx: &mut Context<Self>,
15608 ) {
15609 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15610
15611 let buffer = self.buffer.read(cx).snapshot(cx);
15612 let selections = self
15613 .selections
15614 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15615 .into_iter()
15616 // subtracting the offset requires sorting
15617 .sorted_by_key(|i| i.start);
15618
15619 let full_edits = selections
15620 .into_iter()
15621 .filter_map(|selection| {
15622 let child = if selection.is_empty()
15623 && let Some((_, ancestor_range)) =
15624 buffer.syntax_ancestor(selection.start..selection.end)
15625 {
15626 ancestor_range
15627 } else {
15628 selection.range()
15629 };
15630
15631 let mut parent = child.clone();
15632 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15633 parent = ancestor_range;
15634 if parent.start < child.start || parent.end > child.end {
15635 break;
15636 }
15637 }
15638
15639 if parent == child {
15640 return None;
15641 }
15642 let text = buffer.text_for_range(child).collect::<String>();
15643 Some((selection.id, parent, text))
15644 })
15645 .collect::<Vec<_>>();
15646 if full_edits.is_empty() {
15647 return;
15648 }
15649
15650 self.transact(window, cx, |this, window, cx| {
15651 this.buffer.update(cx, |buffer, cx| {
15652 buffer.edit(
15653 full_edits
15654 .iter()
15655 .map(|(_, p, t)| (p.clone(), t.clone()))
15656 .collect::<Vec<_>>(),
15657 None,
15658 cx,
15659 );
15660 });
15661 this.change_selections(Default::default(), window, cx, |s| {
15662 let mut offset = 0;
15663 let mut selections = vec![];
15664 for (id, parent, text) in full_edits {
15665 let start = parent.start - offset;
15666 offset += (parent.end - parent.start) - text.len();
15667 selections.push(Selection {
15668 id,
15669 start,
15670 end: start + text.len(),
15671 reversed: false,
15672 goal: Default::default(),
15673 });
15674 }
15675 s.select(selections);
15676 });
15677 });
15678 }
15679
15680 pub fn select_next_syntax_node(
15681 &mut self,
15682 _: &SelectNextSyntaxNode,
15683 window: &mut Window,
15684 cx: &mut Context<Self>,
15685 ) {
15686 let old_selections: Box<[_]> = self
15687 .selections
15688 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15689 .into();
15690 if old_selections.is_empty() {
15691 return;
15692 }
15693
15694 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15695
15696 let buffer = self.buffer.read(cx).snapshot(cx);
15697 let mut selected_sibling = false;
15698
15699 let new_selections = old_selections
15700 .iter()
15701 .map(|selection| {
15702 let old_range = selection.start..selection.end;
15703
15704 let old_range =
15705 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15706 let excerpt = buffer.excerpt_containing(old_range.clone());
15707
15708 if let Some(mut excerpt) = excerpt
15709 && let Some(node) = excerpt
15710 .buffer()
15711 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
15712 {
15713 let new_range = excerpt.map_range_from_buffer(
15714 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15715 );
15716 selected_sibling = true;
15717 Selection {
15718 id: selection.id,
15719 start: new_range.start,
15720 end: new_range.end,
15721 goal: SelectionGoal::None,
15722 reversed: selection.reversed,
15723 }
15724 } else {
15725 selection.clone()
15726 }
15727 })
15728 .collect::<Vec<_>>();
15729
15730 if selected_sibling {
15731 self.change_selections(
15732 SelectionEffects::scroll(Autoscroll::fit()),
15733 window,
15734 cx,
15735 |s| {
15736 s.select(new_selections);
15737 },
15738 );
15739 }
15740 }
15741
15742 pub fn select_prev_syntax_node(
15743 &mut self,
15744 _: &SelectPreviousSyntaxNode,
15745 window: &mut Window,
15746 cx: &mut Context<Self>,
15747 ) {
15748 let old_selections: Box<[_]> = self
15749 .selections
15750 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15751 .into();
15752 if old_selections.is_empty() {
15753 return;
15754 }
15755
15756 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15757
15758 let buffer = self.buffer.read(cx).snapshot(cx);
15759 let mut selected_sibling = false;
15760
15761 let new_selections = old_selections
15762 .iter()
15763 .map(|selection| {
15764 let old_range = selection.start..selection.end;
15765 let old_range =
15766 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15767 let excerpt = buffer.excerpt_containing(old_range.clone());
15768
15769 if let Some(mut excerpt) = excerpt
15770 && let Some(node) = excerpt
15771 .buffer()
15772 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
15773 {
15774 let new_range = excerpt.map_range_from_buffer(
15775 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15776 );
15777 selected_sibling = true;
15778 Selection {
15779 id: selection.id,
15780 start: new_range.start,
15781 end: new_range.end,
15782 goal: SelectionGoal::None,
15783 reversed: selection.reversed,
15784 }
15785 } else {
15786 selection.clone()
15787 }
15788 })
15789 .collect::<Vec<_>>();
15790
15791 if selected_sibling {
15792 self.change_selections(
15793 SelectionEffects::scroll(Autoscroll::fit()),
15794 window,
15795 cx,
15796 |s| {
15797 s.select(new_selections);
15798 },
15799 );
15800 }
15801 }
15802
15803 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15804 if !EditorSettings::get_global(cx).gutter.runnables {
15805 self.clear_tasks();
15806 return Task::ready(());
15807 }
15808 let project = self.project().map(Entity::downgrade);
15809 let task_sources = self.lsp_task_sources(cx);
15810 let multi_buffer = self.buffer.downgrade();
15811 cx.spawn_in(window, async move |editor, cx| {
15812 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15813 let Some(project) = project.and_then(|p| p.upgrade()) else {
15814 return;
15815 };
15816 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15817 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15818 }) else {
15819 return;
15820 };
15821
15822 let hide_runnables = project
15823 .update(cx, |project, _| project.is_via_collab())
15824 .unwrap_or(true);
15825 if hide_runnables {
15826 return;
15827 }
15828 let new_rows =
15829 cx.background_spawn({
15830 let snapshot = display_snapshot.clone();
15831 async move {
15832 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15833 }
15834 })
15835 .await;
15836 let Ok(lsp_tasks) =
15837 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15838 else {
15839 return;
15840 };
15841 let lsp_tasks = lsp_tasks.await;
15842
15843 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15844 lsp_tasks
15845 .into_iter()
15846 .flat_map(|(kind, tasks)| {
15847 tasks.into_iter().filter_map(move |(location, task)| {
15848 Some((kind.clone(), location?, task))
15849 })
15850 })
15851 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15852 let buffer = location.target.buffer;
15853 let buffer_snapshot = buffer.read(cx).snapshot();
15854 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15855 |(excerpt_id, snapshot, _)| {
15856 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15857 display_snapshot
15858 .buffer_snapshot()
15859 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15860 } else {
15861 None
15862 }
15863 },
15864 );
15865 if let Some(offset) = offset {
15866 let task_buffer_range =
15867 location.target.range.to_point(&buffer_snapshot);
15868 let context_buffer_range =
15869 task_buffer_range.to_offset(&buffer_snapshot);
15870 let context_range = BufferOffset(context_buffer_range.start)
15871 ..BufferOffset(context_buffer_range.end);
15872
15873 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15874 .or_insert_with(|| RunnableTasks {
15875 templates: Vec::new(),
15876 offset,
15877 column: task_buffer_range.start.column,
15878 extra_variables: HashMap::default(),
15879 context_range,
15880 })
15881 .templates
15882 .push((kind, task.original_task().clone()));
15883 }
15884
15885 acc
15886 })
15887 }) else {
15888 return;
15889 };
15890
15891 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15892 buffer.language_settings(cx).tasks.prefer_lsp
15893 }) else {
15894 return;
15895 };
15896
15897 let rows = Self::runnable_rows(
15898 project,
15899 display_snapshot,
15900 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15901 new_rows,
15902 cx.clone(),
15903 )
15904 .await;
15905 editor
15906 .update(cx, |editor, _| {
15907 editor.clear_tasks();
15908 for (key, mut value) in rows {
15909 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15910 value.templates.extend(lsp_tasks.templates);
15911 }
15912
15913 editor.insert_tasks(key, value);
15914 }
15915 for (key, value) in lsp_tasks_by_rows {
15916 editor.insert_tasks(key, value);
15917 }
15918 })
15919 .ok();
15920 })
15921 }
15922 fn fetch_runnable_ranges(
15923 snapshot: &DisplaySnapshot,
15924 range: Range<Anchor>,
15925 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
15926 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15927 }
15928
15929 fn runnable_rows(
15930 project: Entity<Project>,
15931 snapshot: DisplaySnapshot,
15932 prefer_lsp: bool,
15933 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
15934 cx: AsyncWindowContext,
15935 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15936 cx.spawn(async move |cx| {
15937 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15938 for (run_range, mut runnable) in runnable_ranges {
15939 let Some(tasks) = cx
15940 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15941 .ok()
15942 else {
15943 continue;
15944 };
15945 let mut tasks = tasks.await;
15946
15947 if prefer_lsp {
15948 tasks.retain(|(task_kind, _)| {
15949 !matches!(task_kind, TaskSourceKind::Language { .. })
15950 });
15951 }
15952 if tasks.is_empty() {
15953 continue;
15954 }
15955
15956 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
15957 let Some(row) = snapshot
15958 .buffer_snapshot()
15959 .buffer_line_for_row(MultiBufferRow(point.row))
15960 .map(|(_, range)| range.start.row)
15961 else {
15962 continue;
15963 };
15964
15965 let context_range =
15966 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15967 runnable_rows.push((
15968 (runnable.buffer_id, row),
15969 RunnableTasks {
15970 templates: tasks,
15971 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
15972 context_range,
15973 column: point.column,
15974 extra_variables: runnable.extra_captures,
15975 },
15976 ));
15977 }
15978 runnable_rows
15979 })
15980 }
15981
15982 fn templates_with_tags(
15983 project: &Entity<Project>,
15984 runnable: &mut Runnable,
15985 cx: &mut App,
15986 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15987 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15988 let (worktree_id, file) = project
15989 .buffer_for_id(runnable.buffer, cx)
15990 .and_then(|buffer| buffer.read(cx).file())
15991 .map(|file| (file.worktree_id(cx), file.clone()))
15992 .unzip();
15993
15994 (
15995 project.task_store().read(cx).task_inventory().cloned(),
15996 worktree_id,
15997 file,
15998 )
15999 });
16000
16001 let tags = mem::take(&mut runnable.tags);
16002 let language = runnable.language.clone();
16003 cx.spawn(async move |cx| {
16004 let mut templates_with_tags = Vec::new();
16005 if let Some(inventory) = inventory {
16006 for RunnableTag(tag) in tags {
16007 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16008 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16009 }) else {
16010 return templates_with_tags;
16011 };
16012 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16013 move |(_, template)| {
16014 template.tags.iter().any(|source_tag| source_tag == &tag)
16015 },
16016 ));
16017 }
16018 }
16019 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16020
16021 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16022 // Strongest source wins; if we have worktree tag binding, prefer that to
16023 // global and language bindings;
16024 // if we have a global binding, prefer that to language binding.
16025 let first_mismatch = templates_with_tags
16026 .iter()
16027 .position(|(tag_source, _)| tag_source != leading_tag_source);
16028 if let Some(index) = first_mismatch {
16029 templates_with_tags.truncate(index);
16030 }
16031 }
16032
16033 templates_with_tags
16034 })
16035 }
16036
16037 pub fn move_to_enclosing_bracket(
16038 &mut self,
16039 _: &MoveToEnclosingBracket,
16040 window: &mut Window,
16041 cx: &mut Context<Self>,
16042 ) {
16043 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16044 self.change_selections(Default::default(), window, cx, |s| {
16045 s.move_offsets_with(|snapshot, selection| {
16046 let Some(enclosing_bracket_ranges) =
16047 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16048 else {
16049 return;
16050 };
16051
16052 let mut best_length = usize::MAX;
16053 let mut best_inside = false;
16054 let mut best_in_bracket_range = false;
16055 let mut best_destination = None;
16056 for (open, close) in enclosing_bracket_ranges {
16057 let close = close.to_inclusive();
16058 let length = *close.end() - open.start;
16059 let inside = selection.start >= open.end && selection.end <= *close.start();
16060 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16061 || close.contains(&selection.head());
16062
16063 // If best is next to a bracket and current isn't, skip
16064 if !in_bracket_range && best_in_bracket_range {
16065 continue;
16066 }
16067
16068 // Prefer smaller lengths unless best is inside and current isn't
16069 if length > best_length && (best_inside || !inside) {
16070 continue;
16071 }
16072
16073 best_length = length;
16074 best_inside = inside;
16075 best_in_bracket_range = in_bracket_range;
16076 best_destination = Some(
16077 if close.contains(&selection.start) && close.contains(&selection.end) {
16078 if inside { open.end } else { open.start }
16079 } else if inside {
16080 *close.start()
16081 } else {
16082 *close.end()
16083 },
16084 );
16085 }
16086
16087 if let Some(destination) = best_destination {
16088 selection.collapse_to(destination, SelectionGoal::None);
16089 }
16090 })
16091 });
16092 }
16093
16094 pub fn undo_selection(
16095 &mut self,
16096 _: &UndoSelection,
16097 window: &mut Window,
16098 cx: &mut Context<Self>,
16099 ) {
16100 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16101 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16102 self.selection_history.mode = SelectionHistoryMode::Undoing;
16103 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16104 this.end_selection(window, cx);
16105 this.change_selections(
16106 SelectionEffects::scroll(Autoscroll::newest()),
16107 window,
16108 cx,
16109 |s| s.select_anchors(entry.selections.to_vec()),
16110 );
16111 });
16112 self.selection_history.mode = SelectionHistoryMode::Normal;
16113
16114 self.select_next_state = entry.select_next_state;
16115 self.select_prev_state = entry.select_prev_state;
16116 self.add_selections_state = entry.add_selections_state;
16117 }
16118 }
16119
16120 pub fn redo_selection(
16121 &mut self,
16122 _: &RedoSelection,
16123 window: &mut Window,
16124 cx: &mut Context<Self>,
16125 ) {
16126 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16127 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16128 self.selection_history.mode = SelectionHistoryMode::Redoing;
16129 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16130 this.end_selection(window, cx);
16131 this.change_selections(
16132 SelectionEffects::scroll(Autoscroll::newest()),
16133 window,
16134 cx,
16135 |s| s.select_anchors(entry.selections.to_vec()),
16136 );
16137 });
16138 self.selection_history.mode = SelectionHistoryMode::Normal;
16139
16140 self.select_next_state = entry.select_next_state;
16141 self.select_prev_state = entry.select_prev_state;
16142 self.add_selections_state = entry.add_selections_state;
16143 }
16144 }
16145
16146 pub fn expand_excerpts(
16147 &mut self,
16148 action: &ExpandExcerpts,
16149 _: &mut Window,
16150 cx: &mut Context<Self>,
16151 ) {
16152 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16153 }
16154
16155 pub fn expand_excerpts_down(
16156 &mut self,
16157 action: &ExpandExcerptsDown,
16158 _: &mut Window,
16159 cx: &mut Context<Self>,
16160 ) {
16161 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16162 }
16163
16164 pub fn expand_excerpts_up(
16165 &mut self,
16166 action: &ExpandExcerptsUp,
16167 _: &mut Window,
16168 cx: &mut Context<Self>,
16169 ) {
16170 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16171 }
16172
16173 pub fn expand_excerpts_for_direction(
16174 &mut self,
16175 lines: u32,
16176 direction: ExpandExcerptDirection,
16177
16178 cx: &mut Context<Self>,
16179 ) {
16180 let selections = self.selections.disjoint_anchors_arc();
16181
16182 let lines = if lines == 0 {
16183 EditorSettings::get_global(cx).expand_excerpt_lines
16184 } else {
16185 lines
16186 };
16187
16188 self.buffer.update(cx, |buffer, cx| {
16189 let snapshot = buffer.snapshot(cx);
16190 let mut excerpt_ids = selections
16191 .iter()
16192 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16193 .collect::<Vec<_>>();
16194 excerpt_ids.sort();
16195 excerpt_ids.dedup();
16196 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16197 })
16198 }
16199
16200 pub fn expand_excerpt(
16201 &mut self,
16202 excerpt: ExcerptId,
16203 direction: ExpandExcerptDirection,
16204 window: &mut Window,
16205 cx: &mut Context<Self>,
16206 ) {
16207 let current_scroll_position = self.scroll_position(cx);
16208 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16209 let mut scroll = None;
16210
16211 if direction == ExpandExcerptDirection::Down {
16212 let multi_buffer = self.buffer.read(cx);
16213 let snapshot = multi_buffer.snapshot(cx);
16214 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16215 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16216 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16217 {
16218 let buffer_snapshot = buffer.read(cx).snapshot();
16219 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16220 let last_row = buffer_snapshot.max_point().row;
16221 let lines_below = last_row.saturating_sub(excerpt_end_row);
16222 if lines_below >= lines_to_expand {
16223 scroll = Some(
16224 current_scroll_position
16225 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16226 );
16227 }
16228 }
16229 }
16230 if direction == ExpandExcerptDirection::Up
16231 && self
16232 .buffer
16233 .read(cx)
16234 .snapshot(cx)
16235 .excerpt_before(excerpt)
16236 .is_none()
16237 {
16238 scroll = Some(current_scroll_position);
16239 }
16240
16241 self.buffer.update(cx, |buffer, cx| {
16242 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16243 });
16244
16245 if let Some(new_scroll_position) = scroll {
16246 self.set_scroll_position(new_scroll_position, window, cx);
16247 }
16248 }
16249
16250 pub fn go_to_singleton_buffer_point(
16251 &mut self,
16252 point: Point,
16253 window: &mut Window,
16254 cx: &mut Context<Self>,
16255 ) {
16256 self.go_to_singleton_buffer_range(point..point, window, cx);
16257 }
16258
16259 pub fn go_to_singleton_buffer_range(
16260 &mut self,
16261 range: Range<Point>,
16262 window: &mut Window,
16263 cx: &mut Context<Self>,
16264 ) {
16265 let multibuffer = self.buffer().read(cx);
16266 let Some(buffer) = multibuffer.as_singleton() else {
16267 return;
16268 };
16269 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16270 return;
16271 };
16272 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16273 return;
16274 };
16275 self.change_selections(
16276 SelectionEffects::default().nav_history(true),
16277 window,
16278 cx,
16279 |s| s.select_anchor_ranges([start..end]),
16280 );
16281 }
16282
16283 pub fn go_to_diagnostic(
16284 &mut self,
16285 action: &GoToDiagnostic,
16286 window: &mut Window,
16287 cx: &mut Context<Self>,
16288 ) {
16289 if !self.diagnostics_enabled() {
16290 return;
16291 }
16292 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16293 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16294 }
16295
16296 pub fn go_to_prev_diagnostic(
16297 &mut self,
16298 action: &GoToPreviousDiagnostic,
16299 window: &mut Window,
16300 cx: &mut Context<Self>,
16301 ) {
16302 if !self.diagnostics_enabled() {
16303 return;
16304 }
16305 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16306 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16307 }
16308
16309 pub fn go_to_diagnostic_impl(
16310 &mut self,
16311 direction: Direction,
16312 severity: GoToDiagnosticSeverityFilter,
16313 window: &mut Window,
16314 cx: &mut Context<Self>,
16315 ) {
16316 let buffer = self.buffer.read(cx).snapshot(cx);
16317 let selection = self
16318 .selections
16319 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16320
16321 let mut active_group_id = None;
16322 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16323 && active_group.active_range.start.to_offset(&buffer) == selection.start
16324 {
16325 active_group_id = Some(active_group.group_id);
16326 }
16327
16328 fn filtered<'a>(
16329 severity: GoToDiagnosticSeverityFilter,
16330 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16331 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16332 diagnostics
16333 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16334 .filter(|entry| entry.range.start != entry.range.end)
16335 .filter(|entry| !entry.diagnostic.is_unnecessary)
16336 }
16337
16338 let before = filtered(
16339 severity,
16340 buffer
16341 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16342 .filter(|entry| entry.range.start <= selection.start),
16343 );
16344 let after = filtered(
16345 severity,
16346 buffer
16347 .diagnostics_in_range(selection.start..buffer.len())
16348 .filter(|entry| entry.range.start >= selection.start),
16349 );
16350
16351 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16352 if direction == Direction::Prev {
16353 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16354 {
16355 for diagnostic in prev_diagnostics.into_iter().rev() {
16356 if diagnostic.range.start != selection.start
16357 || active_group_id
16358 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16359 {
16360 found = Some(diagnostic);
16361 break 'outer;
16362 }
16363 }
16364 }
16365 } else {
16366 for diagnostic in after.chain(before) {
16367 if diagnostic.range.start != selection.start
16368 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16369 {
16370 found = Some(diagnostic);
16371 break;
16372 }
16373 }
16374 }
16375 let Some(next_diagnostic) = found else {
16376 return;
16377 };
16378
16379 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16380 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16381 return;
16382 };
16383 let snapshot = self.snapshot(window, cx);
16384 if snapshot.intersects_fold(next_diagnostic.range.start) {
16385 self.unfold_ranges(
16386 std::slice::from_ref(&next_diagnostic.range),
16387 true,
16388 false,
16389 cx,
16390 );
16391 }
16392 self.change_selections(Default::default(), window, cx, |s| {
16393 s.select_ranges(vec![
16394 next_diagnostic.range.start..next_diagnostic.range.start,
16395 ])
16396 });
16397 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16398 self.refresh_edit_prediction(false, true, window, cx);
16399 }
16400
16401 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16402 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16403 let snapshot = self.snapshot(window, cx);
16404 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16405 self.go_to_hunk_before_or_after_position(
16406 &snapshot,
16407 selection.head(),
16408 Direction::Next,
16409 window,
16410 cx,
16411 );
16412 }
16413
16414 pub fn go_to_hunk_before_or_after_position(
16415 &mut self,
16416 snapshot: &EditorSnapshot,
16417 position: Point,
16418 direction: Direction,
16419 window: &mut Window,
16420 cx: &mut Context<Editor>,
16421 ) {
16422 let row = if direction == Direction::Next {
16423 self.hunk_after_position(snapshot, position)
16424 .map(|hunk| hunk.row_range.start)
16425 } else {
16426 self.hunk_before_position(snapshot, position)
16427 };
16428
16429 if let Some(row) = row {
16430 let destination = Point::new(row.0, 0);
16431 let autoscroll = Autoscroll::center();
16432
16433 self.unfold_ranges(&[destination..destination], false, false, cx);
16434 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16435 s.select_ranges([destination..destination]);
16436 });
16437 }
16438 }
16439
16440 fn hunk_after_position(
16441 &mut self,
16442 snapshot: &EditorSnapshot,
16443 position: Point,
16444 ) -> Option<MultiBufferDiffHunk> {
16445 snapshot
16446 .buffer_snapshot()
16447 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16448 .find(|hunk| hunk.row_range.start.0 > position.row)
16449 .or_else(|| {
16450 snapshot
16451 .buffer_snapshot()
16452 .diff_hunks_in_range(Point::zero()..position)
16453 .find(|hunk| hunk.row_range.end.0 < position.row)
16454 })
16455 }
16456
16457 fn go_to_prev_hunk(
16458 &mut self,
16459 _: &GoToPreviousHunk,
16460 window: &mut Window,
16461 cx: &mut Context<Self>,
16462 ) {
16463 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16464 let snapshot = self.snapshot(window, cx);
16465 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16466 self.go_to_hunk_before_or_after_position(
16467 &snapshot,
16468 selection.head(),
16469 Direction::Prev,
16470 window,
16471 cx,
16472 );
16473 }
16474
16475 fn hunk_before_position(
16476 &mut self,
16477 snapshot: &EditorSnapshot,
16478 position: Point,
16479 ) -> Option<MultiBufferRow> {
16480 snapshot
16481 .buffer_snapshot()
16482 .diff_hunk_before(position)
16483 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16484 }
16485
16486 fn go_to_next_change(
16487 &mut self,
16488 _: &GoToNextChange,
16489 window: &mut Window,
16490 cx: &mut Context<Self>,
16491 ) {
16492 if let Some(selections) = self
16493 .change_list
16494 .next_change(1, Direction::Next)
16495 .map(|s| s.to_vec())
16496 {
16497 self.change_selections(Default::default(), window, cx, |s| {
16498 let map = s.display_snapshot();
16499 s.select_display_ranges(selections.iter().map(|a| {
16500 let point = a.to_display_point(&map);
16501 point..point
16502 }))
16503 })
16504 }
16505 }
16506
16507 fn go_to_previous_change(
16508 &mut self,
16509 _: &GoToPreviousChange,
16510 window: &mut Window,
16511 cx: &mut Context<Self>,
16512 ) {
16513 if let Some(selections) = self
16514 .change_list
16515 .next_change(1, Direction::Prev)
16516 .map(|s| s.to_vec())
16517 {
16518 self.change_selections(Default::default(), window, cx, |s| {
16519 let map = s.display_snapshot();
16520 s.select_display_ranges(selections.iter().map(|a| {
16521 let point = a.to_display_point(&map);
16522 point..point
16523 }))
16524 })
16525 }
16526 }
16527
16528 pub fn go_to_next_document_highlight(
16529 &mut self,
16530 _: &GoToNextDocumentHighlight,
16531 window: &mut Window,
16532 cx: &mut Context<Self>,
16533 ) {
16534 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16535 }
16536
16537 pub fn go_to_prev_document_highlight(
16538 &mut self,
16539 _: &GoToPreviousDocumentHighlight,
16540 window: &mut Window,
16541 cx: &mut Context<Self>,
16542 ) {
16543 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16544 }
16545
16546 pub fn go_to_document_highlight_before_or_after_position(
16547 &mut self,
16548 direction: Direction,
16549 window: &mut Window,
16550 cx: &mut Context<Editor>,
16551 ) {
16552 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16553 let snapshot = self.snapshot(window, cx);
16554 let buffer = &snapshot.buffer_snapshot();
16555 let position = self
16556 .selections
16557 .newest::<Point>(&snapshot.display_snapshot)
16558 .head();
16559 let anchor_position = buffer.anchor_after(position);
16560
16561 // Get all document highlights (both read and write)
16562 let mut all_highlights = Vec::new();
16563
16564 if let Some((_, read_highlights)) = self
16565 .background_highlights
16566 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16567 {
16568 all_highlights.extend(read_highlights.iter());
16569 }
16570
16571 if let Some((_, write_highlights)) = self
16572 .background_highlights
16573 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16574 {
16575 all_highlights.extend(write_highlights.iter());
16576 }
16577
16578 if all_highlights.is_empty() {
16579 return;
16580 }
16581
16582 // Sort highlights by position
16583 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16584
16585 let target_highlight = match direction {
16586 Direction::Next => {
16587 // Find the first highlight after the current position
16588 all_highlights
16589 .iter()
16590 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16591 }
16592 Direction::Prev => {
16593 // Find the last highlight before the current position
16594 all_highlights
16595 .iter()
16596 .rev()
16597 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16598 }
16599 };
16600
16601 if let Some(highlight) = target_highlight {
16602 let destination = highlight.start.to_point(buffer);
16603 let autoscroll = Autoscroll::center();
16604
16605 self.unfold_ranges(&[destination..destination], false, false, cx);
16606 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16607 s.select_ranges([destination..destination]);
16608 });
16609 }
16610 }
16611
16612 fn go_to_line<T: 'static>(
16613 &mut self,
16614 position: Anchor,
16615 highlight_color: Option<Hsla>,
16616 window: &mut Window,
16617 cx: &mut Context<Self>,
16618 ) {
16619 let snapshot = self.snapshot(window, cx).display_snapshot;
16620 let position = position.to_point(&snapshot.buffer_snapshot());
16621 let start = snapshot
16622 .buffer_snapshot()
16623 .clip_point(Point::new(position.row, 0), Bias::Left);
16624 let end = start + Point::new(1, 0);
16625 let start = snapshot.buffer_snapshot().anchor_before(start);
16626 let end = snapshot.buffer_snapshot().anchor_before(end);
16627
16628 self.highlight_rows::<T>(
16629 start..end,
16630 highlight_color
16631 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16632 Default::default(),
16633 cx,
16634 );
16635
16636 if self.buffer.read(cx).is_singleton() {
16637 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16638 }
16639 }
16640
16641 pub fn go_to_definition(
16642 &mut self,
16643 _: &GoToDefinition,
16644 window: &mut Window,
16645 cx: &mut Context<Self>,
16646 ) -> Task<Result<Navigated>> {
16647 let definition =
16648 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16649 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16650 cx.spawn_in(window, async move |editor, cx| {
16651 if definition.await? == Navigated::Yes {
16652 return Ok(Navigated::Yes);
16653 }
16654 match fallback_strategy {
16655 GoToDefinitionFallback::None => Ok(Navigated::No),
16656 GoToDefinitionFallback::FindAllReferences => {
16657 match editor.update_in(cx, |editor, window, cx| {
16658 editor.find_all_references(&FindAllReferences, window, cx)
16659 })? {
16660 Some(references) => references.await,
16661 None => Ok(Navigated::No),
16662 }
16663 }
16664 }
16665 })
16666 }
16667
16668 pub fn go_to_declaration(
16669 &mut self,
16670 _: &GoToDeclaration,
16671 window: &mut Window,
16672 cx: &mut Context<Self>,
16673 ) -> Task<Result<Navigated>> {
16674 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16675 }
16676
16677 pub fn go_to_declaration_split(
16678 &mut self,
16679 _: &GoToDeclaration,
16680 window: &mut Window,
16681 cx: &mut Context<Self>,
16682 ) -> Task<Result<Navigated>> {
16683 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16684 }
16685
16686 pub fn go_to_implementation(
16687 &mut self,
16688 _: &GoToImplementation,
16689 window: &mut Window,
16690 cx: &mut Context<Self>,
16691 ) -> Task<Result<Navigated>> {
16692 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16693 }
16694
16695 pub fn go_to_implementation_split(
16696 &mut self,
16697 _: &GoToImplementationSplit,
16698 window: &mut Window,
16699 cx: &mut Context<Self>,
16700 ) -> Task<Result<Navigated>> {
16701 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16702 }
16703
16704 pub fn go_to_type_definition(
16705 &mut self,
16706 _: &GoToTypeDefinition,
16707 window: &mut Window,
16708 cx: &mut Context<Self>,
16709 ) -> Task<Result<Navigated>> {
16710 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16711 }
16712
16713 pub fn go_to_definition_split(
16714 &mut self,
16715 _: &GoToDefinitionSplit,
16716 window: &mut Window,
16717 cx: &mut Context<Self>,
16718 ) -> Task<Result<Navigated>> {
16719 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16720 }
16721
16722 pub fn go_to_type_definition_split(
16723 &mut self,
16724 _: &GoToTypeDefinitionSplit,
16725 window: &mut Window,
16726 cx: &mut Context<Self>,
16727 ) -> Task<Result<Navigated>> {
16728 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16729 }
16730
16731 fn go_to_definition_of_kind(
16732 &mut self,
16733 kind: GotoDefinitionKind,
16734 split: bool,
16735 window: &mut Window,
16736 cx: &mut Context<Self>,
16737 ) -> Task<Result<Navigated>> {
16738 let Some(provider) = self.semantics_provider.clone() else {
16739 return Task::ready(Ok(Navigated::No));
16740 };
16741 let head = self
16742 .selections
16743 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
16744 .head();
16745 let buffer = self.buffer.read(cx);
16746 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16747 return Task::ready(Ok(Navigated::No));
16748 };
16749 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16750 return Task::ready(Ok(Navigated::No));
16751 };
16752
16753 cx.spawn_in(window, async move |editor, cx| {
16754 let Some(definitions) = definitions.await? else {
16755 return Ok(Navigated::No);
16756 };
16757 let navigated = editor
16758 .update_in(cx, |editor, window, cx| {
16759 editor.navigate_to_hover_links(
16760 Some(kind),
16761 definitions
16762 .into_iter()
16763 .filter(|location| {
16764 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16765 })
16766 .map(HoverLink::Text)
16767 .collect::<Vec<_>>(),
16768 split,
16769 window,
16770 cx,
16771 )
16772 })?
16773 .await?;
16774 anyhow::Ok(navigated)
16775 })
16776 }
16777
16778 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16779 let selection = self.selections.newest_anchor();
16780 let head = selection.head();
16781 let tail = selection.tail();
16782
16783 let Some((buffer, start_position)) =
16784 self.buffer.read(cx).text_anchor_for_position(head, cx)
16785 else {
16786 return;
16787 };
16788
16789 let end_position = if head != tail {
16790 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16791 return;
16792 };
16793 Some(pos)
16794 } else {
16795 None
16796 };
16797
16798 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16799 let url = if let Some(end_pos) = end_position {
16800 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16801 } else {
16802 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16803 };
16804
16805 if let Some(url) = url {
16806 cx.update(|window, cx| {
16807 if parse_zed_link(&url, cx).is_some() {
16808 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16809 } else {
16810 cx.open_url(&url);
16811 }
16812 })?;
16813 }
16814
16815 anyhow::Ok(())
16816 });
16817
16818 url_finder.detach();
16819 }
16820
16821 pub fn open_selected_filename(
16822 &mut self,
16823 _: &OpenSelectedFilename,
16824 window: &mut Window,
16825 cx: &mut Context<Self>,
16826 ) {
16827 let Some(workspace) = self.workspace() else {
16828 return;
16829 };
16830
16831 let position = self.selections.newest_anchor().head();
16832
16833 let Some((buffer, buffer_position)) =
16834 self.buffer.read(cx).text_anchor_for_position(position, cx)
16835 else {
16836 return;
16837 };
16838
16839 let project = self.project.clone();
16840
16841 cx.spawn_in(window, async move |_, cx| {
16842 let result = find_file(&buffer, project, buffer_position, cx).await;
16843
16844 if let Some((_, path)) = result {
16845 workspace
16846 .update_in(cx, |workspace, window, cx| {
16847 workspace.open_resolved_path(path, window, cx)
16848 })?
16849 .await?;
16850 }
16851 anyhow::Ok(())
16852 })
16853 .detach();
16854 }
16855
16856 pub(crate) fn navigate_to_hover_links(
16857 &mut self,
16858 kind: Option<GotoDefinitionKind>,
16859 definitions: Vec<HoverLink>,
16860 split: bool,
16861 window: &mut Window,
16862 cx: &mut Context<Editor>,
16863 ) -> Task<Result<Navigated>> {
16864 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16865 let mut first_url_or_file = None;
16866 let definitions: Vec<_> = definitions
16867 .into_iter()
16868 .filter_map(|def| match def {
16869 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16870 HoverLink::InlayHint(lsp_location, server_id) => {
16871 let computation =
16872 self.compute_target_location(lsp_location, server_id, window, cx);
16873 Some(cx.background_spawn(computation))
16874 }
16875 HoverLink::Url(url) => {
16876 first_url_or_file = Some(Either::Left(url));
16877 None
16878 }
16879 HoverLink::File(path) => {
16880 first_url_or_file = Some(Either::Right(path));
16881 None
16882 }
16883 })
16884 .collect();
16885
16886 let workspace = self.workspace();
16887
16888 cx.spawn_in(window, async move |editor, cx| {
16889 let locations: Vec<Location> = future::join_all(definitions)
16890 .await
16891 .into_iter()
16892 .filter_map(|location| location.transpose())
16893 .collect::<Result<_>>()
16894 .context("location tasks")?;
16895 let mut locations = cx.update(|_, cx| {
16896 locations
16897 .into_iter()
16898 .map(|location| {
16899 let buffer = location.buffer.read(cx);
16900 (location.buffer, location.range.to_point(buffer))
16901 })
16902 .into_group_map()
16903 })?;
16904 let mut num_locations = 0;
16905 for ranges in locations.values_mut() {
16906 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16907 ranges.dedup();
16908 num_locations += ranges.len();
16909 }
16910
16911 if num_locations > 1 {
16912 let Some(workspace) = workspace else {
16913 return Ok(Navigated::No);
16914 };
16915
16916 let tab_kind = match kind {
16917 Some(GotoDefinitionKind::Implementation) => "Implementations",
16918 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16919 Some(GotoDefinitionKind::Declaration) => "Declarations",
16920 Some(GotoDefinitionKind::Type) => "Types",
16921 };
16922 let title = editor
16923 .update_in(cx, |_, _, cx| {
16924 let target = locations
16925 .iter()
16926 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16927 .map(|(buffer, location)| {
16928 buffer
16929 .read(cx)
16930 .text_for_range(location.clone())
16931 .collect::<String>()
16932 })
16933 .filter(|text| !text.contains('\n'))
16934 .unique()
16935 .take(3)
16936 .join(", ");
16937 if target.is_empty() {
16938 tab_kind.to_owned()
16939 } else {
16940 format!("{tab_kind} for {target}")
16941 }
16942 })
16943 .context("buffer title")?;
16944
16945 let opened = workspace
16946 .update_in(cx, |workspace, window, cx| {
16947 Self::open_locations_in_multibuffer(
16948 workspace,
16949 locations,
16950 title,
16951 split,
16952 MultibufferSelectionMode::First,
16953 window,
16954 cx,
16955 )
16956 })
16957 .is_ok();
16958
16959 anyhow::Ok(Navigated::from_bool(opened))
16960 } else if num_locations == 0 {
16961 // If there is one url or file, open it directly
16962 match first_url_or_file {
16963 Some(Either::Left(url)) => {
16964 cx.update(|_, cx| cx.open_url(&url))?;
16965 Ok(Navigated::Yes)
16966 }
16967 Some(Either::Right(path)) => {
16968 let Some(workspace) = workspace else {
16969 return Ok(Navigated::No);
16970 };
16971
16972 workspace
16973 .update_in(cx, |workspace, window, cx| {
16974 workspace.open_resolved_path(path, window, cx)
16975 })?
16976 .await?;
16977 Ok(Navigated::Yes)
16978 }
16979 None => Ok(Navigated::No),
16980 }
16981 } else {
16982 let Some(workspace) = workspace else {
16983 return Ok(Navigated::No);
16984 };
16985
16986 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16987 let target_range = target_ranges.first().unwrap().clone();
16988
16989 editor.update_in(cx, |editor, window, cx| {
16990 let range = target_range.to_point(target_buffer.read(cx));
16991 let range = editor.range_for_match(&range);
16992 let range = collapse_multiline_range(range);
16993
16994 if !split
16995 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16996 {
16997 editor.go_to_singleton_buffer_range(range, window, cx);
16998 } else {
16999 let pane = workspace.read(cx).active_pane().clone();
17000 window.defer(cx, move |window, cx| {
17001 let target_editor: Entity<Self> =
17002 workspace.update(cx, |workspace, cx| {
17003 let pane = if split {
17004 workspace.adjacent_pane(window, cx)
17005 } else {
17006 workspace.active_pane().clone()
17007 };
17008
17009 workspace.open_project_item(
17010 pane,
17011 target_buffer.clone(),
17012 true,
17013 true,
17014 window,
17015 cx,
17016 )
17017 });
17018 target_editor.update(cx, |target_editor, cx| {
17019 // When selecting a definition in a different buffer, disable the nav history
17020 // to avoid creating a history entry at the previous cursor location.
17021 pane.update(cx, |pane, _| pane.disable_history());
17022 target_editor.go_to_singleton_buffer_range(range, window, cx);
17023 pane.update(cx, |pane, _| pane.enable_history());
17024 });
17025 });
17026 }
17027 Navigated::Yes
17028 })
17029 }
17030 })
17031 }
17032
17033 fn compute_target_location(
17034 &self,
17035 lsp_location: lsp::Location,
17036 server_id: LanguageServerId,
17037 window: &mut Window,
17038 cx: &mut Context<Self>,
17039 ) -> Task<anyhow::Result<Option<Location>>> {
17040 let Some(project) = self.project.clone() else {
17041 return Task::ready(Ok(None));
17042 };
17043
17044 cx.spawn_in(window, async move |editor, cx| {
17045 let location_task = editor.update(cx, |_, cx| {
17046 project.update(cx, |project, cx| {
17047 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17048 })
17049 })?;
17050 let location = Some({
17051 let target_buffer_handle = location_task.await.context("open local buffer")?;
17052 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17053 let target_start = target_buffer
17054 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17055 let target_end = target_buffer
17056 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17057 target_buffer.anchor_after(target_start)
17058 ..target_buffer.anchor_before(target_end)
17059 })?;
17060 Location {
17061 buffer: target_buffer_handle,
17062 range,
17063 }
17064 });
17065 Ok(location)
17066 })
17067 }
17068
17069 fn go_to_next_reference(
17070 &mut self,
17071 _: &GoToNextReference,
17072 window: &mut Window,
17073 cx: &mut Context<Self>,
17074 ) {
17075 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17076 if let Some(task) = task {
17077 task.detach();
17078 };
17079 }
17080
17081 fn go_to_prev_reference(
17082 &mut self,
17083 _: &GoToPreviousReference,
17084 window: &mut Window,
17085 cx: &mut Context<Self>,
17086 ) {
17087 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17088 if let Some(task) = task {
17089 task.detach();
17090 };
17091 }
17092
17093 pub fn go_to_reference_before_or_after_position(
17094 &mut self,
17095 direction: Direction,
17096 count: usize,
17097 window: &mut Window,
17098 cx: &mut Context<Self>,
17099 ) -> Option<Task<Result<()>>> {
17100 let selection = self.selections.newest_anchor();
17101 let head = selection.head();
17102
17103 let multi_buffer = self.buffer.read(cx);
17104
17105 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17106 let workspace = self.workspace()?;
17107 let project = workspace.read(cx).project().clone();
17108 let references =
17109 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17110 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17111 let Some(locations) = references.await? else {
17112 return Ok(());
17113 };
17114
17115 if locations.is_empty() {
17116 // totally normal - the cursor may be on something which is not
17117 // a symbol (e.g. a keyword)
17118 log::info!("no references found under cursor");
17119 return Ok(());
17120 }
17121
17122 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17123
17124 let multi_buffer_snapshot =
17125 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
17126
17127 let (locations, current_location_index) =
17128 multi_buffer.update(cx, |multi_buffer, cx| {
17129 let mut locations = locations
17130 .into_iter()
17131 .filter_map(|loc| {
17132 let start = multi_buffer.buffer_anchor_to_anchor(
17133 &loc.buffer,
17134 loc.range.start,
17135 cx,
17136 )?;
17137 let end = multi_buffer.buffer_anchor_to_anchor(
17138 &loc.buffer,
17139 loc.range.end,
17140 cx,
17141 )?;
17142 Some(start..end)
17143 })
17144 .collect::<Vec<_>>();
17145
17146 // There is an O(n) implementation, but given this list will be
17147 // small (usually <100 items), the extra O(log(n)) factor isn't
17148 // worth the (surprisingly large amount of) extra complexity.
17149 locations
17150 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17151
17152 let head_offset = head.to_offset(&multi_buffer_snapshot);
17153
17154 let current_location_index = locations.iter().position(|loc| {
17155 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17156 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17157 });
17158
17159 (locations, current_location_index)
17160 })?;
17161
17162 let Some(current_location_index) = current_location_index else {
17163 // This indicates something has gone wrong, because we already
17164 // handle the "no references" case above
17165 log::error!(
17166 "failed to find current reference under cursor. Total references: {}",
17167 locations.len()
17168 );
17169 return Ok(());
17170 };
17171
17172 let destination_location_index = match direction {
17173 Direction::Next => (current_location_index + count) % locations.len(),
17174 Direction::Prev => {
17175 (current_location_index + locations.len() - count % locations.len())
17176 % locations.len()
17177 }
17178 };
17179
17180 // TODO(cameron): is this needed?
17181 // the thinking is to avoid "jumping to the current location" (avoid
17182 // polluting "jumplist" in vim terms)
17183 if current_location_index == destination_location_index {
17184 return Ok(());
17185 }
17186
17187 let Range { start, end } = locations[destination_location_index];
17188
17189 editor.update_in(cx, |editor, window, cx| {
17190 let effects = SelectionEffects::default();
17191
17192 editor.unfold_ranges(&[start..end], false, false, cx);
17193 editor.change_selections(effects, window, cx, |s| {
17194 s.select_ranges([start..start]);
17195 });
17196 })?;
17197
17198 Ok(())
17199 }))
17200 }
17201
17202 pub fn find_all_references(
17203 &mut self,
17204 _: &FindAllReferences,
17205 window: &mut Window,
17206 cx: &mut Context<Self>,
17207 ) -> Option<Task<Result<Navigated>>> {
17208 let selection = self
17209 .selections
17210 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17211 let multi_buffer = self.buffer.read(cx);
17212 let head = selection.head();
17213
17214 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17215 let head_anchor = multi_buffer_snapshot.anchor_at(
17216 head,
17217 if head < selection.tail() {
17218 Bias::Right
17219 } else {
17220 Bias::Left
17221 },
17222 );
17223
17224 match self
17225 .find_all_references_task_sources
17226 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17227 {
17228 Ok(_) => {
17229 log::info!(
17230 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17231 );
17232 return None;
17233 }
17234 Err(i) => {
17235 self.find_all_references_task_sources.insert(i, head_anchor);
17236 }
17237 }
17238
17239 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17240 let workspace = self.workspace()?;
17241 let project = workspace.read(cx).project().clone();
17242 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17243 Some(cx.spawn_in(window, async move |editor, cx| {
17244 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17245 if let Ok(i) = editor
17246 .find_all_references_task_sources
17247 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17248 {
17249 editor.find_all_references_task_sources.remove(i);
17250 }
17251 });
17252
17253 let Some(locations) = references.await? else {
17254 return anyhow::Ok(Navigated::No);
17255 };
17256 let mut locations = cx.update(|_, cx| {
17257 locations
17258 .into_iter()
17259 .map(|location| {
17260 let buffer = location.buffer.read(cx);
17261 (location.buffer, location.range.to_point(buffer))
17262 })
17263 .into_group_map()
17264 })?;
17265 if locations.is_empty() {
17266 return anyhow::Ok(Navigated::No);
17267 }
17268 for ranges in locations.values_mut() {
17269 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17270 ranges.dedup();
17271 }
17272
17273 workspace.update_in(cx, |workspace, window, cx| {
17274 let target = locations
17275 .iter()
17276 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17277 .map(|(buffer, location)| {
17278 buffer
17279 .read(cx)
17280 .text_for_range(location.clone())
17281 .collect::<String>()
17282 })
17283 .filter(|text| !text.contains('\n'))
17284 .unique()
17285 .take(3)
17286 .join(", ");
17287 let title = if target.is_empty() {
17288 "References".to_owned()
17289 } else {
17290 format!("References to {target}")
17291 };
17292 Self::open_locations_in_multibuffer(
17293 workspace,
17294 locations,
17295 title,
17296 false,
17297 MultibufferSelectionMode::First,
17298 window,
17299 cx,
17300 );
17301 Navigated::Yes
17302 })
17303 }))
17304 }
17305
17306 /// Opens a multibuffer with the given project locations in it
17307 pub fn open_locations_in_multibuffer(
17308 workspace: &mut Workspace,
17309 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17310 title: String,
17311 split: bool,
17312 multibuffer_selection_mode: MultibufferSelectionMode,
17313 window: &mut Window,
17314 cx: &mut Context<Workspace>,
17315 ) {
17316 if locations.is_empty() {
17317 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17318 return;
17319 }
17320
17321 let capability = workspace.project().read(cx).capability();
17322 let mut ranges = <Vec<Range<Anchor>>>::new();
17323
17324 // a key to find existing multibuffer editors with the same set of locations
17325 // to prevent us from opening more and more multibuffer tabs for searches and the like
17326 let mut key = (title.clone(), vec![]);
17327 let excerpt_buffer = cx.new(|cx| {
17328 let key = &mut key.1;
17329 let mut multibuffer = MultiBuffer::new(capability);
17330 for (buffer, mut ranges_for_buffer) in locations {
17331 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17332 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17333 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17334 PathKey::for_buffer(&buffer, cx),
17335 buffer.clone(),
17336 ranges_for_buffer,
17337 multibuffer_context_lines(cx),
17338 cx,
17339 );
17340 ranges.extend(new_ranges)
17341 }
17342
17343 multibuffer.with_title(title)
17344 });
17345 let existing = workspace.active_pane().update(cx, |pane, cx| {
17346 pane.items()
17347 .filter_map(|item| item.downcast::<Editor>())
17348 .find(|editor| {
17349 editor
17350 .read(cx)
17351 .lookup_key
17352 .as_ref()
17353 .and_then(|it| {
17354 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17355 })
17356 .is_some_and(|it| *it == key)
17357 })
17358 });
17359 let editor = existing.unwrap_or_else(|| {
17360 cx.new(|cx| {
17361 let mut editor = Editor::for_multibuffer(
17362 excerpt_buffer,
17363 Some(workspace.project().clone()),
17364 window,
17365 cx,
17366 );
17367 editor.lookup_key = Some(Box::new(key));
17368 editor
17369 })
17370 });
17371 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17372 MultibufferSelectionMode::First => {
17373 if let Some(first_range) = ranges.first() {
17374 editor.change_selections(
17375 SelectionEffects::no_scroll(),
17376 window,
17377 cx,
17378 |selections| {
17379 selections.clear_disjoint();
17380 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17381 },
17382 );
17383 }
17384 editor.highlight_background::<Self>(
17385 &ranges,
17386 |theme| theme.colors().editor_highlighted_line_background,
17387 cx,
17388 );
17389 }
17390 MultibufferSelectionMode::All => {
17391 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17392 selections.clear_disjoint();
17393 selections.select_anchor_ranges(ranges);
17394 });
17395 }
17396 });
17397
17398 let item = Box::new(editor);
17399 let item_id = item.item_id();
17400
17401 if split {
17402 let pane = workspace.adjacent_pane(window, cx);
17403 workspace.add_item(pane, item, None, true, true, window, cx);
17404 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17405 let (preview_item_id, preview_item_idx) =
17406 workspace.active_pane().read_with(cx, |pane, _| {
17407 (pane.preview_item_id(), pane.preview_item_idx())
17408 });
17409
17410 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17411
17412 if let Some(preview_item_id) = preview_item_id {
17413 workspace.active_pane().update(cx, |pane, cx| {
17414 pane.remove_item(preview_item_id, false, false, window, cx);
17415 });
17416 }
17417 } else {
17418 workspace.add_item_to_active_pane(item, None, true, window, cx);
17419 }
17420 workspace.active_pane().update(cx, |pane, cx| {
17421 pane.set_preview_item_id(Some(item_id), cx);
17422 });
17423 }
17424
17425 pub fn rename(
17426 &mut self,
17427 _: &Rename,
17428 window: &mut Window,
17429 cx: &mut Context<Self>,
17430 ) -> Option<Task<Result<()>>> {
17431 use language::ToOffset as _;
17432
17433 let provider = self.semantics_provider.clone()?;
17434 let selection = self.selections.newest_anchor().clone();
17435 let (cursor_buffer, cursor_buffer_position) = self
17436 .buffer
17437 .read(cx)
17438 .text_anchor_for_position(selection.head(), cx)?;
17439 let (tail_buffer, cursor_buffer_position_end) = self
17440 .buffer
17441 .read(cx)
17442 .text_anchor_for_position(selection.tail(), cx)?;
17443 if tail_buffer != cursor_buffer {
17444 return None;
17445 }
17446
17447 let snapshot = cursor_buffer.read(cx).snapshot();
17448 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17449 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17450 let prepare_rename = provider
17451 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17452 .unwrap_or_else(|| Task::ready(Ok(None)));
17453 drop(snapshot);
17454
17455 Some(cx.spawn_in(window, async move |this, cx| {
17456 let rename_range = if let Some(range) = prepare_rename.await? {
17457 Some(range)
17458 } else {
17459 this.update(cx, |this, cx| {
17460 let buffer = this.buffer.read(cx).snapshot(cx);
17461 let mut buffer_highlights = this
17462 .document_highlights_for_position(selection.head(), &buffer)
17463 .filter(|highlight| {
17464 highlight.start.excerpt_id == selection.head().excerpt_id
17465 && highlight.end.excerpt_id == selection.head().excerpt_id
17466 });
17467 buffer_highlights
17468 .next()
17469 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17470 })?
17471 };
17472 if let Some(rename_range) = rename_range {
17473 this.update_in(cx, |this, window, cx| {
17474 let snapshot = cursor_buffer.read(cx).snapshot();
17475 let rename_buffer_range = rename_range.to_offset(&snapshot);
17476 let cursor_offset_in_rename_range =
17477 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17478 let cursor_offset_in_rename_range_end =
17479 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17480
17481 this.take_rename(false, window, cx);
17482 let buffer = this.buffer.read(cx).read(cx);
17483 let cursor_offset = selection.head().to_offset(&buffer);
17484 let rename_start =
17485 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
17486 let rename_end = rename_start + rename_buffer_range.len();
17487 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17488 let mut old_highlight_id = None;
17489 let old_name: Arc<str> = buffer
17490 .chunks(rename_start..rename_end, true)
17491 .map(|chunk| {
17492 if old_highlight_id.is_none() {
17493 old_highlight_id = chunk.syntax_highlight_id;
17494 }
17495 chunk.text
17496 })
17497 .collect::<String>()
17498 .into();
17499
17500 drop(buffer);
17501
17502 // Position the selection in the rename editor so that it matches the current selection.
17503 this.show_local_selections = false;
17504 let rename_editor = cx.new(|cx| {
17505 let mut editor = Editor::single_line(window, cx);
17506 editor.buffer.update(cx, |buffer, cx| {
17507 buffer.edit(
17508 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
17509 None,
17510 cx,
17511 )
17512 });
17513 let cursor_offset_in_rename_range =
17514 MultiBufferOffset(cursor_offset_in_rename_range);
17515 let cursor_offset_in_rename_range_end =
17516 MultiBufferOffset(cursor_offset_in_rename_range_end);
17517 let rename_selection_range = match cursor_offset_in_rename_range
17518 .cmp(&cursor_offset_in_rename_range_end)
17519 {
17520 Ordering::Equal => {
17521 editor.select_all(&SelectAll, window, cx);
17522 return editor;
17523 }
17524 Ordering::Less => {
17525 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17526 }
17527 Ordering::Greater => {
17528 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17529 }
17530 };
17531 if rename_selection_range.end.0 > old_name.len() {
17532 editor.select_all(&SelectAll, window, cx);
17533 } else {
17534 editor.change_selections(Default::default(), window, cx, |s| {
17535 s.select_ranges([rename_selection_range]);
17536 });
17537 }
17538 editor
17539 });
17540 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17541 if e == &EditorEvent::Focused {
17542 cx.emit(EditorEvent::FocusedIn)
17543 }
17544 })
17545 .detach();
17546
17547 let write_highlights =
17548 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17549 let read_highlights =
17550 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17551 let ranges = write_highlights
17552 .iter()
17553 .flat_map(|(_, ranges)| ranges.iter())
17554 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17555 .cloned()
17556 .collect();
17557
17558 this.highlight_text::<Rename>(
17559 ranges,
17560 HighlightStyle {
17561 fade_out: Some(0.6),
17562 ..Default::default()
17563 },
17564 cx,
17565 );
17566 let rename_focus_handle = rename_editor.focus_handle(cx);
17567 window.focus(&rename_focus_handle);
17568 let block_id = this.insert_blocks(
17569 [BlockProperties {
17570 style: BlockStyle::Flex,
17571 placement: BlockPlacement::Below(range.start),
17572 height: Some(1),
17573 render: Arc::new({
17574 let rename_editor = rename_editor.clone();
17575 move |cx: &mut BlockContext| {
17576 let mut text_style = cx.editor_style.text.clone();
17577 if let Some(highlight_style) = old_highlight_id
17578 .and_then(|h| h.style(&cx.editor_style.syntax))
17579 {
17580 text_style = text_style.highlight(highlight_style);
17581 }
17582 div()
17583 .block_mouse_except_scroll()
17584 .pl(cx.anchor_x)
17585 .child(EditorElement::new(
17586 &rename_editor,
17587 EditorStyle {
17588 background: cx.theme().system().transparent,
17589 local_player: cx.editor_style.local_player,
17590 text: text_style,
17591 scrollbar_width: cx.editor_style.scrollbar_width,
17592 syntax: cx.editor_style.syntax.clone(),
17593 status: cx.editor_style.status.clone(),
17594 inlay_hints_style: HighlightStyle {
17595 font_weight: Some(FontWeight::BOLD),
17596 ..make_inlay_hints_style(cx.app)
17597 },
17598 edit_prediction_styles: make_suggestion_styles(
17599 cx.app,
17600 ),
17601 ..EditorStyle::default()
17602 },
17603 ))
17604 .into_any_element()
17605 }
17606 }),
17607 priority: 0,
17608 }],
17609 Some(Autoscroll::fit()),
17610 cx,
17611 )[0];
17612 this.pending_rename = Some(RenameState {
17613 range,
17614 old_name,
17615 editor: rename_editor,
17616 block_id,
17617 });
17618 })?;
17619 }
17620
17621 Ok(())
17622 }))
17623 }
17624
17625 pub fn confirm_rename(
17626 &mut self,
17627 _: &ConfirmRename,
17628 window: &mut Window,
17629 cx: &mut Context<Self>,
17630 ) -> Option<Task<Result<()>>> {
17631 let rename = self.take_rename(false, window, cx)?;
17632 let workspace = self.workspace()?.downgrade();
17633 let (buffer, start) = self
17634 .buffer
17635 .read(cx)
17636 .text_anchor_for_position(rename.range.start, cx)?;
17637 let (end_buffer, _) = self
17638 .buffer
17639 .read(cx)
17640 .text_anchor_for_position(rename.range.end, cx)?;
17641 if buffer != end_buffer {
17642 return None;
17643 }
17644
17645 let old_name = rename.old_name;
17646 let new_name = rename.editor.read(cx).text(cx);
17647
17648 let rename = self.semantics_provider.as_ref()?.perform_rename(
17649 &buffer,
17650 start,
17651 new_name.clone(),
17652 cx,
17653 )?;
17654
17655 Some(cx.spawn_in(window, async move |editor, cx| {
17656 let project_transaction = rename.await?;
17657 Self::open_project_transaction(
17658 &editor,
17659 workspace,
17660 project_transaction,
17661 format!("Rename: {} → {}", old_name, new_name),
17662 cx,
17663 )
17664 .await?;
17665
17666 editor.update(cx, |editor, cx| {
17667 editor.refresh_document_highlights(cx);
17668 })?;
17669 Ok(())
17670 }))
17671 }
17672
17673 fn take_rename(
17674 &mut self,
17675 moving_cursor: bool,
17676 window: &mut Window,
17677 cx: &mut Context<Self>,
17678 ) -> Option<RenameState> {
17679 let rename = self.pending_rename.take()?;
17680 if rename.editor.focus_handle(cx).is_focused(window) {
17681 window.focus(&self.focus_handle);
17682 }
17683
17684 self.remove_blocks(
17685 [rename.block_id].into_iter().collect(),
17686 Some(Autoscroll::fit()),
17687 cx,
17688 );
17689 self.clear_highlights::<Rename>(cx);
17690 self.show_local_selections = true;
17691
17692 if moving_cursor {
17693 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17694 editor
17695 .selections
17696 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
17697 .head()
17698 });
17699
17700 // Update the selection to match the position of the selection inside
17701 // the rename editor.
17702 let snapshot = self.buffer.read(cx).read(cx);
17703 let rename_range = rename.range.to_offset(&snapshot);
17704 let cursor_in_editor = snapshot
17705 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17706 .min(rename_range.end);
17707 drop(snapshot);
17708
17709 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17710 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17711 });
17712 } else {
17713 self.refresh_document_highlights(cx);
17714 }
17715
17716 Some(rename)
17717 }
17718
17719 pub fn pending_rename(&self) -> Option<&RenameState> {
17720 self.pending_rename.as_ref()
17721 }
17722
17723 fn format(
17724 &mut self,
17725 _: &Format,
17726 window: &mut Window,
17727 cx: &mut Context<Self>,
17728 ) -> Option<Task<Result<()>>> {
17729 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17730
17731 let project = match &self.project {
17732 Some(project) => project.clone(),
17733 None => return None,
17734 };
17735
17736 Some(self.perform_format(
17737 project,
17738 FormatTrigger::Manual,
17739 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17740 window,
17741 cx,
17742 ))
17743 }
17744
17745 fn format_selections(
17746 &mut self,
17747 _: &FormatSelections,
17748 window: &mut Window,
17749 cx: &mut Context<Self>,
17750 ) -> Option<Task<Result<()>>> {
17751 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17752
17753 let project = match &self.project {
17754 Some(project) => project.clone(),
17755 None => return None,
17756 };
17757
17758 let ranges = self
17759 .selections
17760 .all_adjusted(&self.display_snapshot(cx))
17761 .into_iter()
17762 .map(|selection| selection.range())
17763 .collect_vec();
17764
17765 Some(self.perform_format(
17766 project,
17767 FormatTrigger::Manual,
17768 FormatTarget::Ranges(ranges),
17769 window,
17770 cx,
17771 ))
17772 }
17773
17774 fn perform_format(
17775 &mut self,
17776 project: Entity<Project>,
17777 trigger: FormatTrigger,
17778 target: FormatTarget,
17779 window: &mut Window,
17780 cx: &mut Context<Self>,
17781 ) -> Task<Result<()>> {
17782 let buffer = self.buffer.clone();
17783 let (buffers, target) = match target {
17784 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17785 FormatTarget::Ranges(selection_ranges) => {
17786 let multi_buffer = buffer.read(cx);
17787 let snapshot = multi_buffer.read(cx);
17788 let mut buffers = HashSet::default();
17789 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17790 BTreeMap::new();
17791 for selection_range in selection_ranges {
17792 for (buffer, buffer_range, _) in
17793 snapshot.range_to_buffer_ranges(selection_range)
17794 {
17795 let buffer_id = buffer.remote_id();
17796 let start = buffer.anchor_before(buffer_range.start);
17797 let end = buffer.anchor_after(buffer_range.end);
17798 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17799 buffer_id_to_ranges
17800 .entry(buffer_id)
17801 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17802 .or_insert_with(|| vec![start..end]);
17803 }
17804 }
17805 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17806 }
17807 };
17808
17809 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17810 let selections_prev = transaction_id_prev
17811 .and_then(|transaction_id_prev| {
17812 // default to selections as they were after the last edit, if we have them,
17813 // instead of how they are now.
17814 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17815 // will take you back to where you made the last edit, instead of staying where you scrolled
17816 self.selection_history
17817 .transaction(transaction_id_prev)
17818 .map(|t| t.0.clone())
17819 })
17820 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17821
17822 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17823 let format = project.update(cx, |project, cx| {
17824 project.format(buffers, target, true, trigger, cx)
17825 });
17826
17827 cx.spawn_in(window, async move |editor, cx| {
17828 let transaction = futures::select_biased! {
17829 transaction = format.log_err().fuse() => transaction,
17830 () = timeout => {
17831 log::warn!("timed out waiting for formatting");
17832 None
17833 }
17834 };
17835
17836 buffer
17837 .update(cx, |buffer, cx| {
17838 if let Some(transaction) = transaction
17839 && !buffer.is_singleton()
17840 {
17841 buffer.push_transaction(&transaction.0, cx);
17842 }
17843 cx.notify();
17844 })
17845 .ok();
17846
17847 if let Some(transaction_id_now) =
17848 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17849 {
17850 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17851 if has_new_transaction {
17852 _ = editor.update(cx, |editor, _| {
17853 editor
17854 .selection_history
17855 .insert_transaction(transaction_id_now, selections_prev);
17856 });
17857 }
17858 }
17859
17860 Ok(())
17861 })
17862 }
17863
17864 fn organize_imports(
17865 &mut self,
17866 _: &OrganizeImports,
17867 window: &mut Window,
17868 cx: &mut Context<Self>,
17869 ) -> Option<Task<Result<()>>> {
17870 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17871 let project = match &self.project {
17872 Some(project) => project.clone(),
17873 None => return None,
17874 };
17875 Some(self.perform_code_action_kind(
17876 project,
17877 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17878 window,
17879 cx,
17880 ))
17881 }
17882
17883 fn perform_code_action_kind(
17884 &mut self,
17885 project: Entity<Project>,
17886 kind: CodeActionKind,
17887 window: &mut Window,
17888 cx: &mut Context<Self>,
17889 ) -> Task<Result<()>> {
17890 let buffer = self.buffer.clone();
17891 let buffers = buffer.read(cx).all_buffers();
17892 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17893 let apply_action = project.update(cx, |project, cx| {
17894 project.apply_code_action_kind(buffers, kind, true, cx)
17895 });
17896 cx.spawn_in(window, async move |_, cx| {
17897 let transaction = futures::select_biased! {
17898 () = timeout => {
17899 log::warn!("timed out waiting for executing code action");
17900 None
17901 }
17902 transaction = apply_action.log_err().fuse() => transaction,
17903 };
17904 buffer
17905 .update(cx, |buffer, cx| {
17906 // check if we need this
17907 if let Some(transaction) = transaction
17908 && !buffer.is_singleton()
17909 {
17910 buffer.push_transaction(&transaction.0, cx);
17911 }
17912 cx.notify();
17913 })
17914 .ok();
17915 Ok(())
17916 })
17917 }
17918
17919 pub fn restart_language_server(
17920 &mut self,
17921 _: &RestartLanguageServer,
17922 _: &mut Window,
17923 cx: &mut Context<Self>,
17924 ) {
17925 if let Some(project) = self.project.clone() {
17926 self.buffer.update(cx, |multi_buffer, cx| {
17927 project.update(cx, |project, cx| {
17928 project.restart_language_servers_for_buffers(
17929 multi_buffer.all_buffers().into_iter().collect(),
17930 HashSet::default(),
17931 cx,
17932 );
17933 });
17934 })
17935 }
17936 }
17937
17938 pub fn stop_language_server(
17939 &mut self,
17940 _: &StopLanguageServer,
17941 _: &mut Window,
17942 cx: &mut Context<Self>,
17943 ) {
17944 if let Some(project) = self.project.clone() {
17945 self.buffer.update(cx, |multi_buffer, cx| {
17946 project.update(cx, |project, cx| {
17947 project.stop_language_servers_for_buffers(
17948 multi_buffer.all_buffers().into_iter().collect(),
17949 HashSet::default(),
17950 cx,
17951 );
17952 });
17953 });
17954 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17955 }
17956 }
17957
17958 fn cancel_language_server_work(
17959 workspace: &mut Workspace,
17960 _: &actions::CancelLanguageServerWork,
17961 _: &mut Window,
17962 cx: &mut Context<Workspace>,
17963 ) {
17964 let project = workspace.project();
17965 let buffers = workspace
17966 .active_item(cx)
17967 .and_then(|item| item.act_as::<Editor>(cx))
17968 .map_or(HashSet::default(), |editor| {
17969 editor.read(cx).buffer.read(cx).all_buffers()
17970 });
17971 project.update(cx, |project, cx| {
17972 project.cancel_language_server_work_for_buffers(buffers, cx);
17973 });
17974 }
17975
17976 fn show_character_palette(
17977 &mut self,
17978 _: &ShowCharacterPalette,
17979 window: &mut Window,
17980 _: &mut Context<Self>,
17981 ) {
17982 window.show_character_palette();
17983 }
17984
17985 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17986 if !self.diagnostics_enabled() {
17987 return;
17988 }
17989
17990 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17991 let buffer = self.buffer.read(cx).snapshot(cx);
17992 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17993 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17994 let is_valid = buffer
17995 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
17996 .any(|entry| {
17997 entry.diagnostic.is_primary
17998 && !entry.range.is_empty()
17999 && entry.range.start == primary_range_start
18000 && entry.diagnostic.message == active_diagnostics.active_message
18001 });
18002
18003 if !is_valid {
18004 self.dismiss_diagnostics(cx);
18005 }
18006 }
18007 }
18008
18009 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18010 match &self.active_diagnostics {
18011 ActiveDiagnostic::Group(group) => Some(group),
18012 _ => None,
18013 }
18014 }
18015
18016 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18017 if !self.diagnostics_enabled() {
18018 return;
18019 }
18020 self.dismiss_diagnostics(cx);
18021 self.active_diagnostics = ActiveDiagnostic::All;
18022 }
18023
18024 fn activate_diagnostics(
18025 &mut self,
18026 buffer_id: BufferId,
18027 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18028 window: &mut Window,
18029 cx: &mut Context<Self>,
18030 ) {
18031 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18032 return;
18033 }
18034 self.dismiss_diagnostics(cx);
18035 let snapshot = self.snapshot(window, cx);
18036 let buffer = self.buffer.read(cx).snapshot(cx);
18037 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18038 return;
18039 };
18040
18041 let diagnostic_group = buffer
18042 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18043 .collect::<Vec<_>>();
18044
18045 let language_registry = self
18046 .project()
18047 .map(|project| project.read(cx).languages().clone());
18048
18049 let blocks = renderer.render_group(
18050 diagnostic_group,
18051 buffer_id,
18052 snapshot,
18053 cx.weak_entity(),
18054 language_registry,
18055 cx,
18056 );
18057
18058 let blocks = self.display_map.update(cx, |display_map, cx| {
18059 display_map.insert_blocks(blocks, cx).into_iter().collect()
18060 });
18061 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18062 active_range: buffer.anchor_before(diagnostic.range.start)
18063 ..buffer.anchor_after(diagnostic.range.end),
18064 active_message: diagnostic.diagnostic.message.clone(),
18065 group_id: diagnostic.diagnostic.group_id,
18066 blocks,
18067 });
18068 cx.notify();
18069 }
18070
18071 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18072 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18073 return;
18074 };
18075
18076 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18077 if let ActiveDiagnostic::Group(group) = prev {
18078 self.display_map.update(cx, |display_map, cx| {
18079 display_map.remove_blocks(group.blocks, cx);
18080 });
18081 cx.notify();
18082 }
18083 }
18084
18085 /// Disable inline diagnostics rendering for this editor.
18086 pub fn disable_inline_diagnostics(&mut self) {
18087 self.inline_diagnostics_enabled = false;
18088 self.inline_diagnostics_update = Task::ready(());
18089 self.inline_diagnostics.clear();
18090 }
18091
18092 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18093 self.diagnostics_enabled = false;
18094 self.dismiss_diagnostics(cx);
18095 self.inline_diagnostics_update = Task::ready(());
18096 self.inline_diagnostics.clear();
18097 }
18098
18099 pub fn disable_word_completions(&mut self) {
18100 self.word_completions_enabled = false;
18101 }
18102
18103 pub fn diagnostics_enabled(&self) -> bool {
18104 self.diagnostics_enabled && self.mode.is_full()
18105 }
18106
18107 pub fn inline_diagnostics_enabled(&self) -> bool {
18108 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18109 }
18110
18111 pub fn show_inline_diagnostics(&self) -> bool {
18112 self.show_inline_diagnostics
18113 }
18114
18115 pub fn toggle_inline_diagnostics(
18116 &mut self,
18117 _: &ToggleInlineDiagnostics,
18118 window: &mut Window,
18119 cx: &mut Context<Editor>,
18120 ) {
18121 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18122 self.refresh_inline_diagnostics(false, window, cx);
18123 }
18124
18125 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18126 self.diagnostics_max_severity = severity;
18127 self.display_map.update(cx, |display_map, _| {
18128 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18129 });
18130 }
18131
18132 pub fn toggle_diagnostics(
18133 &mut self,
18134 _: &ToggleDiagnostics,
18135 window: &mut Window,
18136 cx: &mut Context<Editor>,
18137 ) {
18138 if !self.diagnostics_enabled() {
18139 return;
18140 }
18141
18142 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18143 EditorSettings::get_global(cx)
18144 .diagnostics_max_severity
18145 .filter(|severity| severity != &DiagnosticSeverity::Off)
18146 .unwrap_or(DiagnosticSeverity::Hint)
18147 } else {
18148 DiagnosticSeverity::Off
18149 };
18150 self.set_max_diagnostics_severity(new_severity, cx);
18151 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18152 self.active_diagnostics = ActiveDiagnostic::None;
18153 self.inline_diagnostics_update = Task::ready(());
18154 self.inline_diagnostics.clear();
18155 } else {
18156 self.refresh_inline_diagnostics(false, window, cx);
18157 }
18158
18159 cx.notify();
18160 }
18161
18162 pub fn toggle_minimap(
18163 &mut self,
18164 _: &ToggleMinimap,
18165 window: &mut Window,
18166 cx: &mut Context<Editor>,
18167 ) {
18168 if self.supports_minimap(cx) {
18169 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18170 }
18171 }
18172
18173 fn refresh_inline_diagnostics(
18174 &mut self,
18175 debounce: bool,
18176 window: &mut Window,
18177 cx: &mut Context<Self>,
18178 ) {
18179 let max_severity = ProjectSettings::get_global(cx)
18180 .diagnostics
18181 .inline
18182 .max_severity
18183 .unwrap_or(self.diagnostics_max_severity);
18184
18185 if !self.inline_diagnostics_enabled()
18186 || !self.diagnostics_enabled()
18187 || !self.show_inline_diagnostics
18188 || max_severity == DiagnosticSeverity::Off
18189 {
18190 self.inline_diagnostics_update = Task::ready(());
18191 self.inline_diagnostics.clear();
18192 return;
18193 }
18194
18195 let debounce_ms = ProjectSettings::get_global(cx)
18196 .diagnostics
18197 .inline
18198 .update_debounce_ms;
18199 let debounce = if debounce && debounce_ms > 0 {
18200 Some(Duration::from_millis(debounce_ms))
18201 } else {
18202 None
18203 };
18204 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18205 if let Some(debounce) = debounce {
18206 cx.background_executor().timer(debounce).await;
18207 }
18208 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18209 editor
18210 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18211 .ok()
18212 }) else {
18213 return;
18214 };
18215
18216 let new_inline_diagnostics = cx
18217 .background_spawn(async move {
18218 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18219 for diagnostic_entry in
18220 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18221 {
18222 let message = diagnostic_entry
18223 .diagnostic
18224 .message
18225 .split_once('\n')
18226 .map(|(line, _)| line)
18227 .map(SharedString::new)
18228 .unwrap_or_else(|| {
18229 SharedString::new(&*diagnostic_entry.diagnostic.message)
18230 });
18231 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18232 let (Ok(i) | Err(i)) = inline_diagnostics
18233 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18234 inline_diagnostics.insert(
18235 i,
18236 (
18237 start_anchor,
18238 InlineDiagnostic {
18239 message,
18240 group_id: diagnostic_entry.diagnostic.group_id,
18241 start: diagnostic_entry.range.start.to_point(&snapshot),
18242 is_primary: diagnostic_entry.diagnostic.is_primary,
18243 severity: diagnostic_entry.diagnostic.severity,
18244 },
18245 ),
18246 );
18247 }
18248 inline_diagnostics
18249 })
18250 .await;
18251
18252 editor
18253 .update(cx, |editor, cx| {
18254 editor.inline_diagnostics = new_inline_diagnostics;
18255 cx.notify();
18256 })
18257 .ok();
18258 });
18259 }
18260
18261 fn pull_diagnostics(
18262 &mut self,
18263 buffer_id: Option<BufferId>,
18264 window: &Window,
18265 cx: &mut Context<Self>,
18266 ) -> Option<()> {
18267 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18268 return None;
18269 }
18270 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18271 .diagnostics
18272 .lsp_pull_diagnostics;
18273 if !pull_diagnostics_settings.enabled {
18274 return None;
18275 }
18276 let project = self.project()?.downgrade();
18277 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18278 let mut buffers = self.buffer.read(cx).all_buffers();
18279 buffers.retain(|buffer| {
18280 let buffer_id_to_retain = buffer.read(cx).remote_id();
18281 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18282 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18283 });
18284 if buffers.is_empty() {
18285 self.pull_diagnostics_task = Task::ready(());
18286 return None;
18287 }
18288
18289 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18290 cx.background_executor().timer(debounce).await;
18291
18292 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18293 buffers
18294 .into_iter()
18295 .filter_map(|buffer| {
18296 project
18297 .update(cx, |project, cx| {
18298 project.lsp_store().update(cx, |lsp_store, cx| {
18299 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18300 })
18301 })
18302 .ok()
18303 })
18304 .collect::<FuturesUnordered<_>>()
18305 }) else {
18306 return;
18307 };
18308
18309 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18310 match pull_task {
18311 Ok(()) => {
18312 if editor
18313 .update_in(cx, |editor, window, cx| {
18314 editor.update_diagnostics_state(window, cx);
18315 })
18316 .is_err()
18317 {
18318 return;
18319 }
18320 }
18321 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18322 }
18323 }
18324 });
18325
18326 Some(())
18327 }
18328
18329 pub fn set_selections_from_remote(
18330 &mut self,
18331 selections: Vec<Selection<Anchor>>,
18332 pending_selection: Option<Selection<Anchor>>,
18333 window: &mut Window,
18334 cx: &mut Context<Self>,
18335 ) {
18336 let old_cursor_position = self.selections.newest_anchor().head();
18337 self.selections
18338 .change_with(&self.display_snapshot(cx), |s| {
18339 s.select_anchors(selections);
18340 if let Some(pending_selection) = pending_selection {
18341 s.set_pending(pending_selection, SelectMode::Character);
18342 } else {
18343 s.clear_pending();
18344 }
18345 });
18346 self.selections_did_change(
18347 false,
18348 &old_cursor_position,
18349 SelectionEffects::default(),
18350 window,
18351 cx,
18352 );
18353 }
18354
18355 pub fn transact(
18356 &mut self,
18357 window: &mut Window,
18358 cx: &mut Context<Self>,
18359 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18360 ) -> Option<TransactionId> {
18361 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18362 this.start_transaction_at(Instant::now(), window, cx);
18363 update(this, window, cx);
18364 this.end_transaction_at(Instant::now(), cx)
18365 })
18366 }
18367
18368 pub fn start_transaction_at(
18369 &mut self,
18370 now: Instant,
18371 window: &mut Window,
18372 cx: &mut Context<Self>,
18373 ) -> Option<TransactionId> {
18374 self.end_selection(window, cx);
18375 if let Some(tx_id) = self
18376 .buffer
18377 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18378 {
18379 self.selection_history
18380 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18381 cx.emit(EditorEvent::TransactionBegun {
18382 transaction_id: tx_id,
18383 });
18384 Some(tx_id)
18385 } else {
18386 None
18387 }
18388 }
18389
18390 pub fn end_transaction_at(
18391 &mut self,
18392 now: Instant,
18393 cx: &mut Context<Self>,
18394 ) -> Option<TransactionId> {
18395 if let Some(transaction_id) = self
18396 .buffer
18397 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18398 {
18399 if let Some((_, end_selections)) =
18400 self.selection_history.transaction_mut(transaction_id)
18401 {
18402 *end_selections = Some(self.selections.disjoint_anchors_arc());
18403 } else {
18404 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18405 }
18406
18407 cx.emit(EditorEvent::Edited { transaction_id });
18408 Some(transaction_id)
18409 } else {
18410 None
18411 }
18412 }
18413
18414 pub fn modify_transaction_selection_history(
18415 &mut self,
18416 transaction_id: TransactionId,
18417 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18418 ) -> bool {
18419 self.selection_history
18420 .transaction_mut(transaction_id)
18421 .map(modify)
18422 .is_some()
18423 }
18424
18425 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18426 if self.selection_mark_mode {
18427 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18428 s.move_with(|_, sel| {
18429 sel.collapse_to(sel.head(), SelectionGoal::None);
18430 });
18431 })
18432 }
18433 self.selection_mark_mode = true;
18434 cx.notify();
18435 }
18436
18437 pub fn swap_selection_ends(
18438 &mut self,
18439 _: &actions::SwapSelectionEnds,
18440 window: &mut Window,
18441 cx: &mut Context<Self>,
18442 ) {
18443 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18444 s.move_with(|_, sel| {
18445 if sel.start != sel.end {
18446 sel.reversed = !sel.reversed
18447 }
18448 });
18449 });
18450 self.request_autoscroll(Autoscroll::newest(), cx);
18451 cx.notify();
18452 }
18453
18454 pub fn toggle_focus(
18455 workspace: &mut Workspace,
18456 _: &actions::ToggleFocus,
18457 window: &mut Window,
18458 cx: &mut Context<Workspace>,
18459 ) {
18460 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18461 return;
18462 };
18463 workspace.activate_item(&item, true, true, window, cx);
18464 }
18465
18466 pub fn toggle_fold(
18467 &mut self,
18468 _: &actions::ToggleFold,
18469 window: &mut Window,
18470 cx: &mut Context<Self>,
18471 ) {
18472 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18473 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18474 let selection = self.selections.newest::<Point>(&display_map);
18475
18476 let range = if selection.is_empty() {
18477 let point = selection.head().to_display_point(&display_map);
18478 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18479 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18480 .to_point(&display_map);
18481 start..end
18482 } else {
18483 selection.range()
18484 };
18485 if display_map.folds_in_range(range).next().is_some() {
18486 self.unfold_lines(&Default::default(), window, cx)
18487 } else {
18488 self.fold(&Default::default(), window, cx)
18489 }
18490 } else {
18491 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18492 let buffer_ids: HashSet<_> = self
18493 .selections
18494 .disjoint_anchor_ranges()
18495 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18496 .collect();
18497
18498 let should_unfold = buffer_ids
18499 .iter()
18500 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18501
18502 for buffer_id in buffer_ids {
18503 if should_unfold {
18504 self.unfold_buffer(buffer_id, cx);
18505 } else {
18506 self.fold_buffer(buffer_id, cx);
18507 }
18508 }
18509 }
18510 }
18511
18512 pub fn toggle_fold_recursive(
18513 &mut self,
18514 _: &actions::ToggleFoldRecursive,
18515 window: &mut Window,
18516 cx: &mut Context<Self>,
18517 ) {
18518 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18519
18520 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18521 let range = if selection.is_empty() {
18522 let point = selection.head().to_display_point(&display_map);
18523 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18524 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18525 .to_point(&display_map);
18526 start..end
18527 } else {
18528 selection.range()
18529 };
18530 if display_map.folds_in_range(range).next().is_some() {
18531 self.unfold_recursive(&Default::default(), window, cx)
18532 } else {
18533 self.fold_recursive(&Default::default(), window, cx)
18534 }
18535 }
18536
18537 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18538 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18539 let mut to_fold = Vec::new();
18540 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18541 let selections = self.selections.all_adjusted(&display_map);
18542
18543 for selection in selections {
18544 let range = selection.range().sorted();
18545 let buffer_start_row = range.start.row;
18546
18547 if range.start.row != range.end.row {
18548 let mut found = false;
18549 let mut row = range.start.row;
18550 while row <= range.end.row {
18551 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18552 {
18553 found = true;
18554 row = crease.range().end.row + 1;
18555 to_fold.push(crease);
18556 } else {
18557 row += 1
18558 }
18559 }
18560 if found {
18561 continue;
18562 }
18563 }
18564
18565 for row in (0..=range.start.row).rev() {
18566 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18567 && crease.range().end.row >= buffer_start_row
18568 {
18569 to_fold.push(crease);
18570 if row <= range.start.row {
18571 break;
18572 }
18573 }
18574 }
18575 }
18576
18577 self.fold_creases(to_fold, true, window, cx);
18578 } else {
18579 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18580 let buffer_ids = self
18581 .selections
18582 .disjoint_anchor_ranges()
18583 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18584 .collect::<HashSet<_>>();
18585 for buffer_id in buffer_ids {
18586 self.fold_buffer(buffer_id, cx);
18587 }
18588 }
18589 }
18590
18591 pub fn toggle_fold_all(
18592 &mut self,
18593 _: &actions::ToggleFoldAll,
18594 window: &mut Window,
18595 cx: &mut Context<Self>,
18596 ) {
18597 if self.buffer.read(cx).is_singleton() {
18598 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18599 let has_folds = display_map
18600 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
18601 .next()
18602 .is_some();
18603
18604 if has_folds {
18605 self.unfold_all(&actions::UnfoldAll, window, cx);
18606 } else {
18607 self.fold_all(&actions::FoldAll, window, cx);
18608 }
18609 } else {
18610 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18611 let should_unfold = buffer_ids
18612 .iter()
18613 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18614
18615 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18616 editor
18617 .update_in(cx, |editor, _, cx| {
18618 for buffer_id in buffer_ids {
18619 if should_unfold {
18620 editor.unfold_buffer(buffer_id, cx);
18621 } else {
18622 editor.fold_buffer(buffer_id, cx);
18623 }
18624 }
18625 })
18626 .ok();
18627 });
18628 }
18629 }
18630
18631 fn fold_at_level(
18632 &mut self,
18633 fold_at: &FoldAtLevel,
18634 window: &mut Window,
18635 cx: &mut Context<Self>,
18636 ) {
18637 if !self.buffer.read(cx).is_singleton() {
18638 return;
18639 }
18640
18641 let fold_at_level = fold_at.0;
18642 let snapshot = self.buffer.read(cx).snapshot(cx);
18643 let mut to_fold = Vec::new();
18644 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18645
18646 let row_ranges_to_keep: Vec<Range<u32>> = self
18647 .selections
18648 .all::<Point>(&self.display_snapshot(cx))
18649 .into_iter()
18650 .map(|sel| sel.start.row..sel.end.row)
18651 .collect();
18652
18653 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18654 while start_row < end_row {
18655 match self
18656 .snapshot(window, cx)
18657 .crease_for_buffer_row(MultiBufferRow(start_row))
18658 {
18659 Some(crease) => {
18660 let nested_start_row = crease.range().start.row + 1;
18661 let nested_end_row = crease.range().end.row;
18662
18663 if current_level < fold_at_level {
18664 stack.push((nested_start_row, nested_end_row, current_level + 1));
18665 } else if current_level == fold_at_level {
18666 // Fold iff there is no selection completely contained within the fold region
18667 if !row_ranges_to_keep.iter().any(|selection| {
18668 selection.end >= nested_start_row
18669 && selection.start <= nested_end_row
18670 }) {
18671 to_fold.push(crease);
18672 }
18673 }
18674
18675 start_row = nested_end_row + 1;
18676 }
18677 None => start_row += 1,
18678 }
18679 }
18680 }
18681
18682 self.fold_creases(to_fold, true, window, cx);
18683 }
18684
18685 pub fn fold_at_level_1(
18686 &mut self,
18687 _: &actions::FoldAtLevel1,
18688 window: &mut Window,
18689 cx: &mut Context<Self>,
18690 ) {
18691 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18692 }
18693
18694 pub fn fold_at_level_2(
18695 &mut self,
18696 _: &actions::FoldAtLevel2,
18697 window: &mut Window,
18698 cx: &mut Context<Self>,
18699 ) {
18700 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18701 }
18702
18703 pub fn fold_at_level_3(
18704 &mut self,
18705 _: &actions::FoldAtLevel3,
18706 window: &mut Window,
18707 cx: &mut Context<Self>,
18708 ) {
18709 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18710 }
18711
18712 pub fn fold_at_level_4(
18713 &mut self,
18714 _: &actions::FoldAtLevel4,
18715 window: &mut Window,
18716 cx: &mut Context<Self>,
18717 ) {
18718 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18719 }
18720
18721 pub fn fold_at_level_5(
18722 &mut self,
18723 _: &actions::FoldAtLevel5,
18724 window: &mut Window,
18725 cx: &mut Context<Self>,
18726 ) {
18727 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18728 }
18729
18730 pub fn fold_at_level_6(
18731 &mut self,
18732 _: &actions::FoldAtLevel6,
18733 window: &mut Window,
18734 cx: &mut Context<Self>,
18735 ) {
18736 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18737 }
18738
18739 pub fn fold_at_level_7(
18740 &mut self,
18741 _: &actions::FoldAtLevel7,
18742 window: &mut Window,
18743 cx: &mut Context<Self>,
18744 ) {
18745 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18746 }
18747
18748 pub fn fold_at_level_8(
18749 &mut self,
18750 _: &actions::FoldAtLevel8,
18751 window: &mut Window,
18752 cx: &mut Context<Self>,
18753 ) {
18754 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18755 }
18756
18757 pub fn fold_at_level_9(
18758 &mut self,
18759 _: &actions::FoldAtLevel9,
18760 window: &mut Window,
18761 cx: &mut Context<Self>,
18762 ) {
18763 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18764 }
18765
18766 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18767 if self.buffer.read(cx).is_singleton() {
18768 let mut fold_ranges = Vec::new();
18769 let snapshot = self.buffer.read(cx).snapshot(cx);
18770
18771 for row in 0..snapshot.max_row().0 {
18772 if let Some(foldable_range) = self
18773 .snapshot(window, cx)
18774 .crease_for_buffer_row(MultiBufferRow(row))
18775 {
18776 fold_ranges.push(foldable_range);
18777 }
18778 }
18779
18780 self.fold_creases(fold_ranges, true, window, cx);
18781 } else {
18782 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18783 editor
18784 .update_in(cx, |editor, _, cx| {
18785 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18786 editor.fold_buffer(buffer_id, cx);
18787 }
18788 })
18789 .ok();
18790 });
18791 }
18792 }
18793
18794 pub fn fold_function_bodies(
18795 &mut self,
18796 _: &actions::FoldFunctionBodies,
18797 window: &mut Window,
18798 cx: &mut Context<Self>,
18799 ) {
18800 let snapshot = self.buffer.read(cx).snapshot(cx);
18801
18802 let ranges = snapshot
18803 .text_object_ranges(
18804 MultiBufferOffset(0)..snapshot.len(),
18805 TreeSitterOptions::default(),
18806 )
18807 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18808 .collect::<Vec<_>>();
18809
18810 let creases = ranges
18811 .into_iter()
18812 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18813 .collect();
18814
18815 self.fold_creases(creases, true, window, cx);
18816 }
18817
18818 pub fn fold_recursive(
18819 &mut self,
18820 _: &actions::FoldRecursive,
18821 window: &mut Window,
18822 cx: &mut Context<Self>,
18823 ) {
18824 let mut to_fold = Vec::new();
18825 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18826 let selections = self.selections.all_adjusted(&display_map);
18827
18828 for selection in selections {
18829 let range = selection.range().sorted();
18830 let buffer_start_row = range.start.row;
18831
18832 if range.start.row != range.end.row {
18833 let mut found = false;
18834 for row in range.start.row..=range.end.row {
18835 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18836 found = true;
18837 to_fold.push(crease);
18838 }
18839 }
18840 if found {
18841 continue;
18842 }
18843 }
18844
18845 for row in (0..=range.start.row).rev() {
18846 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18847 if crease.range().end.row >= buffer_start_row {
18848 to_fold.push(crease);
18849 } else {
18850 break;
18851 }
18852 }
18853 }
18854 }
18855
18856 self.fold_creases(to_fold, true, window, cx);
18857 }
18858
18859 pub fn fold_at(
18860 &mut self,
18861 buffer_row: MultiBufferRow,
18862 window: &mut Window,
18863 cx: &mut Context<Self>,
18864 ) {
18865 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18866
18867 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18868 let autoscroll = self
18869 .selections
18870 .all::<Point>(&display_map)
18871 .iter()
18872 .any(|selection| crease.range().overlaps(&selection.range()));
18873
18874 self.fold_creases(vec![crease], autoscroll, window, cx);
18875 }
18876 }
18877
18878 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18879 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18880 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18881 let buffer = display_map.buffer_snapshot();
18882 let selections = self.selections.all::<Point>(&display_map);
18883 let ranges = selections
18884 .iter()
18885 .map(|s| {
18886 let range = s.display_range(&display_map).sorted();
18887 let mut start = range.start.to_point(&display_map);
18888 let mut end = range.end.to_point(&display_map);
18889 start.column = 0;
18890 end.column = buffer.line_len(MultiBufferRow(end.row));
18891 start..end
18892 })
18893 .collect::<Vec<_>>();
18894
18895 self.unfold_ranges(&ranges, true, true, cx);
18896 } else {
18897 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18898 let buffer_ids = self
18899 .selections
18900 .disjoint_anchor_ranges()
18901 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18902 .collect::<HashSet<_>>();
18903 for buffer_id in buffer_ids {
18904 self.unfold_buffer(buffer_id, cx);
18905 }
18906 }
18907 }
18908
18909 pub fn unfold_recursive(
18910 &mut self,
18911 _: &UnfoldRecursive,
18912 _window: &mut Window,
18913 cx: &mut Context<Self>,
18914 ) {
18915 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18916 let selections = self.selections.all::<Point>(&display_map);
18917 let ranges = selections
18918 .iter()
18919 .map(|s| {
18920 let mut range = s.display_range(&display_map).sorted();
18921 *range.start.column_mut() = 0;
18922 *range.end.column_mut() = display_map.line_len(range.end.row());
18923 let start = range.start.to_point(&display_map);
18924 let end = range.end.to_point(&display_map);
18925 start..end
18926 })
18927 .collect::<Vec<_>>();
18928
18929 self.unfold_ranges(&ranges, true, true, cx);
18930 }
18931
18932 pub fn unfold_at(
18933 &mut self,
18934 buffer_row: MultiBufferRow,
18935 _window: &mut Window,
18936 cx: &mut Context<Self>,
18937 ) {
18938 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18939
18940 let intersection_range = Point::new(buffer_row.0, 0)
18941 ..Point::new(
18942 buffer_row.0,
18943 display_map.buffer_snapshot().line_len(buffer_row),
18944 );
18945
18946 let autoscroll = self
18947 .selections
18948 .all::<Point>(&display_map)
18949 .iter()
18950 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18951
18952 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18953 }
18954
18955 pub fn unfold_all(
18956 &mut self,
18957 _: &actions::UnfoldAll,
18958 _window: &mut Window,
18959 cx: &mut Context<Self>,
18960 ) {
18961 if self.buffer.read(cx).is_singleton() {
18962 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18963 self.unfold_ranges(
18964 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
18965 true,
18966 true,
18967 cx,
18968 );
18969 } else {
18970 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18971 editor
18972 .update(cx, |editor, cx| {
18973 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18974 editor.unfold_buffer(buffer_id, cx);
18975 }
18976 })
18977 .ok();
18978 });
18979 }
18980 }
18981
18982 pub fn fold_selected_ranges(
18983 &mut self,
18984 _: &FoldSelectedRanges,
18985 window: &mut Window,
18986 cx: &mut Context<Self>,
18987 ) {
18988 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18989 let selections = self.selections.all_adjusted(&display_map);
18990 let ranges = selections
18991 .into_iter()
18992 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18993 .collect::<Vec<_>>();
18994 self.fold_creases(ranges, true, window, cx);
18995 }
18996
18997 pub fn fold_ranges<T: ToOffset + Clone>(
18998 &mut self,
18999 ranges: Vec<Range<T>>,
19000 auto_scroll: bool,
19001 window: &mut Window,
19002 cx: &mut Context<Self>,
19003 ) {
19004 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19005 let ranges = ranges
19006 .into_iter()
19007 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19008 .collect::<Vec<_>>();
19009 self.fold_creases(ranges, auto_scroll, window, cx);
19010 }
19011
19012 pub fn fold_creases<T: ToOffset + Clone>(
19013 &mut self,
19014 creases: Vec<Crease<T>>,
19015 auto_scroll: bool,
19016 _window: &mut Window,
19017 cx: &mut Context<Self>,
19018 ) {
19019 if creases.is_empty() {
19020 return;
19021 }
19022
19023 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19024
19025 if auto_scroll {
19026 self.request_autoscroll(Autoscroll::fit(), cx);
19027 }
19028
19029 cx.notify();
19030
19031 self.scrollbar_marker_state.dirty = true;
19032 self.folds_did_change(cx);
19033 }
19034
19035 /// Removes any folds whose ranges intersect any of the given ranges.
19036 pub fn unfold_ranges<T: ToOffset + Clone>(
19037 &mut self,
19038 ranges: &[Range<T>],
19039 inclusive: bool,
19040 auto_scroll: bool,
19041 cx: &mut Context<Self>,
19042 ) {
19043 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19044 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19045 });
19046 self.folds_did_change(cx);
19047 }
19048
19049 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19050 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19051 return;
19052 }
19053
19054 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19055 self.display_map.update(cx, |display_map, cx| {
19056 display_map.fold_buffers([buffer_id], cx)
19057 });
19058
19059 let snapshot = self.display_snapshot(cx);
19060 self.selections.change_with(&snapshot, |selections| {
19061 selections.remove_selections_from_buffer(buffer_id);
19062 });
19063
19064 cx.emit(EditorEvent::BufferFoldToggled {
19065 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19066 folded: true,
19067 });
19068 cx.notify();
19069 }
19070
19071 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19072 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19073 return;
19074 }
19075 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19076 self.display_map.update(cx, |display_map, cx| {
19077 display_map.unfold_buffers([buffer_id], cx);
19078 });
19079 cx.emit(EditorEvent::BufferFoldToggled {
19080 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19081 folded: false,
19082 });
19083 cx.notify();
19084 }
19085
19086 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19087 self.display_map.read(cx).is_buffer_folded(buffer)
19088 }
19089
19090 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19091 self.display_map.read(cx).folded_buffers()
19092 }
19093
19094 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19095 self.display_map.update(cx, |display_map, cx| {
19096 display_map.disable_header_for_buffer(buffer_id, cx);
19097 });
19098 cx.notify();
19099 }
19100
19101 /// Removes any folds with the given ranges.
19102 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19103 &mut self,
19104 ranges: &[Range<T>],
19105 type_id: TypeId,
19106 auto_scroll: bool,
19107 cx: &mut Context<Self>,
19108 ) {
19109 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19110 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19111 });
19112 self.folds_did_change(cx);
19113 }
19114
19115 fn remove_folds_with<T: ToOffset + Clone>(
19116 &mut self,
19117 ranges: &[Range<T>],
19118 auto_scroll: bool,
19119 cx: &mut Context<Self>,
19120 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19121 ) {
19122 if ranges.is_empty() {
19123 return;
19124 }
19125
19126 let mut buffers_affected = HashSet::default();
19127 let multi_buffer = self.buffer().read(cx);
19128 for range in ranges {
19129 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19130 buffers_affected.insert(buffer.read(cx).remote_id());
19131 };
19132 }
19133
19134 self.display_map.update(cx, update);
19135
19136 if auto_scroll {
19137 self.request_autoscroll(Autoscroll::fit(), cx);
19138 }
19139
19140 cx.notify();
19141 self.scrollbar_marker_state.dirty = true;
19142 self.active_indent_guides_state.dirty = true;
19143 }
19144
19145 pub fn update_renderer_widths(
19146 &mut self,
19147 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19148 cx: &mut Context<Self>,
19149 ) -> bool {
19150 self.display_map
19151 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19152 }
19153
19154 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19155 self.display_map.read(cx).fold_placeholder.clone()
19156 }
19157
19158 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19159 self.buffer.update(cx, |buffer, cx| {
19160 buffer.set_all_diff_hunks_expanded(cx);
19161 });
19162 }
19163
19164 pub fn expand_all_diff_hunks(
19165 &mut self,
19166 _: &ExpandAllDiffHunks,
19167 _window: &mut Window,
19168 cx: &mut Context<Self>,
19169 ) {
19170 self.buffer.update(cx, |buffer, cx| {
19171 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19172 });
19173 }
19174
19175 pub fn collapse_all_diff_hunks(
19176 &mut self,
19177 _: &CollapseAllDiffHunks,
19178 _window: &mut Window,
19179 cx: &mut Context<Self>,
19180 ) {
19181 self.buffer.update(cx, |buffer, cx| {
19182 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19183 });
19184 }
19185
19186 pub fn toggle_selected_diff_hunks(
19187 &mut self,
19188 _: &ToggleSelectedDiffHunks,
19189 _window: &mut Window,
19190 cx: &mut Context<Self>,
19191 ) {
19192 let ranges: Vec<_> = self
19193 .selections
19194 .disjoint_anchors()
19195 .iter()
19196 .map(|s| s.range())
19197 .collect();
19198 self.toggle_diff_hunks_in_ranges(ranges, cx);
19199 }
19200
19201 pub fn diff_hunks_in_ranges<'a>(
19202 &'a self,
19203 ranges: &'a [Range<Anchor>],
19204 buffer: &'a MultiBufferSnapshot,
19205 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19206 ranges.iter().flat_map(move |range| {
19207 let end_excerpt_id = range.end.excerpt_id;
19208 let range = range.to_point(buffer);
19209 let mut peek_end = range.end;
19210 if range.end.row < buffer.max_row().0 {
19211 peek_end = Point::new(range.end.row + 1, 0);
19212 }
19213 buffer
19214 .diff_hunks_in_range(range.start..peek_end)
19215 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19216 })
19217 }
19218
19219 pub fn has_stageable_diff_hunks_in_ranges(
19220 &self,
19221 ranges: &[Range<Anchor>],
19222 snapshot: &MultiBufferSnapshot,
19223 ) -> bool {
19224 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19225 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19226 }
19227
19228 pub fn toggle_staged_selected_diff_hunks(
19229 &mut self,
19230 _: &::git::ToggleStaged,
19231 _: &mut Window,
19232 cx: &mut Context<Self>,
19233 ) {
19234 let snapshot = self.buffer.read(cx).snapshot(cx);
19235 let ranges: Vec<_> = self
19236 .selections
19237 .disjoint_anchors()
19238 .iter()
19239 .map(|s| s.range())
19240 .collect();
19241 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19242 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19243 }
19244
19245 pub fn set_render_diff_hunk_controls(
19246 &mut self,
19247 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19248 cx: &mut Context<Self>,
19249 ) {
19250 self.render_diff_hunk_controls = render_diff_hunk_controls;
19251 cx.notify();
19252 }
19253
19254 pub fn stage_and_next(
19255 &mut self,
19256 _: &::git::StageAndNext,
19257 window: &mut Window,
19258 cx: &mut Context<Self>,
19259 ) {
19260 self.do_stage_or_unstage_and_next(true, window, cx);
19261 }
19262
19263 pub fn unstage_and_next(
19264 &mut self,
19265 _: &::git::UnstageAndNext,
19266 window: &mut Window,
19267 cx: &mut Context<Self>,
19268 ) {
19269 self.do_stage_or_unstage_and_next(false, window, cx);
19270 }
19271
19272 pub fn stage_or_unstage_diff_hunks(
19273 &mut self,
19274 stage: bool,
19275 ranges: Vec<Range<Anchor>>,
19276 cx: &mut Context<Self>,
19277 ) {
19278 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19279 cx.spawn(async move |this, cx| {
19280 task.await?;
19281 this.update(cx, |this, cx| {
19282 let snapshot = this.buffer.read(cx).snapshot(cx);
19283 let chunk_by = this
19284 .diff_hunks_in_ranges(&ranges, &snapshot)
19285 .chunk_by(|hunk| hunk.buffer_id);
19286 for (buffer_id, hunks) in &chunk_by {
19287 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19288 }
19289 })
19290 })
19291 .detach_and_log_err(cx);
19292 }
19293
19294 fn save_buffers_for_ranges_if_needed(
19295 &mut self,
19296 ranges: &[Range<Anchor>],
19297 cx: &mut Context<Editor>,
19298 ) -> Task<Result<()>> {
19299 let multibuffer = self.buffer.read(cx);
19300 let snapshot = multibuffer.read(cx);
19301 let buffer_ids: HashSet<_> = ranges
19302 .iter()
19303 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19304 .collect();
19305 drop(snapshot);
19306
19307 let mut buffers = HashSet::default();
19308 for buffer_id in buffer_ids {
19309 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19310 let buffer = buffer_entity.read(cx);
19311 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19312 {
19313 buffers.insert(buffer_entity);
19314 }
19315 }
19316 }
19317
19318 if let Some(project) = &self.project {
19319 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19320 } else {
19321 Task::ready(Ok(()))
19322 }
19323 }
19324
19325 fn do_stage_or_unstage_and_next(
19326 &mut self,
19327 stage: bool,
19328 window: &mut Window,
19329 cx: &mut Context<Self>,
19330 ) {
19331 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19332
19333 if ranges.iter().any(|range| range.start != range.end) {
19334 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19335 return;
19336 }
19337
19338 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19339 let snapshot = self.snapshot(window, cx);
19340 let position = self
19341 .selections
19342 .newest::<Point>(&snapshot.display_snapshot)
19343 .head();
19344 let mut row = snapshot
19345 .buffer_snapshot()
19346 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19347 .find(|hunk| hunk.row_range.start.0 > position.row)
19348 .map(|hunk| hunk.row_range.start);
19349
19350 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19351 // Outside of the project diff editor, wrap around to the beginning.
19352 if !all_diff_hunks_expanded {
19353 row = row.or_else(|| {
19354 snapshot
19355 .buffer_snapshot()
19356 .diff_hunks_in_range(Point::zero()..position)
19357 .find(|hunk| hunk.row_range.end.0 < position.row)
19358 .map(|hunk| hunk.row_range.start)
19359 });
19360 }
19361
19362 if let Some(row) = row {
19363 let destination = Point::new(row.0, 0);
19364 let autoscroll = Autoscroll::center();
19365
19366 self.unfold_ranges(&[destination..destination], false, false, cx);
19367 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19368 s.select_ranges([destination..destination]);
19369 });
19370 }
19371 }
19372
19373 fn do_stage_or_unstage(
19374 &self,
19375 stage: bool,
19376 buffer_id: BufferId,
19377 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19378 cx: &mut App,
19379 ) -> Option<()> {
19380 let project = self.project()?;
19381 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19382 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19383 let buffer_snapshot = buffer.read(cx).snapshot();
19384 let file_exists = buffer_snapshot
19385 .file()
19386 .is_some_and(|file| file.disk_state().exists());
19387 diff.update(cx, |diff, cx| {
19388 diff.stage_or_unstage_hunks(
19389 stage,
19390 &hunks
19391 .map(|hunk| buffer_diff::DiffHunk {
19392 buffer_range: hunk.buffer_range,
19393 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19394 ..hunk.diff_base_byte_range.end.0,
19395 secondary_status: hunk.secondary_status,
19396 range: Point::zero()..Point::zero(), // unused
19397 })
19398 .collect::<Vec<_>>(),
19399 &buffer_snapshot,
19400 file_exists,
19401 cx,
19402 )
19403 });
19404 None
19405 }
19406
19407 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19408 let ranges: Vec<_> = self
19409 .selections
19410 .disjoint_anchors()
19411 .iter()
19412 .map(|s| s.range())
19413 .collect();
19414 self.buffer
19415 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19416 }
19417
19418 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19419 self.buffer.update(cx, |buffer, cx| {
19420 let ranges = vec![Anchor::min()..Anchor::max()];
19421 if !buffer.all_diff_hunks_expanded()
19422 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19423 {
19424 buffer.collapse_diff_hunks(ranges, cx);
19425 true
19426 } else {
19427 false
19428 }
19429 })
19430 }
19431
19432 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19433 if self.buffer.read(cx).all_diff_hunks_expanded() {
19434 return true;
19435 }
19436 let ranges = vec![Anchor::min()..Anchor::max()];
19437 self.buffer
19438 .read(cx)
19439 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19440 }
19441
19442 fn toggle_diff_hunks_in_ranges(
19443 &mut self,
19444 ranges: Vec<Range<Anchor>>,
19445 cx: &mut Context<Editor>,
19446 ) {
19447 self.buffer.update(cx, |buffer, cx| {
19448 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19449 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19450 })
19451 }
19452
19453 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19454 self.buffer.update(cx, |buffer, cx| {
19455 let snapshot = buffer.snapshot(cx);
19456 let excerpt_id = range.end.excerpt_id;
19457 let point_range = range.to_point(&snapshot);
19458 let expand = !buffer.single_hunk_is_expanded(range, cx);
19459 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19460 })
19461 }
19462
19463 pub(crate) fn apply_all_diff_hunks(
19464 &mut self,
19465 _: &ApplyAllDiffHunks,
19466 window: &mut Window,
19467 cx: &mut Context<Self>,
19468 ) {
19469 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19470
19471 let buffers = self.buffer.read(cx).all_buffers();
19472 for branch_buffer in buffers {
19473 branch_buffer.update(cx, |branch_buffer, cx| {
19474 branch_buffer.merge_into_base(Vec::new(), cx);
19475 });
19476 }
19477
19478 if let Some(project) = self.project.clone() {
19479 self.save(
19480 SaveOptions {
19481 format: true,
19482 autosave: false,
19483 },
19484 project,
19485 window,
19486 cx,
19487 )
19488 .detach_and_log_err(cx);
19489 }
19490 }
19491
19492 pub(crate) fn apply_selected_diff_hunks(
19493 &mut self,
19494 _: &ApplyDiffHunk,
19495 window: &mut Window,
19496 cx: &mut Context<Self>,
19497 ) {
19498 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19499 let snapshot = self.snapshot(window, cx);
19500 let hunks = snapshot.hunks_for_ranges(
19501 self.selections
19502 .all(&snapshot.display_snapshot)
19503 .into_iter()
19504 .map(|selection| selection.range()),
19505 );
19506 let mut ranges_by_buffer = HashMap::default();
19507 self.transact(window, cx, |editor, _window, cx| {
19508 for hunk in hunks {
19509 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19510 ranges_by_buffer
19511 .entry(buffer.clone())
19512 .or_insert_with(Vec::new)
19513 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19514 }
19515 }
19516
19517 for (buffer, ranges) in ranges_by_buffer {
19518 buffer.update(cx, |buffer, cx| {
19519 buffer.merge_into_base(ranges, cx);
19520 });
19521 }
19522 });
19523
19524 if let Some(project) = self.project.clone() {
19525 self.save(
19526 SaveOptions {
19527 format: true,
19528 autosave: false,
19529 },
19530 project,
19531 window,
19532 cx,
19533 )
19534 .detach_and_log_err(cx);
19535 }
19536 }
19537
19538 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19539 if hovered != self.gutter_hovered {
19540 self.gutter_hovered = hovered;
19541 cx.notify();
19542 }
19543 }
19544
19545 pub fn insert_blocks(
19546 &mut self,
19547 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19548 autoscroll: Option<Autoscroll>,
19549 cx: &mut Context<Self>,
19550 ) -> Vec<CustomBlockId> {
19551 let blocks = self
19552 .display_map
19553 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19554 if let Some(autoscroll) = autoscroll {
19555 self.request_autoscroll(autoscroll, cx);
19556 }
19557 cx.notify();
19558 blocks
19559 }
19560
19561 pub fn resize_blocks(
19562 &mut self,
19563 heights: HashMap<CustomBlockId, u32>,
19564 autoscroll: Option<Autoscroll>,
19565 cx: &mut Context<Self>,
19566 ) {
19567 self.display_map
19568 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19569 if let Some(autoscroll) = autoscroll {
19570 self.request_autoscroll(autoscroll, cx);
19571 }
19572 cx.notify();
19573 }
19574
19575 pub fn replace_blocks(
19576 &mut self,
19577 renderers: HashMap<CustomBlockId, RenderBlock>,
19578 autoscroll: Option<Autoscroll>,
19579 cx: &mut Context<Self>,
19580 ) {
19581 self.display_map
19582 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19583 if let Some(autoscroll) = autoscroll {
19584 self.request_autoscroll(autoscroll, cx);
19585 }
19586 cx.notify();
19587 }
19588
19589 pub fn remove_blocks(
19590 &mut self,
19591 block_ids: HashSet<CustomBlockId>,
19592 autoscroll: Option<Autoscroll>,
19593 cx: &mut Context<Self>,
19594 ) {
19595 self.display_map.update(cx, |display_map, cx| {
19596 display_map.remove_blocks(block_ids, cx)
19597 });
19598 if let Some(autoscroll) = autoscroll {
19599 self.request_autoscroll(autoscroll, cx);
19600 }
19601 cx.notify();
19602 }
19603
19604 pub fn row_for_block(
19605 &self,
19606 block_id: CustomBlockId,
19607 cx: &mut Context<Self>,
19608 ) -> Option<DisplayRow> {
19609 self.display_map
19610 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19611 }
19612
19613 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19614 self.focused_block = Some(focused_block);
19615 }
19616
19617 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19618 self.focused_block.take()
19619 }
19620
19621 pub fn insert_creases(
19622 &mut self,
19623 creases: impl IntoIterator<Item = Crease<Anchor>>,
19624 cx: &mut Context<Self>,
19625 ) -> Vec<CreaseId> {
19626 self.display_map
19627 .update(cx, |map, cx| map.insert_creases(creases, cx))
19628 }
19629
19630 pub fn remove_creases(
19631 &mut self,
19632 ids: impl IntoIterator<Item = CreaseId>,
19633 cx: &mut Context<Self>,
19634 ) -> Vec<(CreaseId, Range<Anchor>)> {
19635 self.display_map
19636 .update(cx, |map, cx| map.remove_creases(ids, cx))
19637 }
19638
19639 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19640 self.display_map
19641 .update(cx, |map, cx| map.snapshot(cx))
19642 .longest_row()
19643 }
19644
19645 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19646 self.display_map
19647 .update(cx, |map, cx| map.snapshot(cx))
19648 .max_point()
19649 }
19650
19651 pub fn text(&self, cx: &App) -> String {
19652 self.buffer.read(cx).read(cx).text()
19653 }
19654
19655 pub fn is_empty(&self, cx: &App) -> bool {
19656 self.buffer.read(cx).read(cx).is_empty()
19657 }
19658
19659 pub fn text_option(&self, cx: &App) -> Option<String> {
19660 let text = self.text(cx);
19661 let text = text.trim();
19662
19663 if text.is_empty() {
19664 return None;
19665 }
19666
19667 Some(text.to_string())
19668 }
19669
19670 pub fn set_text(
19671 &mut self,
19672 text: impl Into<Arc<str>>,
19673 window: &mut Window,
19674 cx: &mut Context<Self>,
19675 ) {
19676 self.transact(window, cx, |this, _, cx| {
19677 this.buffer
19678 .read(cx)
19679 .as_singleton()
19680 .expect("you can only call set_text on editors for singleton buffers")
19681 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19682 });
19683 }
19684
19685 pub fn display_text(&self, cx: &mut App) -> String {
19686 self.display_map
19687 .update(cx, |map, cx| map.snapshot(cx))
19688 .text()
19689 }
19690
19691 fn create_minimap(
19692 &self,
19693 minimap_settings: MinimapSettings,
19694 window: &mut Window,
19695 cx: &mut Context<Self>,
19696 ) -> Option<Entity<Self>> {
19697 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19698 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19699 }
19700
19701 fn initialize_new_minimap(
19702 &self,
19703 minimap_settings: MinimapSettings,
19704 window: &mut Window,
19705 cx: &mut Context<Self>,
19706 ) -> Entity<Self> {
19707 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19708
19709 let mut minimap = Editor::new_internal(
19710 EditorMode::Minimap {
19711 parent: cx.weak_entity(),
19712 },
19713 self.buffer.clone(),
19714 None,
19715 Some(self.display_map.clone()),
19716 window,
19717 cx,
19718 );
19719 minimap.scroll_manager.clone_state(&self.scroll_manager);
19720 minimap.set_text_style_refinement(TextStyleRefinement {
19721 font_size: Some(MINIMAP_FONT_SIZE),
19722 font_weight: Some(MINIMAP_FONT_WEIGHT),
19723 ..Default::default()
19724 });
19725 minimap.update_minimap_configuration(minimap_settings, cx);
19726 cx.new(|_| minimap)
19727 }
19728
19729 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19730 let current_line_highlight = minimap_settings
19731 .current_line_highlight
19732 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19733 self.set_current_line_highlight(Some(current_line_highlight));
19734 }
19735
19736 pub fn minimap(&self) -> Option<&Entity<Self>> {
19737 self.minimap
19738 .as_ref()
19739 .filter(|_| self.minimap_visibility.visible())
19740 }
19741
19742 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19743 let mut wrap_guides = smallvec![];
19744
19745 if self.show_wrap_guides == Some(false) {
19746 return wrap_guides;
19747 }
19748
19749 let settings = self.buffer.read(cx).language_settings(cx);
19750 if settings.show_wrap_guides {
19751 match self.soft_wrap_mode(cx) {
19752 SoftWrap::Column(soft_wrap) => {
19753 wrap_guides.push((soft_wrap as usize, true));
19754 }
19755 SoftWrap::Bounded(soft_wrap) => {
19756 wrap_guides.push((soft_wrap as usize, true));
19757 }
19758 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19759 }
19760 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19761 }
19762
19763 wrap_guides
19764 }
19765
19766 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19767 let settings = self.buffer.read(cx).language_settings(cx);
19768 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19769 match mode {
19770 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19771 SoftWrap::None
19772 }
19773 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19774 language_settings::SoftWrap::PreferredLineLength => {
19775 SoftWrap::Column(settings.preferred_line_length)
19776 }
19777 language_settings::SoftWrap::Bounded => {
19778 SoftWrap::Bounded(settings.preferred_line_length)
19779 }
19780 }
19781 }
19782
19783 pub fn set_soft_wrap_mode(
19784 &mut self,
19785 mode: language_settings::SoftWrap,
19786
19787 cx: &mut Context<Self>,
19788 ) {
19789 self.soft_wrap_mode_override = Some(mode);
19790 cx.notify();
19791 }
19792
19793 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19794 self.hard_wrap = hard_wrap;
19795 cx.notify();
19796 }
19797
19798 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19799 self.text_style_refinement = Some(style);
19800 }
19801
19802 /// called by the Element so we know what style we were most recently rendered with.
19803 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19804 // We intentionally do not inform the display map about the minimap style
19805 // so that wrapping is not recalculated and stays consistent for the editor
19806 // and its linked minimap.
19807 if !self.mode.is_minimap() {
19808 let font = style.text.font();
19809 let font_size = style.text.font_size.to_pixels(window.rem_size());
19810 let display_map = self
19811 .placeholder_display_map
19812 .as_ref()
19813 .filter(|_| self.is_empty(cx))
19814 .unwrap_or(&self.display_map);
19815
19816 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19817 }
19818 self.style = Some(style);
19819 }
19820
19821 pub fn style(&self) -> Option<&EditorStyle> {
19822 self.style.as_ref()
19823 }
19824
19825 // Called by the element. This method is not designed to be called outside of the editor
19826 // element's layout code because it does not notify when rewrapping is computed synchronously.
19827 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19828 if self.is_empty(cx) {
19829 self.placeholder_display_map
19830 .as_ref()
19831 .map_or(false, |display_map| {
19832 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19833 })
19834 } else {
19835 self.display_map
19836 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19837 }
19838 }
19839
19840 pub fn set_soft_wrap(&mut self) {
19841 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19842 }
19843
19844 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19845 if self.soft_wrap_mode_override.is_some() {
19846 self.soft_wrap_mode_override.take();
19847 } else {
19848 let soft_wrap = match self.soft_wrap_mode(cx) {
19849 SoftWrap::GitDiff => return,
19850 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19851 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19852 language_settings::SoftWrap::None
19853 }
19854 };
19855 self.soft_wrap_mode_override = Some(soft_wrap);
19856 }
19857 cx.notify();
19858 }
19859
19860 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19861 let Some(workspace) = self.workspace() else {
19862 return;
19863 };
19864 let fs = workspace.read(cx).app_state().fs.clone();
19865 let current_show = TabBarSettings::get_global(cx).show;
19866 update_settings_file(fs, cx, move |setting, _| {
19867 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19868 });
19869 }
19870
19871 pub fn toggle_indent_guides(
19872 &mut self,
19873 _: &ToggleIndentGuides,
19874 _: &mut Window,
19875 cx: &mut Context<Self>,
19876 ) {
19877 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19878 self.buffer
19879 .read(cx)
19880 .language_settings(cx)
19881 .indent_guides
19882 .enabled
19883 });
19884 self.show_indent_guides = Some(!currently_enabled);
19885 cx.notify();
19886 }
19887
19888 fn should_show_indent_guides(&self) -> Option<bool> {
19889 self.show_indent_guides
19890 }
19891
19892 pub fn toggle_line_numbers(
19893 &mut self,
19894 _: &ToggleLineNumbers,
19895 _: &mut Window,
19896 cx: &mut Context<Self>,
19897 ) {
19898 let mut editor_settings = EditorSettings::get_global(cx).clone();
19899 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19900 EditorSettings::override_global(editor_settings, cx);
19901 }
19902
19903 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19904 if let Some(show_line_numbers) = self.show_line_numbers {
19905 return show_line_numbers;
19906 }
19907 EditorSettings::get_global(cx).gutter.line_numbers
19908 }
19909
19910 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19911 match (
19912 self.use_relative_line_numbers,
19913 EditorSettings::get_global(cx).relative_line_numbers,
19914 ) {
19915 (None, setting) => setting,
19916 (Some(false), _) => RelativeLineNumbers::Disabled,
19917 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19918 (Some(true), _) => RelativeLineNumbers::Enabled,
19919 }
19920 }
19921
19922 pub fn toggle_relative_line_numbers(
19923 &mut self,
19924 _: &ToggleRelativeLineNumbers,
19925 _: &mut Window,
19926 cx: &mut Context<Self>,
19927 ) {
19928 let is_relative = self.relative_line_numbers(cx);
19929 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19930 }
19931
19932 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19933 self.use_relative_line_numbers = is_relative;
19934 cx.notify();
19935 }
19936
19937 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19938 self.show_gutter = show_gutter;
19939 cx.notify();
19940 }
19941
19942 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19943 self.show_scrollbars = ScrollbarAxes {
19944 horizontal: show,
19945 vertical: show,
19946 };
19947 cx.notify();
19948 }
19949
19950 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19951 self.show_scrollbars.vertical = show;
19952 cx.notify();
19953 }
19954
19955 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19956 self.show_scrollbars.horizontal = show;
19957 cx.notify();
19958 }
19959
19960 pub fn set_minimap_visibility(
19961 &mut self,
19962 minimap_visibility: MinimapVisibility,
19963 window: &mut Window,
19964 cx: &mut Context<Self>,
19965 ) {
19966 if self.minimap_visibility != minimap_visibility {
19967 if minimap_visibility.visible() && self.minimap.is_none() {
19968 let minimap_settings = EditorSettings::get_global(cx).minimap;
19969 self.minimap =
19970 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19971 }
19972 self.minimap_visibility = minimap_visibility;
19973 cx.notify();
19974 }
19975 }
19976
19977 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19978 self.set_show_scrollbars(false, cx);
19979 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19980 }
19981
19982 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19983 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19984 }
19985
19986 /// Normally the text in full mode and auto height editors is padded on the
19987 /// left side by roughly half a character width for improved hit testing.
19988 ///
19989 /// Use this method to disable this for cases where this is not wanted (e.g.
19990 /// if you want to align the editor text with some other text above or below)
19991 /// or if you want to add this padding to single-line editors.
19992 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19993 self.offset_content = offset_content;
19994 cx.notify();
19995 }
19996
19997 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19998 self.show_line_numbers = Some(show_line_numbers);
19999 cx.notify();
20000 }
20001
20002 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20003 self.disable_expand_excerpt_buttons = true;
20004 cx.notify();
20005 }
20006
20007 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20008 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20009 cx.notify();
20010 }
20011
20012 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20013 self.show_code_actions = Some(show_code_actions);
20014 cx.notify();
20015 }
20016
20017 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20018 self.show_runnables = Some(show_runnables);
20019 cx.notify();
20020 }
20021
20022 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20023 self.show_breakpoints = Some(show_breakpoints);
20024 cx.notify();
20025 }
20026
20027 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20028 if self.display_map.read(cx).masked != masked {
20029 self.display_map.update(cx, |map, _| map.masked = masked);
20030 }
20031 cx.notify()
20032 }
20033
20034 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20035 self.show_wrap_guides = Some(show_wrap_guides);
20036 cx.notify();
20037 }
20038
20039 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20040 self.show_indent_guides = Some(show_indent_guides);
20041 cx.notify();
20042 }
20043
20044 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20045 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20046 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20047 && let Some(dir) = file.abs_path(cx).parent()
20048 {
20049 return Some(dir.to_owned());
20050 }
20051 }
20052
20053 None
20054 }
20055
20056 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20057 self.active_excerpt(cx)?
20058 .1
20059 .read(cx)
20060 .file()
20061 .and_then(|f| f.as_local())
20062 }
20063
20064 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20065 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20066 let buffer = buffer.read(cx);
20067 if let Some(project_path) = buffer.project_path(cx) {
20068 let project = self.project()?.read(cx);
20069 project.absolute_path(&project_path, cx)
20070 } else {
20071 buffer
20072 .file()
20073 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20074 }
20075 })
20076 }
20077
20078 pub fn reveal_in_finder(
20079 &mut self,
20080 _: &RevealInFileManager,
20081 _window: &mut Window,
20082 cx: &mut Context<Self>,
20083 ) {
20084 if let Some(target) = self.target_file(cx) {
20085 cx.reveal_path(&target.abs_path(cx));
20086 }
20087 }
20088
20089 pub fn copy_path(
20090 &mut self,
20091 _: &zed_actions::workspace::CopyPath,
20092 _window: &mut Window,
20093 cx: &mut Context<Self>,
20094 ) {
20095 if let Some(path) = self.target_file_abs_path(cx)
20096 && let Some(path) = path.to_str()
20097 {
20098 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20099 } else {
20100 cx.propagate();
20101 }
20102 }
20103
20104 pub fn copy_relative_path(
20105 &mut self,
20106 _: &zed_actions::workspace::CopyRelativePath,
20107 _window: &mut Window,
20108 cx: &mut Context<Self>,
20109 ) {
20110 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20111 let project = self.project()?.read(cx);
20112 let path = buffer.read(cx).file()?.path();
20113 let path = path.display(project.path_style(cx));
20114 Some(path)
20115 }) {
20116 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20117 } else {
20118 cx.propagate();
20119 }
20120 }
20121
20122 /// Returns the project path for the editor's buffer, if any buffer is
20123 /// opened in the editor.
20124 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20125 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20126 buffer.read(cx).project_path(cx)
20127 } else {
20128 None
20129 }
20130 }
20131
20132 // Returns true if the editor handled a go-to-line request
20133 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20134 maybe!({
20135 let breakpoint_store = self.breakpoint_store.as_ref()?;
20136
20137 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20138 else {
20139 self.clear_row_highlights::<ActiveDebugLine>();
20140 return None;
20141 };
20142
20143 let position = active_stack_frame.position;
20144 let buffer_id = position.buffer_id?;
20145 let snapshot = self
20146 .project
20147 .as_ref()?
20148 .read(cx)
20149 .buffer_for_id(buffer_id, cx)?
20150 .read(cx)
20151 .snapshot();
20152
20153 let mut handled = false;
20154 for (id, ExcerptRange { context, .. }) in
20155 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20156 {
20157 if context.start.cmp(&position, &snapshot).is_ge()
20158 || context.end.cmp(&position, &snapshot).is_lt()
20159 {
20160 continue;
20161 }
20162 let snapshot = self.buffer.read(cx).snapshot(cx);
20163 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20164
20165 handled = true;
20166 self.clear_row_highlights::<ActiveDebugLine>();
20167
20168 self.go_to_line::<ActiveDebugLine>(
20169 multibuffer_anchor,
20170 Some(cx.theme().colors().editor_debugger_active_line_background),
20171 window,
20172 cx,
20173 );
20174
20175 cx.notify();
20176 }
20177
20178 handled.then_some(())
20179 })
20180 .is_some()
20181 }
20182
20183 pub fn copy_file_name_without_extension(
20184 &mut self,
20185 _: &CopyFileNameWithoutExtension,
20186 _: &mut Window,
20187 cx: &mut Context<Self>,
20188 ) {
20189 if let Some(file) = self.target_file(cx)
20190 && let Some(file_stem) = file.path().file_stem()
20191 {
20192 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20193 }
20194 }
20195
20196 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20197 if let Some(file) = self.target_file(cx)
20198 && let Some(name) = file.path().file_name()
20199 {
20200 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
20201 }
20202 }
20203
20204 pub fn toggle_git_blame(
20205 &mut self,
20206 _: &::git::Blame,
20207 window: &mut Window,
20208 cx: &mut Context<Self>,
20209 ) {
20210 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20211
20212 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20213 self.start_git_blame(true, window, cx);
20214 }
20215
20216 cx.notify();
20217 }
20218
20219 pub fn toggle_git_blame_inline(
20220 &mut self,
20221 _: &ToggleGitBlameInline,
20222 window: &mut Window,
20223 cx: &mut Context<Self>,
20224 ) {
20225 self.toggle_git_blame_inline_internal(true, window, cx);
20226 cx.notify();
20227 }
20228
20229 pub fn open_git_blame_commit(
20230 &mut self,
20231 _: &OpenGitBlameCommit,
20232 window: &mut Window,
20233 cx: &mut Context<Self>,
20234 ) {
20235 self.open_git_blame_commit_internal(window, cx);
20236 }
20237
20238 fn open_git_blame_commit_internal(
20239 &mut self,
20240 window: &mut Window,
20241 cx: &mut Context<Self>,
20242 ) -> Option<()> {
20243 let blame = self.blame.as_ref()?;
20244 let snapshot = self.snapshot(window, cx);
20245 let cursor = self
20246 .selections
20247 .newest::<Point>(&snapshot.display_snapshot)
20248 .head();
20249 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20250 let (_, blame_entry) = blame
20251 .update(cx, |blame, cx| {
20252 blame
20253 .blame_for_rows(
20254 &[RowInfo {
20255 buffer_id: Some(buffer.remote_id()),
20256 buffer_row: Some(point.row),
20257 ..Default::default()
20258 }],
20259 cx,
20260 )
20261 .next()
20262 })
20263 .flatten()?;
20264 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20265 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20266 let workspace = self.workspace()?.downgrade();
20267 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20268 None
20269 }
20270
20271 pub fn git_blame_inline_enabled(&self) -> bool {
20272 self.git_blame_inline_enabled
20273 }
20274
20275 pub fn toggle_selection_menu(
20276 &mut self,
20277 _: &ToggleSelectionMenu,
20278 _: &mut Window,
20279 cx: &mut Context<Self>,
20280 ) {
20281 self.show_selection_menu = self
20282 .show_selection_menu
20283 .map(|show_selections_menu| !show_selections_menu)
20284 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20285
20286 cx.notify();
20287 }
20288
20289 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20290 self.show_selection_menu
20291 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20292 }
20293
20294 fn start_git_blame(
20295 &mut self,
20296 user_triggered: bool,
20297 window: &mut Window,
20298 cx: &mut Context<Self>,
20299 ) {
20300 if let Some(project) = self.project() {
20301 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20302 && buffer.read(cx).file().is_none()
20303 {
20304 return;
20305 }
20306
20307 let focused = self.focus_handle(cx).contains_focused(window, cx);
20308
20309 let project = project.clone();
20310 let blame = cx
20311 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20312 self.blame_subscription =
20313 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20314 self.blame = Some(blame);
20315 }
20316 }
20317
20318 fn toggle_git_blame_inline_internal(
20319 &mut self,
20320 user_triggered: bool,
20321 window: &mut Window,
20322 cx: &mut Context<Self>,
20323 ) {
20324 if self.git_blame_inline_enabled {
20325 self.git_blame_inline_enabled = false;
20326 self.show_git_blame_inline = false;
20327 self.show_git_blame_inline_delay_task.take();
20328 } else {
20329 self.git_blame_inline_enabled = true;
20330 self.start_git_blame_inline(user_triggered, window, cx);
20331 }
20332
20333 cx.notify();
20334 }
20335
20336 fn start_git_blame_inline(
20337 &mut self,
20338 user_triggered: bool,
20339 window: &mut Window,
20340 cx: &mut Context<Self>,
20341 ) {
20342 self.start_git_blame(user_triggered, window, cx);
20343
20344 if ProjectSettings::get_global(cx)
20345 .git
20346 .inline_blame_delay()
20347 .is_some()
20348 {
20349 self.start_inline_blame_timer(window, cx);
20350 } else {
20351 self.show_git_blame_inline = true
20352 }
20353 }
20354
20355 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20356 self.blame.as_ref()
20357 }
20358
20359 pub fn show_git_blame_gutter(&self) -> bool {
20360 self.show_git_blame_gutter
20361 }
20362
20363 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20364 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20365 }
20366
20367 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20368 self.show_git_blame_inline
20369 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20370 && !self.newest_selection_head_on_empty_line(cx)
20371 && self.has_blame_entries(cx)
20372 }
20373
20374 fn has_blame_entries(&self, cx: &App) -> bool {
20375 self.blame()
20376 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20377 }
20378
20379 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20380 let cursor_anchor = self.selections.newest_anchor().head();
20381
20382 let snapshot = self.buffer.read(cx).snapshot(cx);
20383 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20384
20385 snapshot.line_len(buffer_row) == 0
20386 }
20387
20388 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20389 let buffer_and_selection = maybe!({
20390 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20391 let selection_range = selection.range();
20392
20393 let multi_buffer = self.buffer().read(cx);
20394 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20395 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20396
20397 let (buffer, range, _) = if selection.reversed {
20398 buffer_ranges.first()
20399 } else {
20400 buffer_ranges.last()
20401 }?;
20402
20403 let selection = text::ToPoint::to_point(&range.start, buffer).row
20404 ..text::ToPoint::to_point(&range.end, buffer).row;
20405 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20406 });
20407
20408 let Some((buffer, selection)) = buffer_and_selection else {
20409 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20410 };
20411
20412 let Some(project) = self.project() else {
20413 return Task::ready(Err(anyhow!("editor does not have project")));
20414 };
20415
20416 project.update(cx, |project, cx| {
20417 project.get_permalink_to_line(&buffer, selection, cx)
20418 })
20419 }
20420
20421 pub fn copy_permalink_to_line(
20422 &mut self,
20423 _: &CopyPermalinkToLine,
20424 window: &mut Window,
20425 cx: &mut Context<Self>,
20426 ) {
20427 let permalink_task = self.get_permalink_to_line(cx);
20428 let workspace = self.workspace();
20429
20430 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20431 Ok(permalink) => {
20432 cx.update(|_, cx| {
20433 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20434 })
20435 .ok();
20436 }
20437 Err(err) => {
20438 let message = format!("Failed to copy permalink: {err}");
20439
20440 anyhow::Result::<()>::Err(err).log_err();
20441
20442 if let Some(workspace) = workspace {
20443 workspace
20444 .update_in(cx, |workspace, _, cx| {
20445 struct CopyPermalinkToLine;
20446
20447 workspace.show_toast(
20448 Toast::new(
20449 NotificationId::unique::<CopyPermalinkToLine>(),
20450 message,
20451 ),
20452 cx,
20453 )
20454 })
20455 .ok();
20456 }
20457 }
20458 })
20459 .detach();
20460 }
20461
20462 pub fn copy_file_location(
20463 &mut self,
20464 _: &CopyFileLocation,
20465 _: &mut Window,
20466 cx: &mut Context<Self>,
20467 ) {
20468 let selection = self
20469 .selections
20470 .newest::<Point>(&self.display_snapshot(cx))
20471 .start
20472 .row
20473 + 1;
20474 if let Some(file) = self.target_file(cx) {
20475 let path = file.path().display(file.path_style(cx));
20476 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20477 }
20478 }
20479
20480 pub fn open_permalink_to_line(
20481 &mut self,
20482 _: &OpenPermalinkToLine,
20483 window: &mut Window,
20484 cx: &mut Context<Self>,
20485 ) {
20486 let permalink_task = self.get_permalink_to_line(cx);
20487 let workspace = self.workspace();
20488
20489 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20490 Ok(permalink) => {
20491 cx.update(|_, cx| {
20492 cx.open_url(permalink.as_ref());
20493 })
20494 .ok();
20495 }
20496 Err(err) => {
20497 let message = format!("Failed to open permalink: {err}");
20498
20499 anyhow::Result::<()>::Err(err).log_err();
20500
20501 if let Some(workspace) = workspace {
20502 workspace
20503 .update(cx, |workspace, cx| {
20504 struct OpenPermalinkToLine;
20505
20506 workspace.show_toast(
20507 Toast::new(
20508 NotificationId::unique::<OpenPermalinkToLine>(),
20509 message,
20510 ),
20511 cx,
20512 )
20513 })
20514 .ok();
20515 }
20516 }
20517 })
20518 .detach();
20519 }
20520
20521 pub fn insert_uuid_v4(
20522 &mut self,
20523 _: &InsertUuidV4,
20524 window: &mut Window,
20525 cx: &mut Context<Self>,
20526 ) {
20527 self.insert_uuid(UuidVersion::V4, window, cx);
20528 }
20529
20530 pub fn insert_uuid_v7(
20531 &mut self,
20532 _: &InsertUuidV7,
20533 window: &mut Window,
20534 cx: &mut Context<Self>,
20535 ) {
20536 self.insert_uuid(UuidVersion::V7, window, cx);
20537 }
20538
20539 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20540 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20541 self.transact(window, cx, |this, window, cx| {
20542 let edits = this
20543 .selections
20544 .all::<Point>(&this.display_snapshot(cx))
20545 .into_iter()
20546 .map(|selection| {
20547 let uuid = match version {
20548 UuidVersion::V4 => uuid::Uuid::new_v4(),
20549 UuidVersion::V7 => uuid::Uuid::now_v7(),
20550 };
20551
20552 (selection.range(), uuid.to_string())
20553 });
20554 this.edit(edits, cx);
20555 this.refresh_edit_prediction(true, false, window, cx);
20556 });
20557 }
20558
20559 pub fn open_selections_in_multibuffer(
20560 &mut self,
20561 _: &OpenSelectionsInMultibuffer,
20562 window: &mut Window,
20563 cx: &mut Context<Self>,
20564 ) {
20565 let multibuffer = self.buffer.read(cx);
20566
20567 let Some(buffer) = multibuffer.as_singleton() else {
20568 return;
20569 };
20570
20571 let Some(workspace) = self.workspace() else {
20572 return;
20573 };
20574
20575 let title = multibuffer.title(cx).to_string();
20576
20577 let locations = self
20578 .selections
20579 .all_anchors(&self.display_snapshot(cx))
20580 .iter()
20581 .map(|selection| {
20582 (
20583 buffer.clone(),
20584 (selection.start.text_anchor..selection.end.text_anchor)
20585 .to_point(buffer.read(cx)),
20586 )
20587 })
20588 .into_group_map();
20589
20590 cx.spawn_in(window, async move |_, cx| {
20591 workspace.update_in(cx, |workspace, window, cx| {
20592 Self::open_locations_in_multibuffer(
20593 workspace,
20594 locations,
20595 format!("Selections for '{title}'"),
20596 false,
20597 MultibufferSelectionMode::All,
20598 window,
20599 cx,
20600 );
20601 })
20602 })
20603 .detach();
20604 }
20605
20606 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20607 /// last highlight added will be used.
20608 ///
20609 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20610 pub fn highlight_rows<T: 'static>(
20611 &mut self,
20612 range: Range<Anchor>,
20613 color: Hsla,
20614 options: RowHighlightOptions,
20615 cx: &mut Context<Self>,
20616 ) {
20617 let snapshot = self.buffer().read(cx).snapshot(cx);
20618 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20619 let ix = row_highlights.binary_search_by(|highlight| {
20620 Ordering::Equal
20621 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20622 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20623 });
20624
20625 if let Err(mut ix) = ix {
20626 let index = post_inc(&mut self.highlight_order);
20627
20628 // If this range intersects with the preceding highlight, then merge it with
20629 // the preceding highlight. Otherwise insert a new highlight.
20630 let mut merged = false;
20631 if ix > 0 {
20632 let prev_highlight = &mut row_highlights[ix - 1];
20633 if prev_highlight
20634 .range
20635 .end
20636 .cmp(&range.start, &snapshot)
20637 .is_ge()
20638 {
20639 ix -= 1;
20640 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20641 prev_highlight.range.end = range.end;
20642 }
20643 merged = true;
20644 prev_highlight.index = index;
20645 prev_highlight.color = color;
20646 prev_highlight.options = options;
20647 }
20648 }
20649
20650 if !merged {
20651 row_highlights.insert(
20652 ix,
20653 RowHighlight {
20654 range,
20655 index,
20656 color,
20657 options,
20658 type_id: TypeId::of::<T>(),
20659 },
20660 );
20661 }
20662
20663 // If any of the following highlights intersect with this one, merge them.
20664 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20665 let highlight = &row_highlights[ix];
20666 if next_highlight
20667 .range
20668 .start
20669 .cmp(&highlight.range.end, &snapshot)
20670 .is_le()
20671 {
20672 if next_highlight
20673 .range
20674 .end
20675 .cmp(&highlight.range.end, &snapshot)
20676 .is_gt()
20677 {
20678 row_highlights[ix].range.end = next_highlight.range.end;
20679 }
20680 row_highlights.remove(ix + 1);
20681 } else {
20682 break;
20683 }
20684 }
20685 }
20686 }
20687
20688 /// Remove any highlighted row ranges of the given type that intersect the
20689 /// given ranges.
20690 pub fn remove_highlighted_rows<T: 'static>(
20691 &mut self,
20692 ranges_to_remove: Vec<Range<Anchor>>,
20693 cx: &mut Context<Self>,
20694 ) {
20695 let snapshot = self.buffer().read(cx).snapshot(cx);
20696 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20697 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20698 row_highlights.retain(|highlight| {
20699 while let Some(range_to_remove) = ranges_to_remove.peek() {
20700 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20701 Ordering::Less | Ordering::Equal => {
20702 ranges_to_remove.next();
20703 }
20704 Ordering::Greater => {
20705 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20706 Ordering::Less | Ordering::Equal => {
20707 return false;
20708 }
20709 Ordering::Greater => break,
20710 }
20711 }
20712 }
20713 }
20714
20715 true
20716 })
20717 }
20718
20719 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20720 pub fn clear_row_highlights<T: 'static>(&mut self) {
20721 self.highlighted_rows.remove(&TypeId::of::<T>());
20722 }
20723
20724 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20725 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20726 self.highlighted_rows
20727 .get(&TypeId::of::<T>())
20728 .map_or(&[] as &[_], |vec| vec.as_slice())
20729 .iter()
20730 .map(|highlight| (highlight.range.clone(), highlight.color))
20731 }
20732
20733 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20734 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20735 /// Allows to ignore certain kinds of highlights.
20736 pub fn highlighted_display_rows(
20737 &self,
20738 window: &mut Window,
20739 cx: &mut App,
20740 ) -> BTreeMap<DisplayRow, LineHighlight> {
20741 let snapshot = self.snapshot(window, cx);
20742 let mut used_highlight_orders = HashMap::default();
20743 self.highlighted_rows
20744 .iter()
20745 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20746 .fold(
20747 BTreeMap::<DisplayRow, LineHighlight>::new(),
20748 |mut unique_rows, highlight| {
20749 let start = highlight.range.start.to_display_point(&snapshot);
20750 let end = highlight.range.end.to_display_point(&snapshot);
20751 let start_row = start.row().0;
20752 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20753 && end.column() == 0
20754 {
20755 end.row().0.saturating_sub(1)
20756 } else {
20757 end.row().0
20758 };
20759 for row in start_row..=end_row {
20760 let used_index =
20761 used_highlight_orders.entry(row).or_insert(highlight.index);
20762 if highlight.index >= *used_index {
20763 *used_index = highlight.index;
20764 unique_rows.insert(
20765 DisplayRow(row),
20766 LineHighlight {
20767 include_gutter: highlight.options.include_gutter,
20768 border: None,
20769 background: highlight.color.into(),
20770 type_id: Some(highlight.type_id),
20771 },
20772 );
20773 }
20774 }
20775 unique_rows
20776 },
20777 )
20778 }
20779
20780 pub fn highlighted_display_row_for_autoscroll(
20781 &self,
20782 snapshot: &DisplaySnapshot,
20783 ) -> Option<DisplayRow> {
20784 self.highlighted_rows
20785 .values()
20786 .flat_map(|highlighted_rows| highlighted_rows.iter())
20787 .filter_map(|highlight| {
20788 if highlight.options.autoscroll {
20789 Some(highlight.range.start.to_display_point(snapshot).row())
20790 } else {
20791 None
20792 }
20793 })
20794 .min()
20795 }
20796
20797 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20798 self.highlight_background::<SearchWithinRange>(
20799 ranges,
20800 |colors| colors.colors().editor_document_highlight_read_background,
20801 cx,
20802 )
20803 }
20804
20805 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20806 self.breadcrumb_header = Some(new_header);
20807 }
20808
20809 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20810 self.clear_background_highlights::<SearchWithinRange>(cx);
20811 }
20812
20813 pub fn highlight_background<T: 'static>(
20814 &mut self,
20815 ranges: &[Range<Anchor>],
20816 color_fetcher: fn(&Theme) -> Hsla,
20817 cx: &mut Context<Self>,
20818 ) {
20819 self.background_highlights.insert(
20820 HighlightKey::Type(TypeId::of::<T>()),
20821 (color_fetcher, Arc::from(ranges)),
20822 );
20823 self.scrollbar_marker_state.dirty = true;
20824 cx.notify();
20825 }
20826
20827 pub fn highlight_background_key<T: 'static>(
20828 &mut self,
20829 key: usize,
20830 ranges: &[Range<Anchor>],
20831 color_fetcher: fn(&Theme) -> Hsla,
20832 cx: &mut Context<Self>,
20833 ) {
20834 self.background_highlights.insert(
20835 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20836 (color_fetcher, Arc::from(ranges)),
20837 );
20838 self.scrollbar_marker_state.dirty = true;
20839 cx.notify();
20840 }
20841
20842 pub fn clear_background_highlights<T: 'static>(
20843 &mut self,
20844 cx: &mut Context<Self>,
20845 ) -> Option<BackgroundHighlight> {
20846 let text_highlights = self
20847 .background_highlights
20848 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20849 if !text_highlights.1.is_empty() {
20850 self.scrollbar_marker_state.dirty = true;
20851 cx.notify();
20852 }
20853 Some(text_highlights)
20854 }
20855
20856 pub fn highlight_gutter<T: 'static>(
20857 &mut self,
20858 ranges: impl Into<Vec<Range<Anchor>>>,
20859 color_fetcher: fn(&App) -> Hsla,
20860 cx: &mut Context<Self>,
20861 ) {
20862 self.gutter_highlights
20863 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20864 cx.notify();
20865 }
20866
20867 pub fn clear_gutter_highlights<T: 'static>(
20868 &mut self,
20869 cx: &mut Context<Self>,
20870 ) -> Option<GutterHighlight> {
20871 cx.notify();
20872 self.gutter_highlights.remove(&TypeId::of::<T>())
20873 }
20874
20875 pub fn insert_gutter_highlight<T: 'static>(
20876 &mut self,
20877 range: Range<Anchor>,
20878 color_fetcher: fn(&App) -> Hsla,
20879 cx: &mut Context<Self>,
20880 ) {
20881 let snapshot = self.buffer().read(cx).snapshot(cx);
20882 let mut highlights = self
20883 .gutter_highlights
20884 .remove(&TypeId::of::<T>())
20885 .map(|(_, highlights)| highlights)
20886 .unwrap_or_default();
20887 let ix = highlights.binary_search_by(|highlight| {
20888 Ordering::Equal
20889 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20890 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20891 });
20892 if let Err(ix) = ix {
20893 highlights.insert(ix, range);
20894 }
20895 self.gutter_highlights
20896 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20897 }
20898
20899 pub fn remove_gutter_highlights<T: 'static>(
20900 &mut self,
20901 ranges_to_remove: Vec<Range<Anchor>>,
20902 cx: &mut Context<Self>,
20903 ) {
20904 let snapshot = self.buffer().read(cx).snapshot(cx);
20905 let Some((color_fetcher, mut gutter_highlights)) =
20906 self.gutter_highlights.remove(&TypeId::of::<T>())
20907 else {
20908 return;
20909 };
20910 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20911 gutter_highlights.retain(|highlight| {
20912 while let Some(range_to_remove) = ranges_to_remove.peek() {
20913 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20914 Ordering::Less | Ordering::Equal => {
20915 ranges_to_remove.next();
20916 }
20917 Ordering::Greater => {
20918 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20919 Ordering::Less | Ordering::Equal => {
20920 return false;
20921 }
20922 Ordering::Greater => break,
20923 }
20924 }
20925 }
20926 }
20927
20928 true
20929 });
20930 self.gutter_highlights
20931 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20932 }
20933
20934 #[cfg(feature = "test-support")]
20935 pub fn all_text_highlights(
20936 &self,
20937 window: &mut Window,
20938 cx: &mut Context<Self>,
20939 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20940 let snapshot = self.snapshot(window, cx);
20941 self.display_map.update(cx, |display_map, _| {
20942 display_map
20943 .all_text_highlights()
20944 .map(|highlight| {
20945 let (style, ranges) = highlight.as_ref();
20946 (
20947 *style,
20948 ranges
20949 .iter()
20950 .map(|range| range.clone().to_display_points(&snapshot))
20951 .collect(),
20952 )
20953 })
20954 .collect()
20955 })
20956 }
20957
20958 #[cfg(feature = "test-support")]
20959 pub fn all_text_background_highlights(
20960 &self,
20961 window: &mut Window,
20962 cx: &mut Context<Self>,
20963 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20964 let snapshot = self.snapshot(window, cx);
20965 let buffer = &snapshot.buffer_snapshot();
20966 let start = buffer.anchor_before(MultiBufferOffset(0));
20967 let end = buffer.anchor_after(buffer.len());
20968 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20969 }
20970
20971 #[cfg(any(test, feature = "test-support"))]
20972 pub fn sorted_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 res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20979 res.sort_by(|a, b| {
20980 a.0.start
20981 .cmp(&b.0.start)
20982 .then_with(|| a.0.end.cmp(&b.0.end))
20983 .then_with(|| a.1.cmp(&b.1))
20984 });
20985 res
20986 }
20987
20988 #[cfg(feature = "test-support")]
20989 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20990 let snapshot = self.buffer().read(cx).snapshot(cx);
20991
20992 let highlights = self
20993 .background_highlights
20994 .get(&HighlightKey::Type(TypeId::of::<
20995 items::BufferSearchHighlights,
20996 >()));
20997
20998 if let Some((_color, ranges)) = highlights {
20999 ranges
21000 .iter()
21001 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21002 .collect_vec()
21003 } else {
21004 vec![]
21005 }
21006 }
21007
21008 fn document_highlights_for_position<'a>(
21009 &'a self,
21010 position: Anchor,
21011 buffer: &'a MultiBufferSnapshot,
21012 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21013 let read_highlights = self
21014 .background_highlights
21015 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21016 .map(|h| &h.1);
21017 let write_highlights = self
21018 .background_highlights
21019 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21020 .map(|h| &h.1);
21021 let left_position = position.bias_left(buffer);
21022 let right_position = position.bias_right(buffer);
21023 read_highlights
21024 .into_iter()
21025 .chain(write_highlights)
21026 .flat_map(move |ranges| {
21027 let start_ix = match ranges.binary_search_by(|probe| {
21028 let cmp = probe.end.cmp(&left_position, buffer);
21029 if cmp.is_ge() {
21030 Ordering::Greater
21031 } else {
21032 Ordering::Less
21033 }
21034 }) {
21035 Ok(i) | Err(i) => i,
21036 };
21037
21038 ranges[start_ix..]
21039 .iter()
21040 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21041 })
21042 }
21043
21044 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21045 self.background_highlights
21046 .get(&HighlightKey::Type(TypeId::of::<T>()))
21047 .is_some_and(|(_, highlights)| !highlights.is_empty())
21048 }
21049
21050 /// Returns all background highlights for a given range.
21051 ///
21052 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21053 pub fn background_highlights_in_range(
21054 &self,
21055 search_range: Range<Anchor>,
21056 display_snapshot: &DisplaySnapshot,
21057 theme: &Theme,
21058 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21059 let mut results = Vec::new();
21060 for (color_fetcher, ranges) in self.background_highlights.values() {
21061 let color = color_fetcher(theme);
21062 let start_ix = match ranges.binary_search_by(|probe| {
21063 let cmp = probe
21064 .end
21065 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21066 if cmp.is_gt() {
21067 Ordering::Greater
21068 } else {
21069 Ordering::Less
21070 }
21071 }) {
21072 Ok(i) | Err(i) => i,
21073 };
21074 for range in &ranges[start_ix..] {
21075 if range
21076 .start
21077 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21078 .is_ge()
21079 {
21080 break;
21081 }
21082
21083 let start = range.start.to_display_point(display_snapshot);
21084 let end = range.end.to_display_point(display_snapshot);
21085 results.push((start..end, color))
21086 }
21087 }
21088 results
21089 }
21090
21091 pub fn gutter_highlights_in_range(
21092 &self,
21093 search_range: Range<Anchor>,
21094 display_snapshot: &DisplaySnapshot,
21095 cx: &App,
21096 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21097 let mut results = Vec::new();
21098 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21099 let color = color_fetcher(cx);
21100 let start_ix = match ranges.binary_search_by(|probe| {
21101 let cmp = probe
21102 .end
21103 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21104 if cmp.is_gt() {
21105 Ordering::Greater
21106 } else {
21107 Ordering::Less
21108 }
21109 }) {
21110 Ok(i) | Err(i) => i,
21111 };
21112 for range in &ranges[start_ix..] {
21113 if range
21114 .start
21115 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21116 .is_ge()
21117 {
21118 break;
21119 }
21120
21121 let start = range.start.to_display_point(display_snapshot);
21122 let end = range.end.to_display_point(display_snapshot);
21123 results.push((start..end, color))
21124 }
21125 }
21126 results
21127 }
21128
21129 /// Get the text ranges corresponding to the redaction query
21130 pub fn redacted_ranges(
21131 &self,
21132 search_range: Range<Anchor>,
21133 display_snapshot: &DisplaySnapshot,
21134 cx: &App,
21135 ) -> Vec<Range<DisplayPoint>> {
21136 display_snapshot
21137 .buffer_snapshot()
21138 .redacted_ranges(search_range, |file| {
21139 if let Some(file) = file {
21140 file.is_private()
21141 && EditorSettings::get(
21142 Some(SettingsLocation {
21143 worktree_id: file.worktree_id(cx),
21144 path: file.path().as_ref(),
21145 }),
21146 cx,
21147 )
21148 .redact_private_values
21149 } else {
21150 false
21151 }
21152 })
21153 .map(|range| {
21154 range.start.to_display_point(display_snapshot)
21155 ..range.end.to_display_point(display_snapshot)
21156 })
21157 .collect()
21158 }
21159
21160 pub fn highlight_text_key<T: 'static>(
21161 &mut self,
21162 key: usize,
21163 ranges: Vec<Range<Anchor>>,
21164 style: HighlightStyle,
21165 cx: &mut Context<Self>,
21166 ) {
21167 self.display_map.update(cx, |map, _| {
21168 map.highlight_text(
21169 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21170 ranges,
21171 style,
21172 );
21173 });
21174 cx.notify();
21175 }
21176
21177 pub fn highlight_text<T: 'static>(
21178 &mut self,
21179 ranges: Vec<Range<Anchor>>,
21180 style: HighlightStyle,
21181 cx: &mut Context<Self>,
21182 ) {
21183 self.display_map.update(cx, |map, _| {
21184 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
21185 });
21186 cx.notify();
21187 }
21188
21189 pub fn text_highlights<'a, T: 'static>(
21190 &'a self,
21191 cx: &'a App,
21192 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21193 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21194 }
21195
21196 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21197 let cleared = self
21198 .display_map
21199 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21200 if cleared {
21201 cx.notify();
21202 }
21203 }
21204
21205 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21206 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21207 && self.focus_handle.is_focused(window)
21208 }
21209
21210 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21211 self.show_cursor_when_unfocused = is_enabled;
21212 cx.notify();
21213 }
21214
21215 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21216 cx.notify();
21217 }
21218
21219 fn on_debug_session_event(
21220 &mut self,
21221 _session: Entity<Session>,
21222 event: &SessionEvent,
21223 cx: &mut Context<Self>,
21224 ) {
21225 if let SessionEvent::InvalidateInlineValue = event {
21226 self.refresh_inline_values(cx);
21227 }
21228 }
21229
21230 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21231 let Some(project) = self.project.clone() else {
21232 return;
21233 };
21234
21235 if !self.inline_value_cache.enabled {
21236 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21237 self.splice_inlays(&inlays, Vec::new(), cx);
21238 return;
21239 }
21240
21241 let current_execution_position = self
21242 .highlighted_rows
21243 .get(&TypeId::of::<ActiveDebugLine>())
21244 .and_then(|lines| lines.last().map(|line| line.range.end));
21245
21246 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21247 let inline_values = editor
21248 .update(cx, |editor, cx| {
21249 let Some(current_execution_position) = current_execution_position else {
21250 return Some(Task::ready(Ok(Vec::new())));
21251 };
21252
21253 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21254 let snapshot = buffer.snapshot(cx);
21255
21256 let excerpt = snapshot.excerpt_containing(
21257 current_execution_position..current_execution_position,
21258 )?;
21259
21260 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21261 })?;
21262
21263 let range =
21264 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21265
21266 project.inline_values(buffer, range, cx)
21267 })
21268 .ok()
21269 .flatten()?
21270 .await
21271 .context("refreshing debugger inlays")
21272 .log_err()?;
21273
21274 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21275
21276 for (buffer_id, inline_value) in inline_values
21277 .into_iter()
21278 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21279 {
21280 buffer_inline_values
21281 .entry(buffer_id)
21282 .or_default()
21283 .push(inline_value);
21284 }
21285
21286 editor
21287 .update(cx, |editor, cx| {
21288 let snapshot = editor.buffer.read(cx).snapshot(cx);
21289 let mut new_inlays = Vec::default();
21290
21291 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21292 let buffer_id = buffer_snapshot.remote_id();
21293 buffer_inline_values
21294 .get(&buffer_id)
21295 .into_iter()
21296 .flatten()
21297 .for_each(|hint| {
21298 let inlay = Inlay::debugger(
21299 post_inc(&mut editor.next_inlay_id),
21300 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
21301 hint.text(),
21302 );
21303 if !inlay.text().chars().contains(&'\n') {
21304 new_inlays.push(inlay);
21305 }
21306 });
21307 }
21308
21309 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21310 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21311
21312 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21313 })
21314 .ok()?;
21315 Some(())
21316 });
21317 }
21318
21319 fn on_buffer_event(
21320 &mut self,
21321 multibuffer: &Entity<MultiBuffer>,
21322 event: &multi_buffer::Event,
21323 window: &mut Window,
21324 cx: &mut Context<Self>,
21325 ) {
21326 match event {
21327 multi_buffer::Event::Edited { edited_buffer } => {
21328 self.scrollbar_marker_state.dirty = true;
21329 self.active_indent_guides_state.dirty = true;
21330 self.refresh_active_diagnostics(cx);
21331 self.refresh_code_actions(window, cx);
21332 self.refresh_selected_text_highlights(true, window, cx);
21333 self.refresh_single_line_folds(window, cx);
21334 self.refresh_matching_bracket_highlights(window, cx);
21335 if self.has_active_edit_prediction() {
21336 self.update_visible_edit_prediction(window, cx);
21337 }
21338
21339 if let Some(buffer) = edited_buffer {
21340 if buffer.read(cx).file().is_none() {
21341 cx.emit(EditorEvent::TitleChanged);
21342 }
21343
21344 if self.project.is_some() {
21345 let buffer_id = buffer.read(cx).remote_id();
21346 self.register_buffer(buffer_id, cx);
21347 self.update_lsp_data(Some(buffer_id), window, cx);
21348 self.refresh_inlay_hints(
21349 InlayHintRefreshReason::BufferEdited(buffer_id),
21350 cx,
21351 );
21352 }
21353 }
21354
21355 cx.emit(EditorEvent::BufferEdited);
21356 cx.emit(SearchEvent::MatchesInvalidated);
21357
21358 let Some(project) = &self.project else { return };
21359 let (telemetry, is_via_ssh) = {
21360 let project = project.read(cx);
21361 let telemetry = project.client().telemetry().clone();
21362 let is_via_ssh = project.is_via_remote_server();
21363 (telemetry, is_via_ssh)
21364 };
21365 telemetry.log_edit_event("editor", is_via_ssh);
21366 }
21367 multi_buffer::Event::ExcerptsAdded {
21368 buffer,
21369 predecessor,
21370 excerpts,
21371 } => {
21372 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21373 let buffer_id = buffer.read(cx).remote_id();
21374 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21375 && let Some(project) = &self.project
21376 {
21377 update_uncommitted_diff_for_buffer(
21378 cx.entity(),
21379 project,
21380 [buffer.clone()],
21381 self.buffer.clone(),
21382 cx,
21383 )
21384 .detach();
21385 }
21386 self.update_lsp_data(Some(buffer_id), window, cx);
21387 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21388 cx.emit(EditorEvent::ExcerptsAdded {
21389 buffer: buffer.clone(),
21390 predecessor: *predecessor,
21391 excerpts: excerpts.clone(),
21392 });
21393 }
21394 multi_buffer::Event::ExcerptsRemoved {
21395 ids,
21396 removed_buffer_ids,
21397 } => {
21398 if let Some(inlay_hints) = &mut self.inlay_hints {
21399 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21400 }
21401 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21402 for buffer_id in removed_buffer_ids {
21403 self.registered_buffers.remove(buffer_id);
21404 }
21405 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21406 cx.emit(EditorEvent::ExcerptsRemoved {
21407 ids: ids.clone(),
21408 removed_buffer_ids: removed_buffer_ids.clone(),
21409 });
21410 }
21411 multi_buffer::Event::ExcerptsEdited {
21412 excerpt_ids,
21413 buffer_ids,
21414 } => {
21415 self.display_map.update(cx, |map, cx| {
21416 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21417 });
21418 cx.emit(EditorEvent::ExcerptsEdited {
21419 ids: excerpt_ids.clone(),
21420 });
21421 }
21422 multi_buffer::Event::ExcerptsExpanded { ids } => {
21423 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21424 self.refresh_document_highlights(cx);
21425 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21426 }
21427 multi_buffer::Event::Reparsed(buffer_id) => {
21428 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21429 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21430
21431 cx.emit(EditorEvent::Reparsed(*buffer_id));
21432 }
21433 multi_buffer::Event::DiffHunksToggled => {
21434 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21435 }
21436 multi_buffer::Event::LanguageChanged(buffer_id) => {
21437 self.registered_buffers.remove(&buffer_id);
21438 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21439 cx.emit(EditorEvent::Reparsed(*buffer_id));
21440 cx.notify();
21441 }
21442 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21443 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21444 multi_buffer::Event::FileHandleChanged
21445 | multi_buffer::Event::Reloaded
21446 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21447 multi_buffer::Event::DiagnosticsUpdated => {
21448 self.update_diagnostics_state(window, cx);
21449 }
21450 _ => {}
21451 };
21452 }
21453
21454 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21455 if !self.diagnostics_enabled() {
21456 return;
21457 }
21458 self.refresh_active_diagnostics(cx);
21459 self.refresh_inline_diagnostics(true, window, cx);
21460 self.scrollbar_marker_state.dirty = true;
21461 cx.notify();
21462 }
21463
21464 pub fn start_temporary_diff_override(&mut self) {
21465 self.load_diff_task.take();
21466 self.temporary_diff_override = true;
21467 }
21468
21469 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21470 self.temporary_diff_override = false;
21471 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21472 self.buffer.update(cx, |buffer, cx| {
21473 buffer.set_all_diff_hunks_collapsed(cx);
21474 });
21475
21476 if let Some(project) = self.project.clone() {
21477 self.load_diff_task = Some(
21478 update_uncommitted_diff_for_buffer(
21479 cx.entity(),
21480 &project,
21481 self.buffer.read(cx).all_buffers(),
21482 self.buffer.clone(),
21483 cx,
21484 )
21485 .shared(),
21486 );
21487 }
21488 }
21489
21490 fn on_display_map_changed(
21491 &mut self,
21492 _: Entity<DisplayMap>,
21493 _: &mut Window,
21494 cx: &mut Context<Self>,
21495 ) {
21496 cx.notify();
21497 }
21498
21499 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21500 if self.diagnostics_enabled() {
21501 let new_severity = EditorSettings::get_global(cx)
21502 .diagnostics_max_severity
21503 .unwrap_or(DiagnosticSeverity::Hint);
21504 self.set_max_diagnostics_severity(new_severity, cx);
21505 }
21506 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21507 self.update_edit_prediction_settings(cx);
21508 self.refresh_edit_prediction(true, false, window, cx);
21509 self.refresh_inline_values(cx);
21510 self.refresh_inlay_hints(
21511 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21512 self.selections.newest_anchor().head(),
21513 &self.buffer.read(cx).snapshot(cx),
21514 cx,
21515 )),
21516 cx,
21517 );
21518
21519 let old_cursor_shape = self.cursor_shape;
21520 let old_show_breadcrumbs = self.show_breadcrumbs;
21521
21522 {
21523 let editor_settings = EditorSettings::get_global(cx);
21524 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21525 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21526 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21527 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21528 }
21529
21530 if old_cursor_shape != self.cursor_shape {
21531 cx.emit(EditorEvent::CursorShapeChanged);
21532 }
21533
21534 if old_show_breadcrumbs != self.show_breadcrumbs {
21535 cx.emit(EditorEvent::BreadcrumbsChanged);
21536 }
21537
21538 let project_settings = ProjectSettings::get_global(cx);
21539 self.buffer_serialization = self
21540 .should_serialize_buffer()
21541 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21542
21543 if self.mode.is_full() {
21544 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21545 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21546 if self.show_inline_diagnostics != show_inline_diagnostics {
21547 self.show_inline_diagnostics = show_inline_diagnostics;
21548 self.refresh_inline_diagnostics(false, window, cx);
21549 }
21550
21551 if self.git_blame_inline_enabled != inline_blame_enabled {
21552 self.toggle_git_blame_inline_internal(false, window, cx);
21553 }
21554
21555 let minimap_settings = EditorSettings::get_global(cx).minimap;
21556 if self.minimap_visibility != MinimapVisibility::Disabled {
21557 if self.minimap_visibility.settings_visibility()
21558 != minimap_settings.minimap_enabled()
21559 {
21560 self.set_minimap_visibility(
21561 MinimapVisibility::for_mode(self.mode(), cx),
21562 window,
21563 cx,
21564 );
21565 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21566 minimap_entity.update(cx, |minimap_editor, cx| {
21567 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21568 })
21569 }
21570 }
21571 }
21572
21573 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21574 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21575 }) {
21576 if !inlay_splice.is_empty() {
21577 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21578 }
21579 self.refresh_colors_for_visible_range(None, window, cx);
21580 }
21581
21582 cx.notify();
21583 }
21584
21585 pub fn set_searchable(&mut self, searchable: bool) {
21586 self.searchable = searchable;
21587 }
21588
21589 pub fn searchable(&self) -> bool {
21590 self.searchable
21591 }
21592
21593 pub fn open_excerpts_in_split(
21594 &mut self,
21595 _: &OpenExcerptsSplit,
21596 window: &mut Window,
21597 cx: &mut Context<Self>,
21598 ) {
21599 self.open_excerpts_common(None, true, window, cx)
21600 }
21601
21602 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21603 self.open_excerpts_common(None, false, window, cx)
21604 }
21605
21606 fn open_excerpts_common(
21607 &mut self,
21608 jump_data: Option<JumpData>,
21609 split: bool,
21610 window: &mut Window,
21611 cx: &mut Context<Self>,
21612 ) {
21613 let Some(workspace) = self.workspace() else {
21614 cx.propagate();
21615 return;
21616 };
21617
21618 if self.buffer.read(cx).is_singleton() {
21619 cx.propagate();
21620 return;
21621 }
21622
21623 let mut new_selections_by_buffer = HashMap::default();
21624 match &jump_data {
21625 Some(JumpData::MultiBufferPoint {
21626 excerpt_id,
21627 position,
21628 anchor,
21629 line_offset_from_top,
21630 }) => {
21631 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21632 if let Some(buffer) = multi_buffer_snapshot
21633 .buffer_id_for_excerpt(*excerpt_id)
21634 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21635 {
21636 let buffer_snapshot = buffer.read(cx).snapshot();
21637 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21638 language::ToPoint::to_point(anchor, &buffer_snapshot)
21639 } else {
21640 buffer_snapshot.clip_point(*position, Bias::Left)
21641 };
21642 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21643 new_selections_by_buffer.insert(
21644 buffer,
21645 (
21646 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
21647 Some(*line_offset_from_top),
21648 ),
21649 );
21650 }
21651 }
21652 Some(JumpData::MultiBufferRow {
21653 row,
21654 line_offset_from_top,
21655 }) => {
21656 let point = MultiBufferPoint::new(row.0, 0);
21657 if let Some((buffer, buffer_point, _)) =
21658 self.buffer.read(cx).point_to_buffer_point(point, cx)
21659 {
21660 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21661 new_selections_by_buffer
21662 .entry(buffer)
21663 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21664 .0
21665 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
21666 }
21667 }
21668 None => {
21669 let selections = self
21670 .selections
21671 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
21672 let multi_buffer = self.buffer.read(cx);
21673 for selection in selections {
21674 for (snapshot, range, _, anchor) in multi_buffer
21675 .snapshot(cx)
21676 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21677 {
21678 if let Some(anchor) = anchor {
21679 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21680 else {
21681 continue;
21682 };
21683 let offset = text::ToOffset::to_offset(
21684 &anchor.text_anchor,
21685 &buffer_handle.read(cx).snapshot(),
21686 );
21687 let range = BufferOffset(offset)..BufferOffset(offset);
21688 new_selections_by_buffer
21689 .entry(buffer_handle)
21690 .or_insert((Vec::new(), None))
21691 .0
21692 .push(range)
21693 } else {
21694 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21695 else {
21696 continue;
21697 };
21698 new_selections_by_buffer
21699 .entry(buffer_handle)
21700 .or_insert((Vec::new(), None))
21701 .0
21702 .push(range)
21703 }
21704 }
21705 }
21706 }
21707 }
21708
21709 new_selections_by_buffer
21710 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21711
21712 if new_selections_by_buffer.is_empty() {
21713 return;
21714 }
21715
21716 // We defer the pane interaction because we ourselves are a workspace item
21717 // and activating a new item causes the pane to call a method on us reentrantly,
21718 // which panics if we're on the stack.
21719 window.defer(cx, move |window, cx| {
21720 workspace.update(cx, |workspace, cx| {
21721 let pane = if split {
21722 workspace.adjacent_pane(window, cx)
21723 } else {
21724 workspace.active_pane().clone()
21725 };
21726
21727 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21728 let editor = buffer
21729 .read(cx)
21730 .file()
21731 .is_none()
21732 .then(|| {
21733 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21734 // so `workspace.open_project_item` will never find them, always opening a new editor.
21735 // Instead, we try to activate the existing editor in the pane first.
21736 let (editor, pane_item_index) =
21737 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21738 let editor = item.downcast::<Editor>()?;
21739 let singleton_buffer =
21740 editor.read(cx).buffer().read(cx).as_singleton()?;
21741 if singleton_buffer == buffer {
21742 Some((editor, i))
21743 } else {
21744 None
21745 }
21746 })?;
21747 pane.update(cx, |pane, cx| {
21748 pane.activate_item(pane_item_index, true, true, window, cx)
21749 });
21750 Some(editor)
21751 })
21752 .flatten()
21753 .unwrap_or_else(|| {
21754 workspace.open_project_item::<Self>(
21755 pane.clone(),
21756 buffer,
21757 true,
21758 true,
21759 window,
21760 cx,
21761 )
21762 });
21763
21764 editor.update(cx, |editor, cx| {
21765 let autoscroll = match scroll_offset {
21766 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21767 None => Autoscroll::newest(),
21768 };
21769 let nav_history = editor.nav_history.take();
21770 editor.change_selections(
21771 SelectionEffects::scroll(autoscroll),
21772 window,
21773 cx,
21774 |s| {
21775 s.select_ranges(ranges.into_iter().map(|range| {
21776 // we checked that the editor is a singleton editor so the offsets are valid
21777 MultiBufferOffset(range.start.0)..MultiBufferOffset(range.end.0)
21778 }));
21779 },
21780 );
21781 editor.nav_history = nav_history;
21782 });
21783 }
21784 })
21785 });
21786 }
21787
21788 // For now, don't allow opening excerpts in buffers that aren't backed by
21789 // regular project files.
21790 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21791 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21792 }
21793
21794 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
21795 let snapshot = self.buffer.read(cx).read(cx);
21796 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21797 Some(
21798 ranges
21799 .iter()
21800 .map(move |range| {
21801 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21802 })
21803 .collect(),
21804 )
21805 }
21806
21807 fn selection_replacement_ranges(
21808 &self,
21809 range: Range<MultiBufferOffsetUtf16>,
21810 cx: &mut App,
21811 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
21812 let selections = self
21813 .selections
21814 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
21815 let newest_selection = selections
21816 .iter()
21817 .max_by_key(|selection| selection.id)
21818 .unwrap();
21819 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
21820 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
21821 let snapshot = self.buffer.read(cx).read(cx);
21822 selections
21823 .into_iter()
21824 .map(|mut selection| {
21825 selection.start.0.0 =
21826 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
21827 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
21828 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21829 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21830 })
21831 .collect()
21832 }
21833
21834 fn report_editor_event(
21835 &self,
21836 reported_event: ReportEditorEvent,
21837 file_extension: Option<String>,
21838 cx: &App,
21839 ) {
21840 if cfg!(any(test, feature = "test-support")) {
21841 return;
21842 }
21843
21844 let Some(project) = &self.project else { return };
21845
21846 // If None, we are in a file without an extension
21847 let file = self
21848 .buffer
21849 .read(cx)
21850 .as_singleton()
21851 .and_then(|b| b.read(cx).file());
21852 let file_extension = file_extension.or(file
21853 .as_ref()
21854 .and_then(|file| Path::new(file.file_name(cx)).extension())
21855 .and_then(|e| e.to_str())
21856 .map(|a| a.to_string()));
21857
21858 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
21859 .map(|vim_mode| vim_mode.0)
21860 .unwrap_or(false);
21861
21862 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21863 let copilot_enabled = edit_predictions_provider
21864 == language::language_settings::EditPredictionProvider::Copilot;
21865 let copilot_enabled_for_language = self
21866 .buffer
21867 .read(cx)
21868 .language_settings(cx)
21869 .show_edit_predictions;
21870
21871 let project = project.read(cx);
21872 let event_type = reported_event.event_type();
21873
21874 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21875 telemetry::event!(
21876 event_type,
21877 type = if auto_saved {"autosave"} else {"manual"},
21878 file_extension,
21879 vim_mode,
21880 copilot_enabled,
21881 copilot_enabled_for_language,
21882 edit_predictions_provider,
21883 is_via_ssh = project.is_via_remote_server(),
21884 );
21885 } else {
21886 telemetry::event!(
21887 event_type,
21888 file_extension,
21889 vim_mode,
21890 copilot_enabled,
21891 copilot_enabled_for_language,
21892 edit_predictions_provider,
21893 is_via_ssh = project.is_via_remote_server(),
21894 );
21895 };
21896 }
21897
21898 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21899 /// with each line being an array of {text, highlight} objects.
21900 fn copy_highlight_json(
21901 &mut self,
21902 _: &CopyHighlightJson,
21903 window: &mut Window,
21904 cx: &mut Context<Self>,
21905 ) {
21906 #[derive(Serialize)]
21907 struct Chunk<'a> {
21908 text: String,
21909 highlight: Option<&'a str>,
21910 }
21911
21912 let snapshot = self.buffer.read(cx).snapshot(cx);
21913 let range = self
21914 .selected_text_range(false, window, cx)
21915 .and_then(|selection| {
21916 if selection.range.is_empty() {
21917 None
21918 } else {
21919 Some(
21920 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
21921 selection.range.start,
21922 )))
21923 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
21924 selection.range.end,
21925 ))),
21926 )
21927 }
21928 })
21929 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
21930
21931 let chunks = snapshot.chunks(range, true);
21932 let mut lines = Vec::new();
21933 let mut line: VecDeque<Chunk> = VecDeque::new();
21934
21935 let Some(style) = self.style.as_ref() else {
21936 return;
21937 };
21938
21939 for chunk in chunks {
21940 let highlight = chunk
21941 .syntax_highlight_id
21942 .and_then(|id| id.name(&style.syntax));
21943 let mut chunk_lines = chunk.text.split('\n').peekable();
21944 while let Some(text) = chunk_lines.next() {
21945 let mut merged_with_last_token = false;
21946 if let Some(last_token) = line.back_mut()
21947 && last_token.highlight == highlight
21948 {
21949 last_token.text.push_str(text);
21950 merged_with_last_token = true;
21951 }
21952
21953 if !merged_with_last_token {
21954 line.push_back(Chunk {
21955 text: text.into(),
21956 highlight,
21957 });
21958 }
21959
21960 if chunk_lines.peek().is_some() {
21961 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21962 line.pop_front();
21963 }
21964 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21965 line.pop_back();
21966 }
21967
21968 lines.push(mem::take(&mut line));
21969 }
21970 }
21971 }
21972
21973 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21974 return;
21975 };
21976 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21977 }
21978
21979 pub fn open_context_menu(
21980 &mut self,
21981 _: &OpenContextMenu,
21982 window: &mut Window,
21983 cx: &mut Context<Self>,
21984 ) {
21985 self.request_autoscroll(Autoscroll::newest(), cx);
21986 let position = self
21987 .selections
21988 .newest_display(&self.display_snapshot(cx))
21989 .start;
21990 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21991 }
21992
21993 pub fn replay_insert_event(
21994 &mut self,
21995 text: &str,
21996 relative_utf16_range: Option<Range<isize>>,
21997 window: &mut Window,
21998 cx: &mut Context<Self>,
21999 ) {
22000 if !self.input_enabled {
22001 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22002 return;
22003 }
22004 if let Some(relative_utf16_range) = relative_utf16_range {
22005 let selections = self
22006 .selections
22007 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22008 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22009 let new_ranges = selections.into_iter().map(|range| {
22010 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22011 range
22012 .head()
22013 .0
22014 .0
22015 .saturating_add_signed(relative_utf16_range.start),
22016 ));
22017 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22018 range
22019 .head()
22020 .0
22021 .0
22022 .saturating_add_signed(relative_utf16_range.end),
22023 ));
22024 start..end
22025 });
22026 s.select_ranges(new_ranges);
22027 });
22028 }
22029
22030 self.handle_input(text, window, cx);
22031 }
22032
22033 pub fn is_focused(&self, window: &Window) -> bool {
22034 self.focus_handle.is_focused(window)
22035 }
22036
22037 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22038 cx.emit(EditorEvent::Focused);
22039
22040 if let Some(descendant) = self
22041 .last_focused_descendant
22042 .take()
22043 .and_then(|descendant| descendant.upgrade())
22044 {
22045 window.focus(&descendant);
22046 } else {
22047 if let Some(blame) = self.blame.as_ref() {
22048 blame.update(cx, GitBlame::focus)
22049 }
22050
22051 self.blink_manager.update(cx, BlinkManager::enable);
22052 self.show_cursor_names(window, cx);
22053 self.buffer.update(cx, |buffer, cx| {
22054 buffer.finalize_last_transaction(cx);
22055 if self.leader_id.is_none() {
22056 buffer.set_active_selections(
22057 &self.selections.disjoint_anchors_arc(),
22058 self.selections.line_mode(),
22059 self.cursor_shape,
22060 cx,
22061 );
22062 }
22063 });
22064 }
22065 }
22066
22067 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22068 cx.emit(EditorEvent::FocusedIn)
22069 }
22070
22071 fn handle_focus_out(
22072 &mut self,
22073 event: FocusOutEvent,
22074 _window: &mut Window,
22075 cx: &mut Context<Self>,
22076 ) {
22077 if event.blurred != self.focus_handle {
22078 self.last_focused_descendant = Some(event.blurred);
22079 }
22080 self.selection_drag_state = SelectionDragState::None;
22081 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22082 }
22083
22084 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22085 self.blink_manager.update(cx, BlinkManager::disable);
22086 self.buffer
22087 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22088
22089 if let Some(blame) = self.blame.as_ref() {
22090 blame.update(cx, GitBlame::blur)
22091 }
22092 if !self.hover_state.focused(window, cx) {
22093 hide_hover(self, cx);
22094 }
22095 if !self
22096 .context_menu
22097 .borrow()
22098 .as_ref()
22099 .is_some_and(|context_menu| context_menu.focused(window, cx))
22100 {
22101 self.hide_context_menu(window, cx);
22102 }
22103 self.take_active_edit_prediction(cx);
22104 cx.emit(EditorEvent::Blurred);
22105 cx.notify();
22106 }
22107
22108 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22109 let mut pending: String = window
22110 .pending_input_keystrokes()
22111 .into_iter()
22112 .flatten()
22113 .filter_map(|keystroke| {
22114 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
22115 keystroke.key_char.clone()
22116 } else {
22117 None
22118 }
22119 })
22120 .collect();
22121
22122 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22123 pending = "".to_string();
22124 }
22125
22126 let existing_pending = self
22127 .text_highlights::<PendingInput>(cx)
22128 .map(|(_, ranges)| ranges.to_vec());
22129 if existing_pending.is_none() && pending.is_empty() {
22130 return;
22131 }
22132 let transaction =
22133 self.transact(window, cx, |this, window, cx| {
22134 let selections = this
22135 .selections
22136 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22137 let edits = selections
22138 .iter()
22139 .map(|selection| (selection.end..selection.end, pending.clone()));
22140 this.edit(edits, cx);
22141 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22142 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22143 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22144 }));
22145 });
22146 if let Some(existing_ranges) = existing_pending {
22147 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22148 this.edit(edits, cx);
22149 }
22150 });
22151
22152 let snapshot = self.snapshot(window, cx);
22153 let ranges = self
22154 .selections
22155 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22156 .into_iter()
22157 .map(|selection| {
22158 snapshot.buffer_snapshot().anchor_after(selection.end)
22159 ..snapshot
22160 .buffer_snapshot()
22161 .anchor_before(selection.end + pending.len())
22162 })
22163 .collect();
22164
22165 if pending.is_empty() {
22166 self.clear_highlights::<PendingInput>(cx);
22167 } else {
22168 self.highlight_text::<PendingInput>(
22169 ranges,
22170 HighlightStyle {
22171 underline: Some(UnderlineStyle {
22172 thickness: px(1.),
22173 color: None,
22174 wavy: false,
22175 }),
22176 ..Default::default()
22177 },
22178 cx,
22179 );
22180 }
22181
22182 self.ime_transaction = self.ime_transaction.or(transaction);
22183 if let Some(transaction) = self.ime_transaction {
22184 self.buffer.update(cx, |buffer, cx| {
22185 buffer.group_until_transaction(transaction, cx);
22186 });
22187 }
22188
22189 if self.text_highlights::<PendingInput>(cx).is_none() {
22190 self.ime_transaction.take();
22191 }
22192 }
22193
22194 pub fn register_action_renderer(
22195 &mut self,
22196 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22197 ) -> Subscription {
22198 let id = self.next_editor_action_id.post_inc();
22199 self.editor_actions
22200 .borrow_mut()
22201 .insert(id, Box::new(listener));
22202
22203 let editor_actions = self.editor_actions.clone();
22204 Subscription::new(move || {
22205 editor_actions.borrow_mut().remove(&id);
22206 })
22207 }
22208
22209 pub fn register_action<A: Action>(
22210 &mut self,
22211 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22212 ) -> Subscription {
22213 let id = self.next_editor_action_id.post_inc();
22214 let listener = Arc::new(listener);
22215 self.editor_actions.borrow_mut().insert(
22216 id,
22217 Box::new(move |_, window, _| {
22218 let listener = listener.clone();
22219 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22220 let action = action.downcast_ref().unwrap();
22221 if phase == DispatchPhase::Bubble {
22222 listener(action, window, cx)
22223 }
22224 })
22225 }),
22226 );
22227
22228 let editor_actions = self.editor_actions.clone();
22229 Subscription::new(move || {
22230 editor_actions.borrow_mut().remove(&id);
22231 })
22232 }
22233
22234 pub fn file_header_size(&self) -> u32 {
22235 FILE_HEADER_HEIGHT
22236 }
22237
22238 pub fn restore(
22239 &mut self,
22240 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22241 window: &mut Window,
22242 cx: &mut Context<Self>,
22243 ) {
22244 let workspace = self.workspace();
22245 let project = self.project();
22246 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22247 let mut tasks = Vec::new();
22248 for (buffer_id, changes) in revert_changes {
22249 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22250 buffer.update(cx, |buffer, cx| {
22251 buffer.edit(
22252 changes
22253 .into_iter()
22254 .map(|(range, text)| (range, text.to_string())),
22255 None,
22256 cx,
22257 );
22258 });
22259
22260 if let Some(project) =
22261 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22262 {
22263 project.update(cx, |project, cx| {
22264 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22265 })
22266 }
22267 }
22268 }
22269 tasks
22270 });
22271 cx.spawn_in(window, async move |_, cx| {
22272 for (buffer, task) in save_tasks {
22273 let result = task.await;
22274 if result.is_err() {
22275 let Some(path) = buffer
22276 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22277 .ok()
22278 else {
22279 continue;
22280 };
22281 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22282 let Some(task) = cx
22283 .update_window_entity(workspace, |workspace, window, cx| {
22284 workspace
22285 .open_path_preview(path, None, false, false, false, window, cx)
22286 })
22287 .ok()
22288 else {
22289 continue;
22290 };
22291 task.await.log_err();
22292 }
22293 }
22294 }
22295 })
22296 .detach();
22297 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22298 selections.refresh()
22299 });
22300 }
22301
22302 pub fn to_pixel_point(
22303 &self,
22304 source: multi_buffer::Anchor,
22305 editor_snapshot: &EditorSnapshot,
22306 window: &mut Window,
22307 ) -> Option<gpui::Point<Pixels>> {
22308 let source_point = source.to_display_point(editor_snapshot);
22309 self.display_to_pixel_point(source_point, editor_snapshot, window)
22310 }
22311
22312 pub fn display_to_pixel_point(
22313 &self,
22314 source: DisplayPoint,
22315 editor_snapshot: &EditorSnapshot,
22316 window: &mut Window,
22317 ) -> Option<gpui::Point<Pixels>> {
22318 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22319 let text_layout_details = self.text_layout_details(window);
22320 let scroll_top = text_layout_details
22321 .scroll_anchor
22322 .scroll_position(editor_snapshot)
22323 .y;
22324
22325 if source.row().as_f64() < scroll_top.floor() {
22326 return None;
22327 }
22328 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22329 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22330 Some(gpui::Point::new(source_x, source_y))
22331 }
22332
22333 pub fn has_visible_completions_menu(&self) -> bool {
22334 !self.edit_prediction_preview_is_active()
22335 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22336 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22337 })
22338 }
22339
22340 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22341 if self.mode.is_minimap() {
22342 return;
22343 }
22344 self.addons
22345 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22346 }
22347
22348 pub fn unregister_addon<T: Addon>(&mut self) {
22349 self.addons.remove(&std::any::TypeId::of::<T>());
22350 }
22351
22352 pub fn addon<T: Addon>(&self) -> Option<&T> {
22353 let type_id = std::any::TypeId::of::<T>();
22354 self.addons
22355 .get(&type_id)
22356 .and_then(|item| item.to_any().downcast_ref::<T>())
22357 }
22358
22359 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22360 let type_id = std::any::TypeId::of::<T>();
22361 self.addons
22362 .get_mut(&type_id)
22363 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22364 }
22365
22366 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22367 let text_layout_details = self.text_layout_details(window);
22368 let style = &text_layout_details.editor_style;
22369 let font_id = window.text_system().resolve_font(&style.text.font());
22370 let font_size = style.text.font_size.to_pixels(window.rem_size());
22371 let line_height = style.text.line_height_in_pixels(window.rem_size());
22372 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22373 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22374
22375 CharacterDimensions {
22376 em_width,
22377 em_advance,
22378 line_height,
22379 }
22380 }
22381
22382 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22383 self.load_diff_task.clone()
22384 }
22385
22386 fn read_metadata_from_db(
22387 &mut self,
22388 item_id: u64,
22389 workspace_id: WorkspaceId,
22390 window: &mut Window,
22391 cx: &mut Context<Editor>,
22392 ) {
22393 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22394 && !self.mode.is_minimap()
22395 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22396 {
22397 let buffer_snapshot = OnceCell::new();
22398
22399 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22400 && !folds.is_empty()
22401 {
22402 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22403 self.fold_ranges(
22404 folds
22405 .into_iter()
22406 .map(|(start, end)| {
22407 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22408 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22409 })
22410 .collect(),
22411 false,
22412 window,
22413 cx,
22414 );
22415 }
22416
22417 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22418 && !selections.is_empty()
22419 {
22420 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22421 // skip adding the initial selection to selection history
22422 self.selection_history.mode = SelectionHistoryMode::Skipping;
22423 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22424 s.select_ranges(selections.into_iter().map(|(start, end)| {
22425 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22426 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22427 }));
22428 });
22429 self.selection_history.mode = SelectionHistoryMode::Normal;
22430 };
22431 }
22432
22433 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22434 }
22435
22436 fn update_lsp_data(
22437 &mut self,
22438 for_buffer: Option<BufferId>,
22439 window: &mut Window,
22440 cx: &mut Context<'_, Self>,
22441 ) {
22442 self.pull_diagnostics(for_buffer, window, cx);
22443 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22444 }
22445
22446 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22447 if self.ignore_lsp_data() {
22448 return;
22449 }
22450 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22451 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22452 }
22453 }
22454
22455 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22456 if self.ignore_lsp_data() {
22457 return;
22458 }
22459
22460 if !self.registered_buffers.contains_key(&buffer_id)
22461 && let Some(project) = self.project.as_ref()
22462 {
22463 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22464 project.update(cx, |project, cx| {
22465 self.registered_buffers.insert(
22466 buffer_id,
22467 project.register_buffer_with_language_servers(&buffer, cx),
22468 );
22469 });
22470 } else {
22471 self.registered_buffers.remove(&buffer_id);
22472 }
22473 }
22474 }
22475
22476 fn ignore_lsp_data(&self) -> bool {
22477 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22478 // skip any LSP updates for it.
22479 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22480 }
22481}
22482
22483fn edit_for_markdown_paste<'a>(
22484 buffer: &MultiBufferSnapshot,
22485 range: Range<MultiBufferOffset>,
22486 to_insert: &'a str,
22487 url: Option<url::Url>,
22488) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
22489 if url.is_none() {
22490 return (range, Cow::Borrowed(to_insert));
22491 };
22492
22493 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22494
22495 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22496 Cow::Borrowed(to_insert)
22497 } else {
22498 Cow::Owned(format!("[{old_text}]({to_insert})"))
22499 };
22500 (range, new_text)
22501}
22502
22503fn process_completion_for_edit(
22504 completion: &Completion,
22505 intent: CompletionIntent,
22506 buffer: &Entity<Buffer>,
22507 cursor_position: &text::Anchor,
22508 cx: &mut Context<Editor>,
22509) -> CompletionEdit {
22510 let buffer = buffer.read(cx);
22511 let buffer_snapshot = buffer.snapshot();
22512 let (snippet, new_text) = if completion.is_snippet() {
22513 let mut snippet_source = completion.new_text.clone();
22514 // Workaround for typescript language server issues so that methods don't expand within
22515 // strings and functions with type expressions. The previous point is used because the query
22516 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22517 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22518 let previous_point = if previous_point.column > 0 {
22519 cursor_position.to_previous_offset(&buffer_snapshot)
22520 } else {
22521 cursor_position.to_offset(&buffer_snapshot)
22522 };
22523 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22524 && scope.prefers_label_for_snippet_in_completion()
22525 && let Some(label) = completion.label()
22526 && matches!(
22527 completion.kind(),
22528 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22529 )
22530 {
22531 snippet_source = label;
22532 }
22533 match Snippet::parse(&snippet_source).log_err() {
22534 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22535 None => (None, completion.new_text.clone()),
22536 }
22537 } else {
22538 (None, completion.new_text.clone())
22539 };
22540
22541 let mut range_to_replace = {
22542 let replace_range = &completion.replace_range;
22543 if let CompletionSource::Lsp {
22544 insert_range: Some(insert_range),
22545 ..
22546 } = &completion.source
22547 {
22548 debug_assert_eq!(
22549 insert_range.start, replace_range.start,
22550 "insert_range and replace_range should start at the same position"
22551 );
22552 debug_assert!(
22553 insert_range
22554 .start
22555 .cmp(cursor_position, &buffer_snapshot)
22556 .is_le(),
22557 "insert_range should start before or at cursor position"
22558 );
22559 debug_assert!(
22560 replace_range
22561 .start
22562 .cmp(cursor_position, &buffer_snapshot)
22563 .is_le(),
22564 "replace_range should start before or at cursor position"
22565 );
22566
22567 let should_replace = match intent {
22568 CompletionIntent::CompleteWithInsert => false,
22569 CompletionIntent::CompleteWithReplace => true,
22570 CompletionIntent::Complete | CompletionIntent::Compose => {
22571 let insert_mode =
22572 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22573 .completions
22574 .lsp_insert_mode;
22575 match insert_mode {
22576 LspInsertMode::Insert => false,
22577 LspInsertMode::Replace => true,
22578 LspInsertMode::ReplaceSubsequence => {
22579 let mut text_to_replace = buffer.chars_for_range(
22580 buffer.anchor_before(replace_range.start)
22581 ..buffer.anchor_after(replace_range.end),
22582 );
22583 let mut current_needle = text_to_replace.next();
22584 for haystack_ch in completion.label.text.chars() {
22585 if let Some(needle_ch) = current_needle
22586 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22587 {
22588 current_needle = text_to_replace.next();
22589 }
22590 }
22591 current_needle.is_none()
22592 }
22593 LspInsertMode::ReplaceSuffix => {
22594 if replace_range
22595 .end
22596 .cmp(cursor_position, &buffer_snapshot)
22597 .is_gt()
22598 {
22599 let range_after_cursor = *cursor_position..replace_range.end;
22600 let text_after_cursor = buffer
22601 .text_for_range(
22602 buffer.anchor_before(range_after_cursor.start)
22603 ..buffer.anchor_after(range_after_cursor.end),
22604 )
22605 .collect::<String>()
22606 .to_ascii_lowercase();
22607 completion
22608 .label
22609 .text
22610 .to_ascii_lowercase()
22611 .ends_with(&text_after_cursor)
22612 } else {
22613 true
22614 }
22615 }
22616 }
22617 }
22618 };
22619
22620 if should_replace {
22621 replace_range.clone()
22622 } else {
22623 insert_range.clone()
22624 }
22625 } else {
22626 replace_range.clone()
22627 }
22628 };
22629
22630 if range_to_replace
22631 .end
22632 .cmp(cursor_position, &buffer_snapshot)
22633 .is_lt()
22634 {
22635 range_to_replace.end = *cursor_position;
22636 }
22637
22638 let replace_range = range_to_replace.to_offset(buffer);
22639 CompletionEdit {
22640 new_text,
22641 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
22642 snippet,
22643 }
22644}
22645
22646struct CompletionEdit {
22647 new_text: String,
22648 replace_range: Range<BufferOffset>,
22649 snippet: Option<Snippet>,
22650}
22651
22652fn insert_extra_newline_brackets(
22653 buffer: &MultiBufferSnapshot,
22654 range: Range<MultiBufferOffset>,
22655 language: &language::LanguageScope,
22656) -> bool {
22657 let leading_whitespace_len = buffer
22658 .reversed_chars_at(range.start)
22659 .take_while(|c| c.is_whitespace() && *c != '\n')
22660 .map(|c| c.len_utf8())
22661 .sum::<usize>();
22662 let trailing_whitespace_len = buffer
22663 .chars_at(range.end)
22664 .take_while(|c| c.is_whitespace() && *c != '\n')
22665 .map(|c| c.len_utf8())
22666 .sum::<usize>();
22667 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22668
22669 language.brackets().any(|(pair, enabled)| {
22670 let pair_start = pair.start.trim_end();
22671 let pair_end = pair.end.trim_start();
22672
22673 enabled
22674 && pair.newline
22675 && buffer.contains_str_at(range.end, pair_end)
22676 && buffer.contains_str_at(
22677 range.start.saturating_sub_usize(pair_start.len()),
22678 pair_start,
22679 )
22680 })
22681}
22682
22683fn insert_extra_newline_tree_sitter(
22684 buffer: &MultiBufferSnapshot,
22685 range: Range<MultiBufferOffset>,
22686) -> bool {
22687 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22688 [(buffer, range, _)] => (*buffer, range.clone()),
22689 _ => return false,
22690 };
22691 let pair = {
22692 let mut result: Option<BracketMatch> = None;
22693
22694 for pair in buffer
22695 .all_bracket_ranges(range.start.0..range.end.0)
22696 .filter(move |pair| {
22697 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
22698 })
22699 {
22700 let len = pair.close_range.end - pair.open_range.start;
22701
22702 if let Some(existing) = &result {
22703 let existing_len = existing.close_range.end - existing.open_range.start;
22704 if len > existing_len {
22705 continue;
22706 }
22707 }
22708
22709 result = Some(pair);
22710 }
22711
22712 result
22713 };
22714 let Some(pair) = pair else {
22715 return false;
22716 };
22717 pair.newline_only
22718 && buffer
22719 .chars_for_range(pair.open_range.end..range.start.0)
22720 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
22721 .all(|c| c.is_whitespace() && c != '\n')
22722}
22723
22724fn update_uncommitted_diff_for_buffer(
22725 editor: Entity<Editor>,
22726 project: &Entity<Project>,
22727 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22728 buffer: Entity<MultiBuffer>,
22729 cx: &mut App,
22730) -> Task<()> {
22731 let mut tasks = Vec::new();
22732 project.update(cx, |project, cx| {
22733 for buffer in buffers {
22734 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22735 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22736 }
22737 }
22738 });
22739 cx.spawn(async move |cx| {
22740 let diffs = future::join_all(tasks).await;
22741 if editor
22742 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22743 .unwrap_or(false)
22744 {
22745 return;
22746 }
22747
22748 buffer
22749 .update(cx, |buffer, cx| {
22750 for diff in diffs.into_iter().flatten() {
22751 buffer.add_diff(diff, cx);
22752 }
22753 })
22754 .ok();
22755 })
22756}
22757
22758fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22759 let tab_size = tab_size.get() as usize;
22760 let mut width = offset;
22761
22762 for ch in text.chars() {
22763 width += if ch == '\t' {
22764 tab_size - (width % tab_size)
22765 } else {
22766 1
22767 };
22768 }
22769
22770 width - offset
22771}
22772
22773#[cfg(test)]
22774mod tests {
22775 use super::*;
22776
22777 #[test]
22778 fn test_string_size_with_expanded_tabs() {
22779 let nz = |val| NonZeroU32::new(val).unwrap();
22780 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22781 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22782 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22783 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22784 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22785 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22786 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22787 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22788 }
22789}
22790
22791/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22792struct WordBreakingTokenizer<'a> {
22793 input: &'a str,
22794}
22795
22796impl<'a> WordBreakingTokenizer<'a> {
22797 fn new(input: &'a str) -> Self {
22798 Self { input }
22799 }
22800}
22801
22802fn is_char_ideographic(ch: char) -> bool {
22803 use unicode_script::Script::*;
22804 use unicode_script::UnicodeScript;
22805 matches!(ch.script(), Han | Tangut | Yi)
22806}
22807
22808fn is_grapheme_ideographic(text: &str) -> bool {
22809 text.chars().any(is_char_ideographic)
22810}
22811
22812fn is_grapheme_whitespace(text: &str) -> bool {
22813 text.chars().any(|x| x.is_whitespace())
22814}
22815
22816fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22817 text.chars()
22818 .next()
22819 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22820}
22821
22822#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22823enum WordBreakToken<'a> {
22824 Word { token: &'a str, grapheme_len: usize },
22825 InlineWhitespace { token: &'a str, grapheme_len: usize },
22826 Newline,
22827}
22828
22829impl<'a> Iterator for WordBreakingTokenizer<'a> {
22830 /// Yields a span, the count of graphemes in the token, and whether it was
22831 /// whitespace. Note that it also breaks at word boundaries.
22832 type Item = WordBreakToken<'a>;
22833
22834 fn next(&mut self) -> Option<Self::Item> {
22835 use unicode_segmentation::UnicodeSegmentation;
22836 if self.input.is_empty() {
22837 return None;
22838 }
22839
22840 let mut iter = self.input.graphemes(true).peekable();
22841 let mut offset = 0;
22842 let mut grapheme_len = 0;
22843 if let Some(first_grapheme) = iter.next() {
22844 let is_newline = first_grapheme == "\n";
22845 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22846 offset += first_grapheme.len();
22847 grapheme_len += 1;
22848 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22849 if let Some(grapheme) = iter.peek().copied()
22850 && should_stay_with_preceding_ideograph(grapheme)
22851 {
22852 offset += grapheme.len();
22853 grapheme_len += 1;
22854 }
22855 } else {
22856 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22857 let mut next_word_bound = words.peek().copied();
22858 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22859 next_word_bound = words.next();
22860 }
22861 while let Some(grapheme) = iter.peek().copied() {
22862 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22863 break;
22864 };
22865 if is_grapheme_whitespace(grapheme) != is_whitespace
22866 || (grapheme == "\n") != is_newline
22867 {
22868 break;
22869 };
22870 offset += grapheme.len();
22871 grapheme_len += 1;
22872 iter.next();
22873 }
22874 }
22875 let token = &self.input[..offset];
22876 self.input = &self.input[offset..];
22877 if token == "\n" {
22878 Some(WordBreakToken::Newline)
22879 } else if is_whitespace {
22880 Some(WordBreakToken::InlineWhitespace {
22881 token,
22882 grapheme_len,
22883 })
22884 } else {
22885 Some(WordBreakToken::Word {
22886 token,
22887 grapheme_len,
22888 })
22889 }
22890 } else {
22891 None
22892 }
22893 }
22894}
22895
22896#[test]
22897fn test_word_breaking_tokenizer() {
22898 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22899 ("", &[]),
22900 (" ", &[whitespace(" ", 2)]),
22901 ("Ʒ", &[word("Ʒ", 1)]),
22902 ("Ǽ", &[word("Ǽ", 1)]),
22903 ("⋑", &[word("⋑", 1)]),
22904 ("⋑⋑", &[word("⋑⋑", 2)]),
22905 (
22906 "原理,进而",
22907 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22908 ),
22909 (
22910 "hello world",
22911 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22912 ),
22913 (
22914 "hello, world",
22915 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22916 ),
22917 (
22918 " hello world",
22919 &[
22920 whitespace(" ", 2),
22921 word("hello", 5),
22922 whitespace(" ", 1),
22923 word("world", 5),
22924 ],
22925 ),
22926 (
22927 "这是什么 \n 钢笔",
22928 &[
22929 word("这", 1),
22930 word("是", 1),
22931 word("什", 1),
22932 word("么", 1),
22933 whitespace(" ", 1),
22934 newline(),
22935 whitespace(" ", 1),
22936 word("钢", 1),
22937 word("笔", 1),
22938 ],
22939 ),
22940 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22941 ];
22942
22943 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22944 WordBreakToken::Word {
22945 token,
22946 grapheme_len,
22947 }
22948 }
22949
22950 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22951 WordBreakToken::InlineWhitespace {
22952 token,
22953 grapheme_len,
22954 }
22955 }
22956
22957 fn newline() -> WordBreakToken<'static> {
22958 WordBreakToken::Newline
22959 }
22960
22961 for (input, result) in tests {
22962 assert_eq!(
22963 WordBreakingTokenizer::new(input)
22964 .collect::<Vec<_>>()
22965 .as_slice(),
22966 *result,
22967 );
22968 }
22969}
22970
22971fn wrap_with_prefix(
22972 first_line_prefix: String,
22973 subsequent_lines_prefix: String,
22974 unwrapped_text: String,
22975 wrap_column: usize,
22976 tab_size: NonZeroU32,
22977 preserve_existing_whitespace: bool,
22978) -> String {
22979 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22980 let subsequent_lines_prefix_len =
22981 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22982 let mut wrapped_text = String::new();
22983 let mut current_line = first_line_prefix;
22984 let mut is_first_line = true;
22985
22986 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22987 let mut current_line_len = first_line_prefix_len;
22988 let mut in_whitespace = false;
22989 for token in tokenizer {
22990 let have_preceding_whitespace = in_whitespace;
22991 match token {
22992 WordBreakToken::Word {
22993 token,
22994 grapheme_len,
22995 } => {
22996 in_whitespace = false;
22997 let current_prefix_len = if is_first_line {
22998 first_line_prefix_len
22999 } else {
23000 subsequent_lines_prefix_len
23001 };
23002 if current_line_len + grapheme_len > wrap_column
23003 && current_line_len != current_prefix_len
23004 {
23005 wrapped_text.push_str(current_line.trim_end());
23006 wrapped_text.push('\n');
23007 is_first_line = false;
23008 current_line = subsequent_lines_prefix.clone();
23009 current_line_len = subsequent_lines_prefix_len;
23010 }
23011 current_line.push_str(token);
23012 current_line_len += grapheme_len;
23013 }
23014 WordBreakToken::InlineWhitespace {
23015 mut token,
23016 mut grapheme_len,
23017 } => {
23018 in_whitespace = true;
23019 if have_preceding_whitespace && !preserve_existing_whitespace {
23020 continue;
23021 }
23022 if !preserve_existing_whitespace {
23023 // Keep a single whitespace grapheme as-is
23024 if let Some(first) =
23025 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23026 {
23027 token = first;
23028 } else {
23029 token = " ";
23030 }
23031 grapheme_len = 1;
23032 }
23033 let current_prefix_len = if is_first_line {
23034 first_line_prefix_len
23035 } else {
23036 subsequent_lines_prefix_len
23037 };
23038 if current_line_len + grapheme_len > wrap_column {
23039 wrapped_text.push_str(current_line.trim_end());
23040 wrapped_text.push('\n');
23041 is_first_line = false;
23042 current_line = subsequent_lines_prefix.clone();
23043 current_line_len = subsequent_lines_prefix_len;
23044 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23045 current_line.push_str(token);
23046 current_line_len += grapheme_len;
23047 }
23048 }
23049 WordBreakToken::Newline => {
23050 in_whitespace = true;
23051 let current_prefix_len = if is_first_line {
23052 first_line_prefix_len
23053 } else {
23054 subsequent_lines_prefix_len
23055 };
23056 if preserve_existing_whitespace {
23057 wrapped_text.push_str(current_line.trim_end());
23058 wrapped_text.push('\n');
23059 is_first_line = false;
23060 current_line = subsequent_lines_prefix.clone();
23061 current_line_len = subsequent_lines_prefix_len;
23062 } else if have_preceding_whitespace {
23063 continue;
23064 } else if current_line_len + 1 > wrap_column
23065 && current_line_len != current_prefix_len
23066 {
23067 wrapped_text.push_str(current_line.trim_end());
23068 wrapped_text.push('\n');
23069 is_first_line = false;
23070 current_line = subsequent_lines_prefix.clone();
23071 current_line_len = subsequent_lines_prefix_len;
23072 } else if current_line_len != current_prefix_len {
23073 current_line.push(' ');
23074 current_line_len += 1;
23075 }
23076 }
23077 }
23078 }
23079
23080 if !current_line.is_empty() {
23081 wrapped_text.push_str(¤t_line);
23082 }
23083 wrapped_text
23084}
23085
23086#[test]
23087fn test_wrap_with_prefix() {
23088 assert_eq!(
23089 wrap_with_prefix(
23090 "# ".to_string(),
23091 "# ".to_string(),
23092 "abcdefg".to_string(),
23093 4,
23094 NonZeroU32::new(4).unwrap(),
23095 false,
23096 ),
23097 "# abcdefg"
23098 );
23099 assert_eq!(
23100 wrap_with_prefix(
23101 "".to_string(),
23102 "".to_string(),
23103 "\thello world".to_string(),
23104 8,
23105 NonZeroU32::new(4).unwrap(),
23106 false,
23107 ),
23108 "hello\nworld"
23109 );
23110 assert_eq!(
23111 wrap_with_prefix(
23112 "// ".to_string(),
23113 "// ".to_string(),
23114 "xx \nyy zz aa bb cc".to_string(),
23115 12,
23116 NonZeroU32::new(4).unwrap(),
23117 false,
23118 ),
23119 "// xx yy zz\n// aa bb cc"
23120 );
23121 assert_eq!(
23122 wrap_with_prefix(
23123 String::new(),
23124 String::new(),
23125 "这是什么 \n 钢笔".to_string(),
23126 3,
23127 NonZeroU32::new(4).unwrap(),
23128 false,
23129 ),
23130 "这是什\n么 钢\n笔"
23131 );
23132 assert_eq!(
23133 wrap_with_prefix(
23134 String::new(),
23135 String::new(),
23136 format!("foo{}bar", '\u{2009}'), // thin space
23137 80,
23138 NonZeroU32::new(4).unwrap(),
23139 false,
23140 ),
23141 format!("foo{}bar", '\u{2009}')
23142 );
23143}
23144
23145pub trait CollaborationHub {
23146 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23147 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23148 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23149}
23150
23151impl CollaborationHub for Entity<Project> {
23152 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23153 self.read(cx).collaborators()
23154 }
23155
23156 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23157 self.read(cx).user_store().read(cx).participant_indices()
23158 }
23159
23160 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23161 let this = self.read(cx);
23162 let user_ids = this.collaborators().values().map(|c| c.user_id);
23163 this.user_store().read(cx).participant_names(user_ids, cx)
23164 }
23165}
23166
23167pub trait SemanticsProvider {
23168 fn hover(
23169 &self,
23170 buffer: &Entity<Buffer>,
23171 position: text::Anchor,
23172 cx: &mut App,
23173 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23174
23175 fn inline_values(
23176 &self,
23177 buffer_handle: Entity<Buffer>,
23178 range: Range<text::Anchor>,
23179 cx: &mut App,
23180 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23181
23182 fn applicable_inlay_chunks(
23183 &self,
23184 buffer: &Entity<Buffer>,
23185 ranges: &[Range<text::Anchor>],
23186 cx: &mut App,
23187 ) -> Vec<Range<BufferRow>>;
23188
23189 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23190
23191 fn inlay_hints(
23192 &self,
23193 invalidate: InvalidationStrategy,
23194 buffer: Entity<Buffer>,
23195 ranges: Vec<Range<text::Anchor>>,
23196 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23197 cx: &mut App,
23198 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23199
23200 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23201
23202 fn document_highlights(
23203 &self,
23204 buffer: &Entity<Buffer>,
23205 position: text::Anchor,
23206 cx: &mut App,
23207 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23208
23209 fn definitions(
23210 &self,
23211 buffer: &Entity<Buffer>,
23212 position: text::Anchor,
23213 kind: GotoDefinitionKind,
23214 cx: &mut App,
23215 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23216
23217 fn range_for_rename(
23218 &self,
23219 buffer: &Entity<Buffer>,
23220 position: text::Anchor,
23221 cx: &mut App,
23222 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23223
23224 fn perform_rename(
23225 &self,
23226 buffer: &Entity<Buffer>,
23227 position: text::Anchor,
23228 new_name: String,
23229 cx: &mut App,
23230 ) -> Option<Task<Result<ProjectTransaction>>>;
23231}
23232
23233pub trait CompletionProvider {
23234 fn completions(
23235 &self,
23236 excerpt_id: ExcerptId,
23237 buffer: &Entity<Buffer>,
23238 buffer_position: text::Anchor,
23239 trigger: CompletionContext,
23240 window: &mut Window,
23241 cx: &mut Context<Editor>,
23242 ) -> Task<Result<Vec<CompletionResponse>>>;
23243
23244 fn resolve_completions(
23245 &self,
23246 _buffer: Entity<Buffer>,
23247 _completion_indices: Vec<usize>,
23248 _completions: Rc<RefCell<Box<[Completion]>>>,
23249 _cx: &mut Context<Editor>,
23250 ) -> Task<Result<bool>> {
23251 Task::ready(Ok(false))
23252 }
23253
23254 fn apply_additional_edits_for_completion(
23255 &self,
23256 _buffer: Entity<Buffer>,
23257 _completions: Rc<RefCell<Box<[Completion]>>>,
23258 _completion_index: usize,
23259 _push_to_history: bool,
23260 _cx: &mut Context<Editor>,
23261 ) -> Task<Result<Option<language::Transaction>>> {
23262 Task::ready(Ok(None))
23263 }
23264
23265 fn is_completion_trigger(
23266 &self,
23267 buffer: &Entity<Buffer>,
23268 position: language::Anchor,
23269 text: &str,
23270 trigger_in_words: bool,
23271 menu_is_open: bool,
23272 cx: &mut Context<Editor>,
23273 ) -> bool;
23274
23275 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23276
23277 fn sort_completions(&self) -> bool {
23278 true
23279 }
23280
23281 fn filter_completions(&self) -> bool {
23282 true
23283 }
23284
23285 fn show_snippets(&self) -> bool {
23286 false
23287 }
23288}
23289
23290pub trait CodeActionProvider {
23291 fn id(&self) -> Arc<str>;
23292
23293 fn code_actions(
23294 &self,
23295 buffer: &Entity<Buffer>,
23296 range: Range<text::Anchor>,
23297 window: &mut Window,
23298 cx: &mut App,
23299 ) -> Task<Result<Vec<CodeAction>>>;
23300
23301 fn apply_code_action(
23302 &self,
23303 buffer_handle: Entity<Buffer>,
23304 action: CodeAction,
23305 excerpt_id: ExcerptId,
23306 push_to_history: bool,
23307 window: &mut Window,
23308 cx: &mut App,
23309 ) -> Task<Result<ProjectTransaction>>;
23310}
23311
23312impl CodeActionProvider for Entity<Project> {
23313 fn id(&self) -> Arc<str> {
23314 "project".into()
23315 }
23316
23317 fn code_actions(
23318 &self,
23319 buffer: &Entity<Buffer>,
23320 range: Range<text::Anchor>,
23321 _window: &mut Window,
23322 cx: &mut App,
23323 ) -> Task<Result<Vec<CodeAction>>> {
23324 self.update(cx, |project, cx| {
23325 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23326 let code_actions = project.code_actions(buffer, range, None, cx);
23327 cx.background_spawn(async move {
23328 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23329 Ok(code_lens_actions
23330 .context("code lens fetch")?
23331 .into_iter()
23332 .flatten()
23333 .chain(
23334 code_actions
23335 .context("code action fetch")?
23336 .into_iter()
23337 .flatten(),
23338 )
23339 .collect())
23340 })
23341 })
23342 }
23343
23344 fn apply_code_action(
23345 &self,
23346 buffer_handle: Entity<Buffer>,
23347 action: CodeAction,
23348 _excerpt_id: ExcerptId,
23349 push_to_history: bool,
23350 _window: &mut Window,
23351 cx: &mut App,
23352 ) -> Task<Result<ProjectTransaction>> {
23353 self.update(cx, |project, cx| {
23354 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23355 })
23356 }
23357}
23358
23359fn snippet_completions(
23360 project: &Project,
23361 buffer: &Entity<Buffer>,
23362 buffer_anchor: text::Anchor,
23363 classifier: CharClassifier,
23364 cx: &mut App,
23365) -> Task<Result<CompletionResponse>> {
23366 let languages = buffer.read(cx).languages_at(buffer_anchor);
23367 let snippet_store = project.snippets().read(cx);
23368
23369 let scopes: Vec<_> = languages
23370 .iter()
23371 .filter_map(|language| {
23372 let language_name = language.lsp_id();
23373 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23374
23375 if snippets.is_empty() {
23376 None
23377 } else {
23378 Some((language.default_scope(), snippets))
23379 }
23380 })
23381 .collect();
23382
23383 if scopes.is_empty() {
23384 return Task::ready(Ok(CompletionResponse {
23385 completions: vec![],
23386 display_options: CompletionDisplayOptions::default(),
23387 is_incomplete: false,
23388 }));
23389 }
23390
23391 let snapshot = buffer.read(cx).text_snapshot();
23392 let executor = cx.background_executor().clone();
23393
23394 cx.background_spawn(async move {
23395 let is_word_char = |c| classifier.is_word(c);
23396
23397 let mut is_incomplete = false;
23398 let mut completions: Vec<Completion> = Vec::new();
23399
23400 const MAX_PREFIX_LEN: usize = 128;
23401 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
23402 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
23403 let window_start = snapshot.clip_offset(window_start, Bias::Left);
23404
23405 let max_buffer_window: String = snapshot
23406 .text_for_range(window_start..buffer_offset)
23407 .collect();
23408
23409 if max_buffer_window.is_empty() {
23410 return Ok(CompletionResponse {
23411 completions: vec![],
23412 display_options: CompletionDisplayOptions::default(),
23413 is_incomplete: true,
23414 });
23415 }
23416
23417 for (_scope, snippets) in scopes.into_iter() {
23418 // Sort snippets by word count to match longer snippet prefixes first.
23419 let mut sorted_snippet_candidates = snippets
23420 .iter()
23421 .enumerate()
23422 .flat_map(|(snippet_ix, snippet)| {
23423 snippet
23424 .prefix
23425 .iter()
23426 .enumerate()
23427 .map(move |(prefix_ix, prefix)| {
23428 let word_count =
23429 snippet_candidate_suffixes(prefix, is_word_char).count();
23430 ((snippet_ix, prefix_ix), prefix, word_count)
23431 })
23432 })
23433 .collect_vec();
23434 sorted_snippet_candidates
23435 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
23436
23437 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
23438
23439 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
23440 .take(
23441 sorted_snippet_candidates
23442 .first()
23443 .map(|(_, _, word_count)| *word_count)
23444 .unwrap_or_default(),
23445 )
23446 .collect_vec();
23447
23448 const MAX_RESULTS: usize = 100;
23449 // Each match also remembers how many characters from the buffer it consumed
23450 let mut matches: Vec<(StringMatch, usize)> = vec![];
23451
23452 let mut snippet_list_cutoff_index = 0;
23453 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
23454 let word_count = buffer_index + 1;
23455 // Increase `snippet_list_cutoff_index` until we have all of the
23456 // snippets with sufficiently many words.
23457 while sorted_snippet_candidates
23458 .get(snippet_list_cutoff_index)
23459 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
23460 *snippet_word_count >= word_count
23461 })
23462 {
23463 snippet_list_cutoff_index += 1;
23464 }
23465
23466 // Take only the candidates with at least `word_count` many words
23467 let snippet_candidates_at_word_len =
23468 &sorted_snippet_candidates[..snippet_list_cutoff_index];
23469
23470 let candidates = snippet_candidates_at_word_len
23471 .iter()
23472 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
23473 .enumerate() // index in `sorted_snippet_candidates`
23474 // First char must match
23475 .filter(|(_ix, prefix)| {
23476 itertools::equal(
23477 prefix
23478 .chars()
23479 .next()
23480 .into_iter()
23481 .flat_map(|c| c.to_lowercase()),
23482 buffer_window
23483 .chars()
23484 .next()
23485 .into_iter()
23486 .flat_map(|c| c.to_lowercase()),
23487 )
23488 })
23489 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
23490 .collect::<Vec<StringMatchCandidate>>();
23491
23492 matches.extend(
23493 fuzzy::match_strings(
23494 &candidates,
23495 &buffer_window,
23496 buffer_window.chars().any(|c| c.is_uppercase()),
23497 true,
23498 MAX_RESULTS - matches.len(), // always prioritize longer snippets
23499 &Default::default(),
23500 executor.clone(),
23501 )
23502 .await
23503 .into_iter()
23504 .map(|string_match| (string_match, buffer_window.len())),
23505 );
23506
23507 if matches.len() >= MAX_RESULTS {
23508 break;
23509 }
23510 }
23511
23512 let to_lsp = |point: &text::Anchor| {
23513 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23514 point_to_lsp(end)
23515 };
23516 let lsp_end = to_lsp(&buffer_anchor);
23517
23518 if matches.len() >= MAX_RESULTS {
23519 is_incomplete = true;
23520 }
23521
23522 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
23523 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
23524 sorted_snippet_candidates[string_match.candidate_id];
23525 let snippet = &snippets[snippet_index];
23526 let start = buffer_offset - buffer_window_len;
23527 let start = snapshot.anchor_before(start);
23528 let range = start..buffer_anchor;
23529 let lsp_start = to_lsp(&start);
23530 let lsp_range = lsp::Range {
23531 start: lsp_start,
23532 end: lsp_end,
23533 };
23534 Completion {
23535 replace_range: range,
23536 new_text: snippet.body.clone(),
23537 source: CompletionSource::Lsp {
23538 insert_range: None,
23539 server_id: LanguageServerId(usize::MAX),
23540 resolved: true,
23541 lsp_completion: Box::new(lsp::CompletionItem {
23542 label: snippet.prefix.first().unwrap().clone(),
23543 kind: Some(CompletionItemKind::SNIPPET),
23544 label_details: snippet.description.as_ref().map(|description| {
23545 lsp::CompletionItemLabelDetails {
23546 detail: Some(description.clone()),
23547 description: None,
23548 }
23549 }),
23550 insert_text_format: Some(InsertTextFormat::SNIPPET),
23551 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23552 lsp::InsertReplaceEdit {
23553 new_text: snippet.body.clone(),
23554 insert: lsp_range,
23555 replace: lsp_range,
23556 },
23557 )),
23558 filter_text: Some(snippet.body.clone()),
23559 sort_text: Some(char::MAX.to_string()),
23560 ..lsp::CompletionItem::default()
23561 }),
23562 lsp_defaults: None,
23563 },
23564 label: CodeLabel {
23565 text: matching_prefix.clone(),
23566 runs: Vec::new(),
23567 filter_range: 0..matching_prefix.len(),
23568 },
23569 icon_path: None,
23570 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23571 single_line: snippet.name.clone().into(),
23572 plain_text: snippet
23573 .description
23574 .clone()
23575 .map(|description| description.into()),
23576 }),
23577 insert_text_mode: None,
23578 confirm: None,
23579 match_start: Some(start),
23580 snippet_deduplication_key: Some((snippet_index, prefix_index)),
23581 }
23582 }));
23583 }
23584
23585 Ok(CompletionResponse {
23586 completions,
23587 display_options: CompletionDisplayOptions::default(),
23588 is_incomplete,
23589 })
23590 })
23591}
23592
23593impl CompletionProvider for Entity<Project> {
23594 fn completions(
23595 &self,
23596 _excerpt_id: ExcerptId,
23597 buffer: &Entity<Buffer>,
23598 buffer_position: text::Anchor,
23599 options: CompletionContext,
23600 _window: &mut Window,
23601 cx: &mut Context<Editor>,
23602 ) -> Task<Result<Vec<CompletionResponse>>> {
23603 self.update(cx, |project, cx| {
23604 let task = project.completions(buffer, buffer_position, options, cx);
23605 cx.background_spawn(task)
23606 })
23607 }
23608
23609 fn resolve_completions(
23610 &self,
23611 buffer: Entity<Buffer>,
23612 completion_indices: Vec<usize>,
23613 completions: Rc<RefCell<Box<[Completion]>>>,
23614 cx: &mut Context<Editor>,
23615 ) -> Task<Result<bool>> {
23616 self.update(cx, |project, cx| {
23617 project.lsp_store().update(cx, |lsp_store, cx| {
23618 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23619 })
23620 })
23621 }
23622
23623 fn apply_additional_edits_for_completion(
23624 &self,
23625 buffer: Entity<Buffer>,
23626 completions: Rc<RefCell<Box<[Completion]>>>,
23627 completion_index: usize,
23628 push_to_history: bool,
23629 cx: &mut Context<Editor>,
23630 ) -> Task<Result<Option<language::Transaction>>> {
23631 self.update(cx, |project, cx| {
23632 project.lsp_store().update(cx, |lsp_store, cx| {
23633 lsp_store.apply_additional_edits_for_completion(
23634 buffer,
23635 completions,
23636 completion_index,
23637 push_to_history,
23638 cx,
23639 )
23640 })
23641 })
23642 }
23643
23644 fn is_completion_trigger(
23645 &self,
23646 buffer: &Entity<Buffer>,
23647 position: language::Anchor,
23648 text: &str,
23649 trigger_in_words: bool,
23650 menu_is_open: bool,
23651 cx: &mut Context<Editor>,
23652 ) -> bool {
23653 let mut chars = text.chars();
23654 let char = if let Some(char) = chars.next() {
23655 char
23656 } else {
23657 return false;
23658 };
23659 if chars.next().is_some() {
23660 return false;
23661 }
23662
23663 let buffer = buffer.read(cx);
23664 let snapshot = buffer.snapshot();
23665 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23666 return false;
23667 }
23668 let classifier = snapshot
23669 .char_classifier_at(position)
23670 .scope_context(Some(CharScopeContext::Completion));
23671 if trigger_in_words && classifier.is_word(char) {
23672 return true;
23673 }
23674
23675 buffer.completion_triggers().contains(text)
23676 }
23677
23678 fn show_snippets(&self) -> bool {
23679 true
23680 }
23681}
23682
23683impl SemanticsProvider for Entity<Project> {
23684 fn hover(
23685 &self,
23686 buffer: &Entity<Buffer>,
23687 position: text::Anchor,
23688 cx: &mut App,
23689 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23690 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23691 }
23692
23693 fn document_highlights(
23694 &self,
23695 buffer: &Entity<Buffer>,
23696 position: text::Anchor,
23697 cx: &mut App,
23698 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23699 Some(self.update(cx, |project, cx| {
23700 project.document_highlights(buffer, position, cx)
23701 }))
23702 }
23703
23704 fn definitions(
23705 &self,
23706 buffer: &Entity<Buffer>,
23707 position: text::Anchor,
23708 kind: GotoDefinitionKind,
23709 cx: &mut App,
23710 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23711 Some(self.update(cx, |project, cx| match kind {
23712 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23713 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23714 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23715 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23716 }))
23717 }
23718
23719 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23720 self.update(cx, |project, cx| {
23721 if project
23722 .active_debug_session(cx)
23723 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23724 {
23725 return true;
23726 }
23727
23728 buffer.update(cx, |buffer, cx| {
23729 project.any_language_server_supports_inlay_hints(buffer, cx)
23730 })
23731 })
23732 }
23733
23734 fn inline_values(
23735 &self,
23736 buffer_handle: Entity<Buffer>,
23737 range: Range<text::Anchor>,
23738 cx: &mut App,
23739 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23740 self.update(cx, |project, cx| {
23741 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23742
23743 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23744 })
23745 }
23746
23747 fn applicable_inlay_chunks(
23748 &self,
23749 buffer: &Entity<Buffer>,
23750 ranges: &[Range<text::Anchor>],
23751 cx: &mut App,
23752 ) -> Vec<Range<BufferRow>> {
23753 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23754 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23755 })
23756 }
23757
23758 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23759 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23760 lsp_store.invalidate_inlay_hints(for_buffers)
23761 });
23762 }
23763
23764 fn inlay_hints(
23765 &self,
23766 invalidate: InvalidationStrategy,
23767 buffer: Entity<Buffer>,
23768 ranges: Vec<Range<text::Anchor>>,
23769 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23770 cx: &mut App,
23771 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23772 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23773 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23774 }))
23775 }
23776
23777 fn range_for_rename(
23778 &self,
23779 buffer: &Entity<Buffer>,
23780 position: text::Anchor,
23781 cx: &mut App,
23782 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23783 Some(self.update(cx, |project, cx| {
23784 let buffer = buffer.clone();
23785 let task = project.prepare_rename(buffer.clone(), position, cx);
23786 cx.spawn(async move |_, cx| {
23787 Ok(match task.await? {
23788 PrepareRenameResponse::Success(range) => Some(range),
23789 PrepareRenameResponse::InvalidPosition => None,
23790 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23791 // Fallback on using TreeSitter info to determine identifier range
23792 buffer.read_with(cx, |buffer, _| {
23793 let snapshot = buffer.snapshot();
23794 let (range, kind) = snapshot.surrounding_word(position, None);
23795 if kind != Some(CharKind::Word) {
23796 return None;
23797 }
23798 Some(
23799 snapshot.anchor_before(range.start)
23800 ..snapshot.anchor_after(range.end),
23801 )
23802 })?
23803 }
23804 })
23805 })
23806 }))
23807 }
23808
23809 fn perform_rename(
23810 &self,
23811 buffer: &Entity<Buffer>,
23812 position: text::Anchor,
23813 new_name: String,
23814 cx: &mut App,
23815 ) -> Option<Task<Result<ProjectTransaction>>> {
23816 Some(self.update(cx, |project, cx| {
23817 project.perform_rename(buffer.clone(), position, new_name, cx)
23818 }))
23819 }
23820}
23821
23822fn consume_contiguous_rows(
23823 contiguous_row_selections: &mut Vec<Selection<Point>>,
23824 selection: &Selection<Point>,
23825 display_map: &DisplaySnapshot,
23826 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23827) -> (MultiBufferRow, MultiBufferRow) {
23828 contiguous_row_selections.push(selection.clone());
23829 let start_row = starting_row(selection, display_map);
23830 let mut end_row = ending_row(selection, display_map);
23831
23832 while let Some(next_selection) = selections.peek() {
23833 if next_selection.start.row <= end_row.0 {
23834 end_row = ending_row(next_selection, display_map);
23835 contiguous_row_selections.push(selections.next().unwrap().clone());
23836 } else {
23837 break;
23838 }
23839 }
23840 (start_row, end_row)
23841}
23842
23843fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23844 if selection.start.column > 0 {
23845 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23846 } else {
23847 MultiBufferRow(selection.start.row)
23848 }
23849}
23850
23851fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23852 if next_selection.end.column > 0 || next_selection.is_empty() {
23853 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23854 } else {
23855 MultiBufferRow(next_selection.end.row)
23856 }
23857}
23858
23859impl EditorSnapshot {
23860 pub fn remote_selections_in_range<'a>(
23861 &'a self,
23862 range: &'a Range<Anchor>,
23863 collaboration_hub: &dyn CollaborationHub,
23864 cx: &'a App,
23865 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23866 let participant_names = collaboration_hub.user_names(cx);
23867 let participant_indices = collaboration_hub.user_participant_indices(cx);
23868 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23869 let collaborators_by_replica_id = collaborators_by_peer_id
23870 .values()
23871 .map(|collaborator| (collaborator.replica_id, collaborator))
23872 .collect::<HashMap<_, _>>();
23873 self.buffer_snapshot()
23874 .selections_in_range(range, false)
23875 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23876 if replica_id == ReplicaId::AGENT {
23877 Some(RemoteSelection {
23878 replica_id,
23879 selection,
23880 cursor_shape,
23881 line_mode,
23882 collaborator_id: CollaboratorId::Agent,
23883 user_name: Some("Agent".into()),
23884 color: cx.theme().players().agent(),
23885 })
23886 } else {
23887 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23888 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23889 let user_name = participant_names.get(&collaborator.user_id).cloned();
23890 Some(RemoteSelection {
23891 replica_id,
23892 selection,
23893 cursor_shape,
23894 line_mode,
23895 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23896 user_name,
23897 color: if let Some(index) = participant_index {
23898 cx.theme().players().color_for_participant(index.0)
23899 } else {
23900 cx.theme().players().absent()
23901 },
23902 })
23903 }
23904 })
23905 }
23906
23907 pub fn hunks_for_ranges(
23908 &self,
23909 ranges: impl IntoIterator<Item = Range<Point>>,
23910 ) -> Vec<MultiBufferDiffHunk> {
23911 let mut hunks = Vec::new();
23912 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23913 HashMap::default();
23914 for query_range in ranges {
23915 let query_rows =
23916 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23917 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23918 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23919 ) {
23920 // Include deleted hunks that are adjacent to the query range, because
23921 // otherwise they would be missed.
23922 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23923 if hunk.status().is_deleted() {
23924 intersects_range |= hunk.row_range.start == query_rows.end;
23925 intersects_range |= hunk.row_range.end == query_rows.start;
23926 }
23927 if intersects_range {
23928 if !processed_buffer_rows
23929 .entry(hunk.buffer_id)
23930 .or_default()
23931 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23932 {
23933 continue;
23934 }
23935 hunks.push(hunk);
23936 }
23937 }
23938 }
23939
23940 hunks
23941 }
23942
23943 fn display_diff_hunks_for_rows<'a>(
23944 &'a self,
23945 display_rows: Range<DisplayRow>,
23946 folded_buffers: &'a HashSet<BufferId>,
23947 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23948 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23949 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23950
23951 self.buffer_snapshot()
23952 .diff_hunks_in_range(buffer_start..buffer_end)
23953 .filter_map(|hunk| {
23954 if folded_buffers.contains(&hunk.buffer_id) {
23955 return None;
23956 }
23957
23958 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23959 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23960
23961 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23962 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23963
23964 let display_hunk = if hunk_display_start.column() != 0 {
23965 DisplayDiffHunk::Folded {
23966 display_row: hunk_display_start.row(),
23967 }
23968 } else {
23969 let mut end_row = hunk_display_end.row();
23970 if hunk_display_end.column() > 0 {
23971 end_row.0 += 1;
23972 }
23973 let is_created_file = hunk.is_created_file();
23974 DisplayDiffHunk::Unfolded {
23975 status: hunk.status(),
23976 diff_base_byte_range: hunk.diff_base_byte_range.start.0
23977 ..hunk.diff_base_byte_range.end.0,
23978 display_row_range: hunk_display_start.row()..end_row,
23979 multi_buffer_range: Anchor::range_in_buffer(
23980 hunk.excerpt_id,
23981 hunk.buffer_id,
23982 hunk.buffer_range,
23983 ),
23984 is_created_file,
23985 }
23986 };
23987
23988 Some(display_hunk)
23989 })
23990 }
23991
23992 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23993 self.display_snapshot
23994 .buffer_snapshot()
23995 .language_at(position)
23996 }
23997
23998 pub fn is_focused(&self) -> bool {
23999 self.is_focused
24000 }
24001
24002 pub fn placeholder_text(&self) -> Option<String> {
24003 self.placeholder_display_snapshot
24004 .as_ref()
24005 .map(|display_map| display_map.text())
24006 }
24007
24008 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24009 self.scroll_anchor.scroll_position(&self.display_snapshot)
24010 }
24011
24012 fn gutter_dimensions(
24013 &self,
24014 font_id: FontId,
24015 font_size: Pixels,
24016 max_line_number_width: Pixels,
24017 cx: &App,
24018 ) -> Option<GutterDimensions> {
24019 if !self.show_gutter {
24020 return None;
24021 }
24022
24023 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
24024 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
24025
24026 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24027 matches!(
24028 ProjectSettings::get_global(cx).git.git_gutter,
24029 GitGutterSetting::TrackedFiles
24030 )
24031 });
24032 let gutter_settings = EditorSettings::get_global(cx).gutter;
24033 let show_line_numbers = self
24034 .show_line_numbers
24035 .unwrap_or(gutter_settings.line_numbers);
24036 let line_gutter_width = if show_line_numbers {
24037 // Avoid flicker-like gutter resizes when the line number gains another digit by
24038 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24039 let min_width_for_number_on_gutter =
24040 ch_advance * gutter_settings.min_line_number_digits as f32;
24041 max_line_number_width.max(min_width_for_number_on_gutter)
24042 } else {
24043 0.0.into()
24044 };
24045
24046 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24047 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24048
24049 let git_blame_entries_width =
24050 self.git_blame_gutter_max_author_length
24051 .map(|max_author_length| {
24052 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24053 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24054
24055 /// The number of characters to dedicate to gaps and margins.
24056 const SPACING_WIDTH: usize = 4;
24057
24058 let max_char_count = max_author_length.min(renderer.max_author_length())
24059 + ::git::SHORT_SHA_LENGTH
24060 + MAX_RELATIVE_TIMESTAMP.len()
24061 + SPACING_WIDTH;
24062
24063 ch_advance * max_char_count
24064 });
24065
24066 let is_singleton = self.buffer_snapshot().is_singleton();
24067
24068 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24069 left_padding += if !is_singleton {
24070 ch_width * 4.0
24071 } else if show_runnables || show_breakpoints {
24072 ch_width * 3.0
24073 } else if show_git_gutter && show_line_numbers {
24074 ch_width * 2.0
24075 } else if show_git_gutter || show_line_numbers {
24076 ch_width
24077 } else {
24078 px(0.)
24079 };
24080
24081 let shows_folds = is_singleton && gutter_settings.folds;
24082
24083 let right_padding = if shows_folds && show_line_numbers {
24084 ch_width * 4.0
24085 } else if shows_folds || (!is_singleton && show_line_numbers) {
24086 ch_width * 3.0
24087 } else if show_line_numbers {
24088 ch_width
24089 } else {
24090 px(0.)
24091 };
24092
24093 Some(GutterDimensions {
24094 left_padding,
24095 right_padding,
24096 width: line_gutter_width + left_padding + right_padding,
24097 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24098 git_blame_entries_width,
24099 })
24100 }
24101
24102 pub fn render_crease_toggle(
24103 &self,
24104 buffer_row: MultiBufferRow,
24105 row_contains_cursor: bool,
24106 editor: Entity<Editor>,
24107 window: &mut Window,
24108 cx: &mut App,
24109 ) -> Option<AnyElement> {
24110 let folded = self.is_line_folded(buffer_row);
24111 let mut is_foldable = false;
24112
24113 if let Some(crease) = self
24114 .crease_snapshot
24115 .query_row(buffer_row, self.buffer_snapshot())
24116 {
24117 is_foldable = true;
24118 match crease {
24119 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24120 if let Some(render_toggle) = render_toggle {
24121 let toggle_callback =
24122 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24123 if folded {
24124 editor.update(cx, |editor, cx| {
24125 editor.fold_at(buffer_row, window, cx)
24126 });
24127 } else {
24128 editor.update(cx, |editor, cx| {
24129 editor.unfold_at(buffer_row, window, cx)
24130 });
24131 }
24132 });
24133 return Some((render_toggle)(
24134 buffer_row,
24135 folded,
24136 toggle_callback,
24137 window,
24138 cx,
24139 ));
24140 }
24141 }
24142 }
24143 }
24144
24145 is_foldable |= self.starts_indent(buffer_row);
24146
24147 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24148 Some(
24149 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24150 .toggle_state(folded)
24151 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24152 if folded {
24153 this.unfold_at(buffer_row, window, cx);
24154 } else {
24155 this.fold_at(buffer_row, window, cx);
24156 }
24157 }))
24158 .into_any_element(),
24159 )
24160 } else {
24161 None
24162 }
24163 }
24164
24165 pub fn render_crease_trailer(
24166 &self,
24167 buffer_row: MultiBufferRow,
24168 window: &mut Window,
24169 cx: &mut App,
24170 ) -> Option<AnyElement> {
24171 let folded = self.is_line_folded(buffer_row);
24172 if let Crease::Inline { render_trailer, .. } = self
24173 .crease_snapshot
24174 .query_row(buffer_row, self.buffer_snapshot())?
24175 {
24176 let render_trailer = render_trailer.as_ref()?;
24177 Some(render_trailer(buffer_row, folded, window, cx))
24178 } else {
24179 None
24180 }
24181 }
24182}
24183
24184impl Deref for EditorSnapshot {
24185 type Target = DisplaySnapshot;
24186
24187 fn deref(&self) -> &Self::Target {
24188 &self.display_snapshot
24189 }
24190}
24191
24192#[derive(Clone, Debug, PartialEq, Eq)]
24193pub enum EditorEvent {
24194 InputIgnored {
24195 text: Arc<str>,
24196 },
24197 InputHandled {
24198 utf16_range_to_replace: Option<Range<isize>>,
24199 text: Arc<str>,
24200 },
24201 ExcerptsAdded {
24202 buffer: Entity<Buffer>,
24203 predecessor: ExcerptId,
24204 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
24205 },
24206 ExcerptsRemoved {
24207 ids: Vec<ExcerptId>,
24208 removed_buffer_ids: Vec<BufferId>,
24209 },
24210 BufferFoldToggled {
24211 ids: Vec<ExcerptId>,
24212 folded: bool,
24213 },
24214 ExcerptsEdited {
24215 ids: Vec<ExcerptId>,
24216 },
24217 ExcerptsExpanded {
24218 ids: Vec<ExcerptId>,
24219 },
24220 BufferEdited,
24221 Edited {
24222 transaction_id: clock::Lamport,
24223 },
24224 Reparsed(BufferId),
24225 Focused,
24226 FocusedIn,
24227 Blurred,
24228 DirtyChanged,
24229 Saved,
24230 TitleChanged,
24231 SelectionsChanged {
24232 local: bool,
24233 },
24234 ScrollPositionChanged {
24235 local: bool,
24236 autoscroll: bool,
24237 },
24238 TransactionUndone {
24239 transaction_id: clock::Lamport,
24240 },
24241 TransactionBegun {
24242 transaction_id: clock::Lamport,
24243 },
24244 CursorShapeChanged,
24245 BreadcrumbsChanged,
24246 PushedToNavHistory {
24247 anchor: Anchor,
24248 is_deactivate: bool,
24249 },
24250}
24251
24252impl EventEmitter<EditorEvent> for Editor {}
24253
24254impl Focusable for Editor {
24255 fn focus_handle(&self, _cx: &App) -> FocusHandle {
24256 self.focus_handle.clone()
24257 }
24258}
24259
24260impl Render for Editor {
24261 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24262 let settings = ThemeSettings::get_global(cx);
24263
24264 let mut text_style = match self.mode {
24265 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24266 color: cx.theme().colors().editor_foreground,
24267 font_family: settings.ui_font.family.clone(),
24268 font_features: settings.ui_font.features.clone(),
24269 font_fallbacks: settings.ui_font.fallbacks.clone(),
24270 font_size: rems(0.875).into(),
24271 font_weight: settings.ui_font.weight,
24272 line_height: relative(settings.buffer_line_height.value()),
24273 ..Default::default()
24274 },
24275 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24276 color: cx.theme().colors().editor_foreground,
24277 font_family: settings.buffer_font.family.clone(),
24278 font_features: settings.buffer_font.features.clone(),
24279 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24280 font_size: settings.buffer_font_size(cx).into(),
24281 font_weight: settings.buffer_font.weight,
24282 line_height: relative(settings.buffer_line_height.value()),
24283 ..Default::default()
24284 },
24285 };
24286 if let Some(text_style_refinement) = &self.text_style_refinement {
24287 text_style.refine(text_style_refinement)
24288 }
24289
24290 let background = match self.mode {
24291 EditorMode::SingleLine => cx.theme().system().transparent,
24292 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24293 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24294 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24295 };
24296
24297 EditorElement::new(
24298 &cx.entity(),
24299 EditorStyle {
24300 background,
24301 border: cx.theme().colors().border,
24302 local_player: cx.theme().players().local(),
24303 text: text_style,
24304 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24305 syntax: cx.theme().syntax().clone(),
24306 status: cx.theme().status().clone(),
24307 inlay_hints_style: make_inlay_hints_style(cx),
24308 edit_prediction_styles: make_suggestion_styles(cx),
24309 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
24310 show_underlines: self.diagnostics_enabled(),
24311 },
24312 )
24313 }
24314}
24315
24316impl EntityInputHandler for Editor {
24317 fn text_for_range(
24318 &mut self,
24319 range_utf16: Range<usize>,
24320 adjusted_range: &mut Option<Range<usize>>,
24321 _: &mut Window,
24322 cx: &mut Context<Self>,
24323 ) -> Option<String> {
24324 let snapshot = self.buffer.read(cx).read(cx);
24325 let start = snapshot.clip_offset_utf16(
24326 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
24327 Bias::Left,
24328 );
24329 let end = snapshot.clip_offset_utf16(
24330 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
24331 Bias::Right,
24332 );
24333 if (start.0.0..end.0.0) != range_utf16 {
24334 adjusted_range.replace(start.0.0..end.0.0);
24335 }
24336 Some(snapshot.text_for_range(start..end).collect())
24337 }
24338
24339 fn selected_text_range(
24340 &mut self,
24341 ignore_disabled_input: bool,
24342 _: &mut Window,
24343 cx: &mut Context<Self>,
24344 ) -> Option<UTF16Selection> {
24345 // Prevent the IME menu from appearing when holding down an alphabetic key
24346 // while input is disabled.
24347 if !ignore_disabled_input && !self.input_enabled {
24348 return None;
24349 }
24350
24351 let selection = self
24352 .selections
24353 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24354 let range = selection.range();
24355
24356 Some(UTF16Selection {
24357 range: range.start.0.0..range.end.0.0,
24358 reversed: selection.reversed,
24359 })
24360 }
24361
24362 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24363 let snapshot = self.buffer.read(cx).read(cx);
24364 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24365 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
24366 }
24367
24368 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24369 self.clear_highlights::<InputComposition>(cx);
24370 self.ime_transaction.take();
24371 }
24372
24373 fn replace_text_in_range(
24374 &mut self,
24375 range_utf16: Option<Range<usize>>,
24376 text: &str,
24377 window: &mut Window,
24378 cx: &mut Context<Self>,
24379 ) {
24380 if !self.input_enabled {
24381 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24382 return;
24383 }
24384
24385 self.transact(window, cx, |this, window, cx| {
24386 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24387 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24388 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24389 Some(this.selection_replacement_ranges(range_utf16, cx))
24390 } else {
24391 this.marked_text_ranges(cx)
24392 };
24393
24394 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24395 let newest_selection_id = this.selections.newest_anchor().id;
24396 this.selections
24397 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24398 .iter()
24399 .zip(ranges_to_replace.iter())
24400 .find_map(|(selection, range)| {
24401 if selection.id == newest_selection_id {
24402 Some(
24403 (range.start.0.0 as isize - selection.head().0.0 as isize)
24404 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24405 )
24406 } else {
24407 None
24408 }
24409 })
24410 });
24411
24412 cx.emit(EditorEvent::InputHandled {
24413 utf16_range_to_replace: range_to_replace,
24414 text: text.into(),
24415 });
24416
24417 if let Some(new_selected_ranges) = new_selected_ranges {
24418 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24419 selections.select_ranges(new_selected_ranges)
24420 });
24421 this.backspace(&Default::default(), window, cx);
24422 }
24423
24424 this.handle_input(text, window, cx);
24425 });
24426
24427 if let Some(transaction) = self.ime_transaction {
24428 self.buffer.update(cx, |buffer, cx| {
24429 buffer.group_until_transaction(transaction, cx);
24430 });
24431 }
24432
24433 self.unmark_text(window, cx);
24434 }
24435
24436 fn replace_and_mark_text_in_range(
24437 &mut self,
24438 range_utf16: Option<Range<usize>>,
24439 text: &str,
24440 new_selected_range_utf16: Option<Range<usize>>,
24441 window: &mut Window,
24442 cx: &mut Context<Self>,
24443 ) {
24444 if !self.input_enabled {
24445 return;
24446 }
24447
24448 let transaction = self.transact(window, cx, |this, window, cx| {
24449 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24450 let snapshot = this.buffer.read(cx).read(cx);
24451 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24452 for marked_range in &mut marked_ranges {
24453 marked_range.end = marked_range.start + relative_range_utf16.end;
24454 marked_range.start += relative_range_utf16.start;
24455 marked_range.start =
24456 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24457 marked_range.end =
24458 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24459 }
24460 }
24461 Some(marked_ranges)
24462 } else if let Some(range_utf16) = range_utf16 {
24463 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24464 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24465 Some(this.selection_replacement_ranges(range_utf16, cx))
24466 } else {
24467 None
24468 };
24469
24470 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24471 let newest_selection_id = this.selections.newest_anchor().id;
24472 this.selections
24473 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24474 .iter()
24475 .zip(ranges_to_replace.iter())
24476 .find_map(|(selection, range)| {
24477 if selection.id == newest_selection_id {
24478 Some(
24479 (range.start.0.0 as isize - selection.head().0.0 as isize)
24480 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24481 )
24482 } else {
24483 None
24484 }
24485 })
24486 });
24487
24488 cx.emit(EditorEvent::InputHandled {
24489 utf16_range_to_replace: range_to_replace,
24490 text: text.into(),
24491 });
24492
24493 if let Some(ranges) = ranges_to_replace {
24494 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24495 s.select_ranges(ranges)
24496 });
24497 }
24498
24499 let marked_ranges = {
24500 let snapshot = this.buffer.read(cx).read(cx);
24501 this.selections
24502 .disjoint_anchors_arc()
24503 .iter()
24504 .map(|selection| {
24505 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24506 })
24507 .collect::<Vec<_>>()
24508 };
24509
24510 if text.is_empty() {
24511 this.unmark_text(window, cx);
24512 } else {
24513 this.highlight_text::<InputComposition>(
24514 marked_ranges.clone(),
24515 HighlightStyle {
24516 underline: Some(UnderlineStyle {
24517 thickness: px(1.),
24518 color: None,
24519 wavy: false,
24520 }),
24521 ..Default::default()
24522 },
24523 cx,
24524 );
24525 }
24526
24527 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24528 let use_autoclose = this.use_autoclose;
24529 let use_auto_surround = this.use_auto_surround;
24530 this.set_use_autoclose(false);
24531 this.set_use_auto_surround(false);
24532 this.handle_input(text, window, cx);
24533 this.set_use_autoclose(use_autoclose);
24534 this.set_use_auto_surround(use_auto_surround);
24535
24536 if let Some(new_selected_range) = new_selected_range_utf16 {
24537 let snapshot = this.buffer.read(cx).read(cx);
24538 let new_selected_ranges = marked_ranges
24539 .into_iter()
24540 .map(|marked_range| {
24541 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24542 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
24543 insertion_start.0 + new_selected_range.start,
24544 ));
24545 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
24546 insertion_start.0 + new_selected_range.end,
24547 ));
24548 snapshot.clip_offset_utf16(new_start, Bias::Left)
24549 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24550 })
24551 .collect::<Vec<_>>();
24552
24553 drop(snapshot);
24554 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24555 selections.select_ranges(new_selected_ranges)
24556 });
24557 }
24558 });
24559
24560 self.ime_transaction = self.ime_transaction.or(transaction);
24561 if let Some(transaction) = self.ime_transaction {
24562 self.buffer.update(cx, |buffer, cx| {
24563 buffer.group_until_transaction(transaction, cx);
24564 });
24565 }
24566
24567 if self.text_highlights::<InputComposition>(cx).is_none() {
24568 self.ime_transaction.take();
24569 }
24570 }
24571
24572 fn bounds_for_range(
24573 &mut self,
24574 range_utf16: Range<usize>,
24575 element_bounds: gpui::Bounds<Pixels>,
24576 window: &mut Window,
24577 cx: &mut Context<Self>,
24578 ) -> Option<gpui::Bounds<Pixels>> {
24579 let text_layout_details = self.text_layout_details(window);
24580 let CharacterDimensions {
24581 em_width,
24582 em_advance,
24583 line_height,
24584 } = self.character_dimensions(window);
24585
24586 let snapshot = self.snapshot(window, cx);
24587 let scroll_position = snapshot.scroll_position();
24588 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24589
24590 let start =
24591 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
24592 let x = Pixels::from(
24593 ScrollOffset::from(
24594 snapshot.x_for_display_point(start, &text_layout_details)
24595 + self.gutter_dimensions.full_width(),
24596 ) - scroll_left,
24597 );
24598 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24599
24600 Some(Bounds {
24601 origin: element_bounds.origin + point(x, y),
24602 size: size(em_width, line_height),
24603 })
24604 }
24605
24606 fn character_index_for_point(
24607 &mut self,
24608 point: gpui::Point<Pixels>,
24609 _window: &mut Window,
24610 _cx: &mut Context<Self>,
24611 ) -> Option<usize> {
24612 let position_map = self.last_position_map.as_ref()?;
24613 if !position_map.text_hitbox.contains(&point) {
24614 return None;
24615 }
24616 let display_point = position_map.point_for_position(point).previous_valid;
24617 let anchor = position_map
24618 .snapshot
24619 .display_point_to_anchor(display_point, Bias::Left);
24620 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24621 Some(utf16_offset.0.0)
24622 }
24623
24624 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24625 self.input_enabled
24626 }
24627}
24628
24629trait SelectionExt {
24630 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24631 fn spanned_rows(
24632 &self,
24633 include_end_if_at_line_start: bool,
24634 map: &DisplaySnapshot,
24635 ) -> Range<MultiBufferRow>;
24636}
24637
24638impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24639 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24640 let start = self
24641 .start
24642 .to_point(map.buffer_snapshot())
24643 .to_display_point(map);
24644 let end = self
24645 .end
24646 .to_point(map.buffer_snapshot())
24647 .to_display_point(map);
24648 if self.reversed {
24649 end..start
24650 } else {
24651 start..end
24652 }
24653 }
24654
24655 fn spanned_rows(
24656 &self,
24657 include_end_if_at_line_start: bool,
24658 map: &DisplaySnapshot,
24659 ) -> Range<MultiBufferRow> {
24660 let start = self.start.to_point(map.buffer_snapshot());
24661 let mut end = self.end.to_point(map.buffer_snapshot());
24662 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24663 end.row -= 1;
24664 }
24665
24666 let buffer_start = map.prev_line_boundary(start).0;
24667 let buffer_end = map.next_line_boundary(end).0;
24668 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24669 }
24670}
24671
24672impl<T: InvalidationRegion> InvalidationStack<T> {
24673 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24674 where
24675 S: Clone + ToOffset,
24676 {
24677 while let Some(region) = self.last() {
24678 let all_selections_inside_invalidation_ranges =
24679 if selections.len() == region.ranges().len() {
24680 selections
24681 .iter()
24682 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24683 .all(|(selection, invalidation_range)| {
24684 let head = selection.head().to_offset(buffer);
24685 invalidation_range.start <= head && invalidation_range.end >= head
24686 })
24687 } else {
24688 false
24689 };
24690
24691 if all_selections_inside_invalidation_ranges {
24692 break;
24693 } else {
24694 self.pop();
24695 }
24696 }
24697 }
24698}
24699
24700impl<T> Default for InvalidationStack<T> {
24701 fn default() -> Self {
24702 Self(Default::default())
24703 }
24704}
24705
24706impl<T> Deref for InvalidationStack<T> {
24707 type Target = Vec<T>;
24708
24709 fn deref(&self) -> &Self::Target {
24710 &self.0
24711 }
24712}
24713
24714impl<T> DerefMut for InvalidationStack<T> {
24715 fn deref_mut(&mut self) -> &mut Self::Target {
24716 &mut self.0
24717 }
24718}
24719
24720impl InvalidationRegion for SnippetState {
24721 fn ranges(&self) -> &[Range<Anchor>] {
24722 &self.ranges[self.active_index]
24723 }
24724}
24725
24726fn edit_prediction_edit_text(
24727 current_snapshot: &BufferSnapshot,
24728 edits: &[(Range<Anchor>, impl AsRef<str>)],
24729 edit_preview: &EditPreview,
24730 include_deletions: bool,
24731 cx: &App,
24732) -> HighlightedText {
24733 let edits = edits
24734 .iter()
24735 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24736 .collect::<Vec<_>>();
24737
24738 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24739}
24740
24741fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24742 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24743 // Just show the raw edit text with basic styling
24744 let mut text = String::new();
24745 let mut highlights = Vec::new();
24746
24747 let insertion_highlight_style = HighlightStyle {
24748 color: Some(cx.theme().colors().text),
24749 ..Default::default()
24750 };
24751
24752 for (_, edit_text) in edits {
24753 let start_offset = text.len();
24754 text.push_str(edit_text);
24755 let end_offset = text.len();
24756
24757 if start_offset < end_offset {
24758 highlights.push((start_offset..end_offset, insertion_highlight_style));
24759 }
24760 }
24761
24762 HighlightedText {
24763 text: text.into(),
24764 highlights,
24765 }
24766}
24767
24768pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24769 match severity {
24770 lsp::DiagnosticSeverity::ERROR => colors.error,
24771 lsp::DiagnosticSeverity::WARNING => colors.warning,
24772 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24773 lsp::DiagnosticSeverity::HINT => colors.info,
24774 _ => colors.ignored,
24775 }
24776}
24777
24778pub fn styled_runs_for_code_label<'a>(
24779 label: &'a CodeLabel,
24780 syntax_theme: &'a theme::SyntaxTheme,
24781) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24782 let fade_out = HighlightStyle {
24783 fade_out: Some(0.35),
24784 ..Default::default()
24785 };
24786
24787 let mut prev_end = label.filter_range.end;
24788 label
24789 .runs
24790 .iter()
24791 .enumerate()
24792 .flat_map(move |(ix, (range, highlight_id))| {
24793 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24794 style
24795 } else {
24796 return Default::default();
24797 };
24798 let muted_style = style.highlight(fade_out);
24799
24800 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24801 if range.start >= label.filter_range.end {
24802 if range.start > prev_end {
24803 runs.push((prev_end..range.start, fade_out));
24804 }
24805 runs.push((range.clone(), muted_style));
24806 } else if range.end <= label.filter_range.end {
24807 runs.push((range.clone(), style));
24808 } else {
24809 runs.push((range.start..label.filter_range.end, style));
24810 runs.push((label.filter_range.end..range.end, muted_style));
24811 }
24812 prev_end = cmp::max(prev_end, range.end);
24813
24814 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24815 runs.push((prev_end..label.text.len(), fade_out));
24816 }
24817
24818 runs
24819 })
24820}
24821
24822pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24823 let mut prev_index = 0;
24824 let mut prev_codepoint: Option<char> = None;
24825 text.char_indices()
24826 .chain([(text.len(), '\0')])
24827 .filter_map(move |(index, codepoint)| {
24828 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24829 let is_boundary = index == text.len()
24830 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24831 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24832 if is_boundary {
24833 let chunk = &text[prev_index..index];
24834 prev_index = index;
24835 Some(chunk)
24836 } else {
24837 None
24838 }
24839 })
24840}
24841
24842/// Given a string of text immediately before the cursor, iterates over possible
24843/// strings a snippet could match to. More precisely: returns an iterator over
24844/// suffixes of `text` created by splitting at word boundaries (before & after
24845/// every non-word character).
24846///
24847/// Shorter suffixes are returned first.
24848pub(crate) fn snippet_candidate_suffixes(
24849 text: &str,
24850 is_word_char: impl Fn(char) -> bool,
24851) -> impl std::iter::Iterator<Item = &str> {
24852 let mut prev_index = text.len();
24853 let mut prev_codepoint = None;
24854 text.char_indices()
24855 .rev()
24856 .chain([(0, '\0')])
24857 .filter_map(move |(index, codepoint)| {
24858 let prev_index = std::mem::replace(&mut prev_index, index);
24859 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24860 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
24861 None
24862 } else {
24863 let chunk = &text[prev_index..]; // go to end of string
24864 Some(chunk)
24865 }
24866 })
24867}
24868
24869pub trait RangeToAnchorExt: Sized {
24870 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24871
24872 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24873 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24874 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24875 }
24876}
24877
24878impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24879 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24880 let start_offset = self.start.to_offset(snapshot);
24881 let end_offset = self.end.to_offset(snapshot);
24882 if start_offset == end_offset {
24883 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24884 } else {
24885 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24886 }
24887 }
24888}
24889
24890pub trait RowExt {
24891 fn as_f64(&self) -> f64;
24892
24893 fn next_row(&self) -> Self;
24894
24895 fn previous_row(&self) -> Self;
24896
24897 fn minus(&self, other: Self) -> u32;
24898}
24899
24900impl RowExt for DisplayRow {
24901 fn as_f64(&self) -> f64 {
24902 self.0 as _
24903 }
24904
24905 fn next_row(&self) -> Self {
24906 Self(self.0 + 1)
24907 }
24908
24909 fn previous_row(&self) -> Self {
24910 Self(self.0.saturating_sub(1))
24911 }
24912
24913 fn minus(&self, other: Self) -> u32 {
24914 self.0 - other.0
24915 }
24916}
24917
24918impl RowExt for MultiBufferRow {
24919 fn as_f64(&self) -> f64 {
24920 self.0 as _
24921 }
24922
24923 fn next_row(&self) -> Self {
24924 Self(self.0 + 1)
24925 }
24926
24927 fn previous_row(&self) -> Self {
24928 Self(self.0.saturating_sub(1))
24929 }
24930
24931 fn minus(&self, other: Self) -> u32 {
24932 self.0 - other.0
24933 }
24934}
24935
24936trait RowRangeExt {
24937 type Row;
24938
24939 fn len(&self) -> usize;
24940
24941 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24942}
24943
24944impl RowRangeExt for Range<MultiBufferRow> {
24945 type Row = MultiBufferRow;
24946
24947 fn len(&self) -> usize {
24948 (self.end.0 - self.start.0) as usize
24949 }
24950
24951 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24952 (self.start.0..self.end.0).map(MultiBufferRow)
24953 }
24954}
24955
24956impl RowRangeExt for Range<DisplayRow> {
24957 type Row = DisplayRow;
24958
24959 fn len(&self) -> usize {
24960 (self.end.0 - self.start.0) as usize
24961 }
24962
24963 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24964 (self.start.0..self.end.0).map(DisplayRow)
24965 }
24966}
24967
24968/// If select range has more than one line, we
24969/// just point the cursor to range.start.
24970fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24971 if range.start.row == range.end.row {
24972 range
24973 } else {
24974 range.start..range.start
24975 }
24976}
24977pub struct KillRing(ClipboardItem);
24978impl Global for KillRing {}
24979
24980const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24981
24982enum BreakpointPromptEditAction {
24983 Log,
24984 Condition,
24985 HitCondition,
24986}
24987
24988struct BreakpointPromptEditor {
24989 pub(crate) prompt: Entity<Editor>,
24990 editor: WeakEntity<Editor>,
24991 breakpoint_anchor: Anchor,
24992 breakpoint: Breakpoint,
24993 edit_action: BreakpointPromptEditAction,
24994 block_ids: HashSet<CustomBlockId>,
24995 editor_margins: Arc<Mutex<EditorMargins>>,
24996 _subscriptions: Vec<Subscription>,
24997}
24998
24999impl BreakpointPromptEditor {
25000 const MAX_LINES: u8 = 4;
25001
25002 fn new(
25003 editor: WeakEntity<Editor>,
25004 breakpoint_anchor: Anchor,
25005 breakpoint: Breakpoint,
25006 edit_action: BreakpointPromptEditAction,
25007 window: &mut Window,
25008 cx: &mut Context<Self>,
25009 ) -> Self {
25010 let base_text = match edit_action {
25011 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25012 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25013 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25014 }
25015 .map(|msg| msg.to_string())
25016 .unwrap_or_default();
25017
25018 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25019 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25020
25021 let prompt = cx.new(|cx| {
25022 let mut prompt = Editor::new(
25023 EditorMode::AutoHeight {
25024 min_lines: 1,
25025 max_lines: Some(Self::MAX_LINES as usize),
25026 },
25027 buffer,
25028 None,
25029 window,
25030 cx,
25031 );
25032 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25033 prompt.set_show_cursor_when_unfocused(false, cx);
25034 prompt.set_placeholder_text(
25035 match edit_action {
25036 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25037 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25038 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25039 },
25040 window,
25041 cx,
25042 );
25043
25044 prompt
25045 });
25046
25047 Self {
25048 prompt,
25049 editor,
25050 breakpoint_anchor,
25051 breakpoint,
25052 edit_action,
25053 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25054 block_ids: Default::default(),
25055 _subscriptions: vec![],
25056 }
25057 }
25058
25059 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25060 self.block_ids.extend(block_ids)
25061 }
25062
25063 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25064 if let Some(editor) = self.editor.upgrade() {
25065 let message = self
25066 .prompt
25067 .read(cx)
25068 .buffer
25069 .read(cx)
25070 .as_singleton()
25071 .expect("A multi buffer in breakpoint prompt isn't possible")
25072 .read(cx)
25073 .as_rope()
25074 .to_string();
25075
25076 editor.update(cx, |editor, cx| {
25077 editor.edit_breakpoint_at_anchor(
25078 self.breakpoint_anchor,
25079 self.breakpoint.clone(),
25080 match self.edit_action {
25081 BreakpointPromptEditAction::Log => {
25082 BreakpointEditAction::EditLogMessage(message.into())
25083 }
25084 BreakpointPromptEditAction::Condition => {
25085 BreakpointEditAction::EditCondition(message.into())
25086 }
25087 BreakpointPromptEditAction::HitCondition => {
25088 BreakpointEditAction::EditHitCondition(message.into())
25089 }
25090 },
25091 cx,
25092 );
25093
25094 editor.remove_blocks(self.block_ids.clone(), None, cx);
25095 cx.focus_self(window);
25096 });
25097 }
25098 }
25099
25100 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25101 self.editor
25102 .update(cx, |editor, cx| {
25103 editor.remove_blocks(self.block_ids.clone(), None, cx);
25104 window.focus(&editor.focus_handle);
25105 })
25106 .log_err();
25107 }
25108
25109 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25110 let settings = ThemeSettings::get_global(cx);
25111 let text_style = TextStyle {
25112 color: if self.prompt.read(cx).read_only(cx) {
25113 cx.theme().colors().text_disabled
25114 } else {
25115 cx.theme().colors().text
25116 },
25117 font_family: settings.buffer_font.family.clone(),
25118 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25119 font_size: settings.buffer_font_size(cx).into(),
25120 font_weight: settings.buffer_font.weight,
25121 line_height: relative(settings.buffer_line_height.value()),
25122 ..Default::default()
25123 };
25124 EditorElement::new(
25125 &self.prompt,
25126 EditorStyle {
25127 background: cx.theme().colors().editor_background,
25128 local_player: cx.theme().players().local(),
25129 text: text_style,
25130 ..Default::default()
25131 },
25132 )
25133 }
25134}
25135
25136impl Render for BreakpointPromptEditor {
25137 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25138 let editor_margins = *self.editor_margins.lock();
25139 let gutter_dimensions = editor_margins.gutter;
25140 h_flex()
25141 .key_context("Editor")
25142 .bg(cx.theme().colors().editor_background)
25143 .border_y_1()
25144 .border_color(cx.theme().status().info_border)
25145 .size_full()
25146 .py(window.line_height() / 2.5)
25147 .on_action(cx.listener(Self::confirm))
25148 .on_action(cx.listener(Self::cancel))
25149 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25150 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25151 }
25152}
25153
25154impl Focusable for BreakpointPromptEditor {
25155 fn focus_handle(&self, cx: &App) -> FocusHandle {
25156 self.prompt.focus_handle(cx)
25157 }
25158}
25159
25160fn all_edits_insertions_or_deletions(
25161 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25162 snapshot: &MultiBufferSnapshot,
25163) -> bool {
25164 let mut all_insertions = true;
25165 let mut all_deletions = true;
25166
25167 for (range, new_text) in edits.iter() {
25168 let range_is_empty = range.to_offset(snapshot).is_empty();
25169 let text_is_empty = new_text.is_empty();
25170
25171 if range_is_empty != text_is_empty {
25172 if range_is_empty {
25173 all_deletions = false;
25174 } else {
25175 all_insertions = false;
25176 }
25177 } else {
25178 return false;
25179 }
25180
25181 if !all_insertions && !all_deletions {
25182 return false;
25183 }
25184 }
25185 all_insertions || all_deletions
25186}
25187
25188struct MissingEditPredictionKeybindingTooltip;
25189
25190impl Render for MissingEditPredictionKeybindingTooltip {
25191 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25192 ui::tooltip_container(cx, |container, cx| {
25193 container
25194 .flex_shrink_0()
25195 .max_w_80()
25196 .min_h(rems_from_px(124.))
25197 .justify_between()
25198 .child(
25199 v_flex()
25200 .flex_1()
25201 .text_ui_sm(cx)
25202 .child(Label::new("Conflict with Accept Keybinding"))
25203 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25204 )
25205 .child(
25206 h_flex()
25207 .pb_1()
25208 .gap_1()
25209 .items_end()
25210 .w_full()
25211 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
25212 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
25213 }))
25214 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
25215 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
25216 })),
25217 )
25218 })
25219 }
25220}
25221
25222#[derive(Debug, Clone, Copy, PartialEq)]
25223pub struct LineHighlight {
25224 pub background: Background,
25225 pub border: Option<gpui::Hsla>,
25226 pub include_gutter: bool,
25227 pub type_id: Option<TypeId>,
25228}
25229
25230struct LineManipulationResult {
25231 pub new_text: String,
25232 pub line_count_before: usize,
25233 pub line_count_after: usize,
25234}
25235
25236fn render_diff_hunk_controls(
25237 row: u32,
25238 status: &DiffHunkStatus,
25239 hunk_range: Range<Anchor>,
25240 is_created_file: bool,
25241 line_height: Pixels,
25242 editor: &Entity<Editor>,
25243 _window: &mut Window,
25244 cx: &mut App,
25245) -> AnyElement {
25246 h_flex()
25247 .h(line_height)
25248 .mr_1()
25249 .gap_1()
25250 .px_0p5()
25251 .pb_1()
25252 .border_x_1()
25253 .border_b_1()
25254 .border_color(cx.theme().colors().border_variant)
25255 .rounded_b_lg()
25256 .bg(cx.theme().colors().editor_background)
25257 .gap_1()
25258 .block_mouse_except_scroll()
25259 .shadow_md()
25260 .child(if status.has_secondary_hunk() {
25261 Button::new(("stage", row as u64), "Stage")
25262 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25263 .tooltip({
25264 let focus_handle = editor.focus_handle(cx);
25265 move |_window, cx| {
25266 Tooltip::for_action_in(
25267 "Stage Hunk",
25268 &::git::ToggleStaged,
25269 &focus_handle,
25270 cx,
25271 )
25272 }
25273 })
25274 .on_click({
25275 let editor = editor.clone();
25276 move |_event, _window, cx| {
25277 editor.update(cx, |editor, cx| {
25278 editor.stage_or_unstage_diff_hunks(
25279 true,
25280 vec![hunk_range.start..hunk_range.start],
25281 cx,
25282 );
25283 });
25284 }
25285 })
25286 } else {
25287 Button::new(("unstage", row as u64), "Unstage")
25288 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25289 .tooltip({
25290 let focus_handle = editor.focus_handle(cx);
25291 move |_window, cx| {
25292 Tooltip::for_action_in(
25293 "Unstage Hunk",
25294 &::git::ToggleStaged,
25295 &focus_handle,
25296 cx,
25297 )
25298 }
25299 })
25300 .on_click({
25301 let editor = editor.clone();
25302 move |_event, _window, cx| {
25303 editor.update(cx, |editor, cx| {
25304 editor.stage_or_unstage_diff_hunks(
25305 false,
25306 vec![hunk_range.start..hunk_range.start],
25307 cx,
25308 );
25309 });
25310 }
25311 })
25312 })
25313 .child(
25314 Button::new(("restore", row as u64), "Restore")
25315 .tooltip({
25316 let focus_handle = editor.focus_handle(cx);
25317 move |_window, cx| {
25318 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
25319 }
25320 })
25321 .on_click({
25322 let editor = editor.clone();
25323 move |_event, window, cx| {
25324 editor.update(cx, |editor, cx| {
25325 let snapshot = editor.snapshot(window, cx);
25326 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
25327 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
25328 });
25329 }
25330 })
25331 .disabled(is_created_file),
25332 )
25333 .when(
25334 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
25335 |el| {
25336 el.child(
25337 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
25338 .shape(IconButtonShape::Square)
25339 .icon_size(IconSize::Small)
25340 // .disabled(!has_multiple_hunks)
25341 .tooltip({
25342 let focus_handle = editor.focus_handle(cx);
25343 move |_window, cx| {
25344 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
25345 }
25346 })
25347 .on_click({
25348 let editor = editor.clone();
25349 move |_event, window, cx| {
25350 editor.update(cx, |editor, cx| {
25351 let snapshot = editor.snapshot(window, cx);
25352 let position =
25353 hunk_range.end.to_point(&snapshot.buffer_snapshot());
25354 editor.go_to_hunk_before_or_after_position(
25355 &snapshot,
25356 position,
25357 Direction::Next,
25358 window,
25359 cx,
25360 );
25361 editor.expand_selected_diff_hunks(cx);
25362 });
25363 }
25364 }),
25365 )
25366 .child(
25367 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25368 .shape(IconButtonShape::Square)
25369 .icon_size(IconSize::Small)
25370 // .disabled(!has_multiple_hunks)
25371 .tooltip({
25372 let focus_handle = editor.focus_handle(cx);
25373 move |_window, cx| {
25374 Tooltip::for_action_in(
25375 "Previous Hunk",
25376 &GoToPreviousHunk,
25377 &focus_handle,
25378 cx,
25379 )
25380 }
25381 })
25382 .on_click({
25383 let editor = editor.clone();
25384 move |_event, window, cx| {
25385 editor.update(cx, |editor, cx| {
25386 let snapshot = editor.snapshot(window, cx);
25387 let point =
25388 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25389 editor.go_to_hunk_before_or_after_position(
25390 &snapshot,
25391 point,
25392 Direction::Prev,
25393 window,
25394 cx,
25395 );
25396 editor.expand_selected_diff_hunks(cx);
25397 });
25398 }
25399 }),
25400 )
25401 },
25402 )
25403 .into_any_element()
25404}
25405
25406pub fn multibuffer_context_lines(cx: &App) -> u32 {
25407 EditorSettings::try_get(cx)
25408 .map(|settings| settings.excerpt_context_lines)
25409 .unwrap_or(2)
25410 .min(32)
25411}