1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15mod blink_manager;
16mod clangd_ext;
17pub mod code_context_menus;
18pub mod display_map;
19mod editor_settings;
20mod element;
21mod git;
22mod highlight_matching_bracket;
23mod hover_links;
24pub mod hover_popover;
25mod indent_guides;
26mod inlays;
27pub mod items;
28mod jsx_tag_auto_close;
29mod linked_editing_ranges;
30mod lsp_colors;
31mod lsp_ext;
32mod mouse_context_menu;
33pub mod movement;
34mod persistence;
35mod rust_analyzer_ext;
36pub mod scroll;
37mod selections_collection;
38pub mod tasks;
39
40#[cfg(test)]
41mod code_completion_tests;
42#[cfg(test)]
43mod edit_prediction_tests;
44#[cfg(test)]
45mod editor_tests;
46mod signature_help;
47#[cfg(any(test, feature = "test-support"))]
48pub mod test;
49
50pub(crate) use actions::*;
51pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
52pub use edit_prediction::Direction;
53pub use editor_settings::{
54 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
55 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
56};
57pub use element::{
58 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
59};
60pub use git::blame::BlameRenderer;
61pub use hover_popover::hover_markdown_style;
62pub use inlays::Inlay;
63pub use items::MAX_TAB_TITLE_LEN;
64pub use lsp::CompletionContext;
65pub use lsp_ext::lsp_tasks;
66pub use multi_buffer::{
67 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
68 RowInfo, ToOffset, ToPoint,
69};
70pub use text::Bias;
71
72use ::git::{
73 Restore,
74 blame::{BlameEntry, ParsedCommitMessage},
75 status::FileStatus,
76};
77use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
78use anyhow::{Context as _, Result, anyhow};
79use blink_manager::BlinkManager;
80use buffer_diff::DiffHunkStatus;
81use client::{Collaborator, ParticipantIndex, parse_zed_link};
82use clock::ReplicaId;
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use collections::{BTreeMap, HashMap, HashSet, VecDeque};
88use convert_case::{Case, Casing};
89use dap::TelemetrySpawnLocation;
90use display_map::*;
91use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
92use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
93use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
94use futures::{
95 FutureExt, StreamExt as _,
96 future::{self, Shared, join},
97 stream::FuturesUnordered,
98};
99use fuzzy::{StringMatch, StringMatchCandidate};
100use git::blame::{GitBlame, GlobalBlameRenderer};
101use gpui::{
102 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
103 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
104 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
105 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
106 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
107 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
108 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
109 div, point, prelude::*, pulsating_between, px, relative, size,
110};
111use hover_links::{HoverLink, HoveredLinkState, find_file};
112use hover_popover::{HoverState, hide_hover};
113use indent_guides::ActiveIndentGuidesState;
114use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
115use itertools::{Either, Itertools};
116use language::{
117 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
118 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
119 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
120 IndentSize, Language, OffsetRangeExt, OutlineItem, Point, Runnable, RunnableRange, Selection,
121 SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
122 language_settings::{
123 self, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings,
124 language_settings,
125 },
126 point_from_lsp, point_to_lsp, text_diff_with_options,
127};
128use linked_editing_ranges::refresh_linked_ranges;
129use lsp::{
130 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
131 LanguageServerId,
132};
133use lsp_colors::LspColorData;
134use markdown::Markdown;
135use mouse_context_menu::MouseContextMenu;
136use movement::TextLayoutDetails;
137use multi_buffer::{
138 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
139};
140use parking_lot::Mutex;
141use persistence::DB;
142use project::{
143 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
144 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
145 InvalidationStrategy, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem,
146 ProjectPath, ProjectTransaction, TaskSourceKind,
147 debugger::{
148 breakpoint_store::{
149 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
150 BreakpointStore, BreakpointStoreEvent,
151 },
152 session::{Session, SessionEvent},
153 },
154 git_store::GitStoreEvent,
155 lsp_store::{
156 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
157 OpenLspBufferHandle,
158 },
159 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
160};
161use rand::seq::SliceRandom;
162use rpc::{ErrorCode, ErrorExt, proto::PeerId};
163use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
164use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
165use serde::{Deserialize, Serialize};
166use settings::{
167 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
168 update_settings_file,
169};
170use smallvec::{SmallVec, smallvec};
171use snippet::Snippet;
172use std::{
173 any::{Any, TypeId},
174 borrow::Cow,
175 cell::{OnceCell, RefCell},
176 cmp::{self, Ordering, Reverse},
177 iter::{self, Peekable},
178 mem,
179 num::NonZeroU32,
180 ops::{Deref, DerefMut, Not, Range, RangeInclusive},
181 path::{Path, PathBuf},
182 rc::Rc,
183 sync::Arc,
184 time::{Duration, Instant},
185};
186use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
187use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
188use theme::{
189 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
190 observe_buffer_font_size_adjustment,
191};
192use ui::{
193 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
194 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
195};
196use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
197use workspace::{
198 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
199 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
200 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
201 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
202 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
203 searchable::SearchEvent,
204};
205
206use crate::{
207 code_context_menus::CompletionsMenuSource,
208 editor_settings::MultiCursorModifier,
209 hover_links::{find_url, find_url_from_range},
210 inlays::{
211 InlineValueCache,
212 inlay_hints::{LspInlayHintData, inlay_hint_settings},
213 },
214 scroll::{ScrollOffset, ScrollPixelOffset},
215 selections_collection::resolve_selections_wrapping_blocks,
216 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
217};
218
219pub const FILE_HEADER_HEIGHT: u32 = 2;
220pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
221const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
222const MAX_LINE_LEN: usize = 1024;
223const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
224const MAX_SELECTION_HISTORY_LEN: usize = 1024;
225pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
226#[doc(hidden)]
227pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
228pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
229
230pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
231pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
232pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
233pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
234
235pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
236pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
237pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
238
239pub type RenderDiffHunkControlsFn = Arc<
240 dyn Fn(
241 u32,
242 &DiffHunkStatus,
243 Range<Anchor>,
244 bool,
245 Pixels,
246 &Entity<Editor>,
247 &mut Window,
248 &mut App,
249 ) -> AnyElement,
250>;
251
252enum ReportEditorEvent {
253 Saved { auto_saved: bool },
254 EditorOpened,
255 Closed,
256}
257
258impl ReportEditorEvent {
259 pub fn event_type(&self) -> &'static str {
260 match self {
261 Self::Saved { .. } => "Editor Saved",
262 Self::EditorOpened => "Editor Opened",
263 Self::Closed => "Editor Closed",
264 }
265 }
266}
267
268pub enum ActiveDebugLine {}
269pub enum DebugStackFrameLine {}
270enum DocumentHighlightRead {}
271enum DocumentHighlightWrite {}
272enum InputComposition {}
273pub enum PendingInput {}
274enum SelectedTextHighlight {}
275
276pub enum ConflictsOuter {}
277pub enum ConflictsOurs {}
278pub enum ConflictsTheirs {}
279pub enum ConflictsOursMarker {}
280pub enum ConflictsTheirsMarker {}
281
282#[derive(Debug, Copy, Clone, PartialEq, Eq)]
283pub enum Navigated {
284 Yes,
285 No,
286}
287
288impl Navigated {
289 pub fn from_bool(yes: bool) -> Navigated {
290 if yes { Navigated::Yes } else { Navigated::No }
291 }
292}
293
294#[derive(Debug, Clone, PartialEq, Eq)]
295enum DisplayDiffHunk {
296 Folded {
297 display_row: DisplayRow,
298 },
299 Unfolded {
300 is_created_file: bool,
301 diff_base_byte_range: Range<usize>,
302 display_row_range: Range<DisplayRow>,
303 multi_buffer_range: Range<Anchor>,
304 status: DiffHunkStatus,
305 },
306}
307
308pub enum HideMouseCursorOrigin {
309 TypingAction,
310 MovementAction,
311}
312
313pub fn init(cx: &mut App) {
314 cx.set_global(GlobalBlameRenderer(Arc::new(())));
315
316 workspace::register_project_item::<Editor>(cx);
317 workspace::FollowableViewRegistry::register::<Editor>(cx);
318 workspace::register_serializable_item::<Editor>(cx);
319
320 cx.observe_new(
321 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
322 workspace.register_action(Editor::new_file);
323 workspace.register_action(Editor::new_file_split);
324 workspace.register_action(Editor::new_file_vertical);
325 workspace.register_action(Editor::new_file_horizontal);
326 workspace.register_action(Editor::cancel_language_server_work);
327 workspace.register_action(Editor::toggle_focus);
328 },
329 )
330 .detach();
331
332 cx.on_action(move |_: &workspace::NewFile, cx| {
333 let app_state = workspace::AppState::global(cx);
334 if let Some(app_state) = app_state.upgrade() {
335 workspace::open_new(
336 Default::default(),
337 app_state,
338 cx,
339 |workspace, window, cx| {
340 Editor::new_file(workspace, &Default::default(), window, cx)
341 },
342 )
343 .detach();
344 }
345 });
346 cx.on_action(move |_: &workspace::NewWindow, cx| {
347 let app_state = workspace::AppState::global(cx);
348 if let Some(app_state) = app_state.upgrade() {
349 workspace::open_new(
350 Default::default(),
351 app_state,
352 cx,
353 |workspace, window, cx| {
354 cx.activate(true);
355 Editor::new_file(workspace, &Default::default(), window, cx)
356 },
357 )
358 .detach();
359 }
360 });
361}
362
363pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
364 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
365}
366
367pub trait DiagnosticRenderer {
368 fn render_group(
369 &self,
370 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
371 buffer_id: BufferId,
372 snapshot: EditorSnapshot,
373 editor: WeakEntity<Editor>,
374 cx: &mut App,
375 ) -> Vec<BlockProperties<Anchor>>;
376
377 fn render_hover(
378 &self,
379 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
380 range: Range<Point>,
381 buffer_id: BufferId,
382 cx: &mut App,
383 ) -> Option<Entity<markdown::Markdown>>;
384
385 fn open_link(
386 &self,
387 editor: &mut Editor,
388 link: SharedString,
389 window: &mut Window,
390 cx: &mut Context<Editor>,
391 );
392}
393
394pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
395
396impl GlobalDiagnosticRenderer {
397 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
398 cx.try_global::<Self>().map(|g| g.0.clone())
399 }
400}
401
402impl gpui::Global for GlobalDiagnosticRenderer {}
403pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
404 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
405}
406
407pub struct SearchWithinRange;
408
409trait InvalidationRegion {
410 fn ranges(&self) -> &[Range<Anchor>];
411}
412
413#[derive(Clone, Debug, PartialEq)]
414pub enum SelectPhase {
415 Begin {
416 position: DisplayPoint,
417 add: bool,
418 click_count: usize,
419 },
420 BeginColumnar {
421 position: DisplayPoint,
422 reset: bool,
423 mode: ColumnarMode,
424 goal_column: u32,
425 },
426 Extend {
427 position: DisplayPoint,
428 click_count: usize,
429 },
430 Update {
431 position: DisplayPoint,
432 goal_column: u32,
433 scroll_delta: gpui::Point<f32>,
434 },
435 End,
436}
437
438#[derive(Clone, Debug, PartialEq)]
439pub enum ColumnarMode {
440 FromMouse,
441 FromSelection,
442}
443
444#[derive(Clone, Debug)]
445pub enum SelectMode {
446 Character,
447 Word(Range<Anchor>),
448 Line(Range<Anchor>),
449 All,
450}
451
452#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
453pub enum SizingBehavior {
454 /// The editor will layout itself using `size_full` and will include the vertical
455 /// scroll margin as requested by user settings.
456 #[default]
457 Default,
458 /// The editor will layout itself using `size_full`, but will not have any
459 /// vertical overscroll.
460 ExcludeOverscrollMargin,
461 /// The editor will request a vertical size according to its content and will be
462 /// layouted without a vertical scroll margin.
463 SizeByContent,
464}
465
466#[derive(Clone, PartialEq, Eq, Debug)]
467pub enum EditorMode {
468 SingleLine,
469 AutoHeight {
470 min_lines: usize,
471 max_lines: Option<usize>,
472 },
473 Full {
474 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
475 scale_ui_elements_with_buffer_font_size: bool,
476 /// When set to `true`, the editor will render a background for the active line.
477 show_active_line_background: bool,
478 /// Determines the sizing behavior for this editor
479 sizing_behavior: SizingBehavior,
480 },
481 Minimap {
482 parent: WeakEntity<Editor>,
483 },
484}
485
486impl EditorMode {
487 pub fn full() -> Self {
488 Self::Full {
489 scale_ui_elements_with_buffer_font_size: true,
490 show_active_line_background: true,
491 sizing_behavior: SizingBehavior::Default,
492 }
493 }
494
495 #[inline]
496 pub fn is_full(&self) -> bool {
497 matches!(self, Self::Full { .. })
498 }
499
500 #[inline]
501 pub fn is_single_line(&self) -> bool {
502 matches!(self, Self::SingleLine { .. })
503 }
504
505 #[inline]
506 fn is_minimap(&self) -> bool {
507 matches!(self, Self::Minimap { .. })
508 }
509}
510
511#[derive(Copy, Clone, Debug)]
512pub enum SoftWrap {
513 /// Prefer not to wrap at all.
514 ///
515 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
516 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
517 GitDiff,
518 /// Prefer a single line generally, unless an overly long line is encountered.
519 None,
520 /// Soft wrap lines that exceed the editor width.
521 EditorWidth,
522 /// Soft wrap lines at the preferred line length.
523 Column(u32),
524 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
525 Bounded(u32),
526}
527
528#[derive(Clone)]
529pub struct EditorStyle {
530 pub background: Hsla,
531 pub border: Hsla,
532 pub local_player: PlayerColor,
533 pub text: TextStyle,
534 pub scrollbar_width: Pixels,
535 pub syntax: Arc<SyntaxTheme>,
536 pub status: StatusColors,
537 pub inlay_hints_style: HighlightStyle,
538 pub edit_prediction_styles: EditPredictionStyles,
539 pub unnecessary_code_fade: f32,
540 pub show_underlines: bool,
541}
542
543impl Default for EditorStyle {
544 fn default() -> Self {
545 Self {
546 background: Hsla::default(),
547 border: Hsla::default(),
548 local_player: PlayerColor::default(),
549 text: TextStyle::default(),
550 scrollbar_width: Pixels::default(),
551 syntax: Default::default(),
552 // HACK: Status colors don't have a real default.
553 // We should look into removing the status colors from the editor
554 // style and retrieve them directly from the theme.
555 status: StatusColors::dark(),
556 inlay_hints_style: HighlightStyle::default(),
557 edit_prediction_styles: EditPredictionStyles {
558 insertion: HighlightStyle::default(),
559 whitespace: HighlightStyle::default(),
560 },
561 unnecessary_code_fade: Default::default(),
562 show_underlines: true,
563 }
564 }
565}
566
567pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
568 let show_background = language_settings::language_settings(None, None, cx)
569 .inlay_hints
570 .show_background;
571
572 let mut style = cx.theme().syntax().get("hint");
573
574 if style.color.is_none() {
575 style.color = Some(cx.theme().status().hint);
576 }
577
578 if !show_background {
579 style.background_color = None;
580 return style;
581 }
582
583 if style.background_color.is_none() {
584 style.background_color = Some(cx.theme().status().hint_background);
585 }
586
587 style
588}
589
590pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
591 EditPredictionStyles {
592 insertion: HighlightStyle {
593 color: Some(cx.theme().status().predictive),
594 ..HighlightStyle::default()
595 },
596 whitespace: HighlightStyle {
597 background_color: Some(cx.theme().status().created_background),
598 ..HighlightStyle::default()
599 },
600 }
601}
602
603type CompletionId = usize;
604
605pub(crate) enum EditDisplayMode {
606 TabAccept,
607 DiffPopover,
608 Inline,
609}
610
611enum EditPrediction {
612 Edit {
613 edits: Vec<(Range<Anchor>, Arc<str>)>,
614 edit_preview: Option<EditPreview>,
615 display_mode: EditDisplayMode,
616 snapshot: BufferSnapshot,
617 },
618 /// Move to a specific location in the active editor
619 MoveWithin {
620 target: Anchor,
621 snapshot: BufferSnapshot,
622 },
623 /// Move to a specific location in a different editor (not the active one)
624 MoveOutside {
625 target: language::Anchor,
626 snapshot: BufferSnapshot,
627 },
628}
629
630struct EditPredictionState {
631 inlay_ids: Vec<InlayId>,
632 completion: EditPrediction,
633 completion_id: Option<SharedString>,
634 invalidation_range: Option<Range<Anchor>>,
635}
636
637enum EditPredictionSettings {
638 Disabled,
639 Enabled {
640 show_in_menu: bool,
641 preview_requires_modifier: bool,
642 },
643}
644
645enum EditPredictionHighlight {}
646
647#[derive(Debug, Clone)]
648struct InlineDiagnostic {
649 message: SharedString,
650 group_id: usize,
651 is_primary: bool,
652 start: Point,
653 severity: lsp::DiagnosticSeverity,
654}
655
656pub enum MenuEditPredictionsPolicy {
657 Never,
658 ByProvider,
659}
660
661pub enum EditPredictionPreview {
662 /// Modifier is not pressed
663 Inactive { released_too_fast: bool },
664 /// Modifier pressed
665 Active {
666 since: Instant,
667 previous_scroll_position: Option<ScrollAnchor>,
668 },
669}
670
671impl EditPredictionPreview {
672 pub fn released_too_fast(&self) -> bool {
673 match self {
674 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
675 EditPredictionPreview::Active { .. } => false,
676 }
677 }
678
679 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
680 if let EditPredictionPreview::Active {
681 previous_scroll_position,
682 ..
683 } = self
684 {
685 *previous_scroll_position = scroll_position;
686 }
687 }
688}
689
690pub struct ContextMenuOptions {
691 pub min_entries_visible: usize,
692 pub max_entries_visible: usize,
693 pub placement: Option<ContextMenuPlacement>,
694}
695
696#[derive(Debug, Clone, PartialEq, Eq)]
697pub enum ContextMenuPlacement {
698 Above,
699 Below,
700}
701
702#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
703struct EditorActionId(usize);
704
705impl EditorActionId {
706 pub fn post_inc(&mut self) -> Self {
707 let answer = self.0;
708
709 *self = Self(answer + 1);
710
711 Self(answer)
712 }
713}
714
715// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
716// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
717
718type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
719type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
720
721#[derive(Default)]
722struct ScrollbarMarkerState {
723 scrollbar_size: Size<Pixels>,
724 dirty: bool,
725 markers: Arc<[PaintQuad]>,
726 pending_refresh: Option<Task<Result<()>>>,
727}
728
729impl ScrollbarMarkerState {
730 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
731 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
732 }
733}
734
735#[derive(Clone, Copy, PartialEq, Eq)]
736pub enum MinimapVisibility {
737 Disabled,
738 Enabled {
739 /// The configuration currently present in the users settings.
740 setting_configuration: bool,
741 /// Whether to override the currently set visibility from the users setting.
742 toggle_override: bool,
743 },
744}
745
746impl MinimapVisibility {
747 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
748 if mode.is_full() {
749 Self::Enabled {
750 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
751 toggle_override: false,
752 }
753 } else {
754 Self::Disabled
755 }
756 }
757
758 fn hidden(&self) -> Self {
759 match *self {
760 Self::Enabled {
761 setting_configuration,
762 ..
763 } => Self::Enabled {
764 setting_configuration,
765 toggle_override: setting_configuration,
766 },
767 Self::Disabled => Self::Disabled,
768 }
769 }
770
771 fn disabled(&self) -> bool {
772 matches!(*self, Self::Disabled)
773 }
774
775 fn settings_visibility(&self) -> bool {
776 match *self {
777 Self::Enabled {
778 setting_configuration,
779 ..
780 } => setting_configuration,
781 _ => false,
782 }
783 }
784
785 fn visible(&self) -> bool {
786 match *self {
787 Self::Enabled {
788 setting_configuration,
789 toggle_override,
790 } => setting_configuration ^ toggle_override,
791 _ => false,
792 }
793 }
794
795 fn toggle_visibility(&self) -> Self {
796 match *self {
797 Self::Enabled {
798 toggle_override,
799 setting_configuration,
800 } => Self::Enabled {
801 setting_configuration,
802 toggle_override: !toggle_override,
803 },
804 Self::Disabled => Self::Disabled,
805 }
806 }
807}
808
809#[derive(Debug, Clone, Copy, PartialEq, Eq)]
810pub enum BufferSerialization {
811 All,
812 NonDirtyBuffers,
813}
814
815impl BufferSerialization {
816 fn new(restore_unsaved_buffers: bool) -> Self {
817 if restore_unsaved_buffers {
818 Self::All
819 } else {
820 Self::NonDirtyBuffers
821 }
822 }
823}
824
825#[derive(Clone, Debug)]
826struct RunnableTasks {
827 templates: Vec<(TaskSourceKind, TaskTemplate)>,
828 offset: multi_buffer::Anchor,
829 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
830 column: u32,
831 // Values of all named captures, including those starting with '_'
832 extra_variables: HashMap<String, String>,
833 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
834 context_range: Range<BufferOffset>,
835}
836
837impl RunnableTasks {
838 fn resolve<'a>(
839 &'a self,
840 cx: &'a task::TaskContext,
841 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
842 self.templates.iter().filter_map(|(kind, template)| {
843 template
844 .resolve_task(&kind.to_id_base(), cx)
845 .map(|task| (kind.clone(), task))
846 })
847 }
848}
849
850#[derive(Clone)]
851pub struct ResolvedTasks {
852 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
853 position: Anchor,
854}
855
856#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
857struct BufferOffset(usize);
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 autoindent_mode: Option<AutoindentMode>,
1100 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1101 input_enabled: bool,
1102 use_modal_editing: bool,
1103 read_only: bool,
1104 leader_id: Option<CollaboratorId>,
1105 remote_id: Option<ViewId>,
1106 pub hover_state: HoverState,
1107 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1108 gutter_hovered: bool,
1109 hovered_link_state: Option<HoveredLinkState>,
1110 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1111 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1112 active_edit_prediction: Option<EditPredictionState>,
1113 /// Used to prevent flickering as the user types while the menu is open
1114 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1115 edit_prediction_settings: EditPredictionSettings,
1116 edit_predictions_hidden_for_vim_mode: bool,
1117 show_edit_predictions_override: Option<bool>,
1118 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1119 edit_prediction_preview: EditPredictionPreview,
1120 edit_prediction_indent_conflict: bool,
1121 edit_prediction_requires_modifier_in_indent_conflict: bool,
1122 next_inlay_id: usize,
1123 next_color_inlay_id: usize,
1124 _subscriptions: Vec<Subscription>,
1125 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1126 gutter_dimensions: GutterDimensions,
1127 style: Option<EditorStyle>,
1128 text_style_refinement: Option<TextStyleRefinement>,
1129 next_editor_action_id: EditorActionId,
1130 editor_actions: Rc<
1131 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1132 >,
1133 use_autoclose: bool,
1134 use_auto_surround: bool,
1135 auto_replace_emoji_shortcode: bool,
1136 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1137 show_git_blame_gutter: bool,
1138 show_git_blame_inline: bool,
1139 show_git_blame_inline_delay_task: Option<Task<()>>,
1140 git_blame_inline_enabled: bool,
1141 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1142 buffer_serialization: Option<BufferSerialization>,
1143 show_selection_menu: Option<bool>,
1144 blame: Option<Entity<GitBlame>>,
1145 blame_subscription: Option<Subscription>,
1146 custom_context_menu: Option<
1147 Box<
1148 dyn 'static
1149 + Fn(
1150 &mut Self,
1151 DisplayPoint,
1152 &mut Window,
1153 &mut Context<Self>,
1154 ) -> Option<Entity<ui::ContextMenu>>,
1155 >,
1156 >,
1157 last_bounds: Option<Bounds<Pixels>>,
1158 last_position_map: Option<Rc<PositionMap>>,
1159 expect_bounds_change: Option<Bounds<Pixels>>,
1160 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1161 tasks_update_task: Option<Task<()>>,
1162 breakpoint_store: Option<Entity<BreakpointStore>>,
1163 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1164 hovered_diff_hunk_row: Option<DisplayRow>,
1165 pull_diagnostics_task: Task<()>,
1166 in_project_search: bool,
1167 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1168 breadcrumb_header: Option<String>,
1169 focused_block: Option<FocusedBlock>,
1170 next_scroll_position: NextScrollCursorCenterTopBottom,
1171 addons: HashMap<TypeId, Box<dyn Addon>>,
1172 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1173 load_diff_task: Option<Shared<Task<()>>>,
1174 /// Whether we are temporarily displaying a diff other than git's
1175 temporary_diff_override: bool,
1176 selection_mark_mode: bool,
1177 toggle_fold_multiple_buffers: Task<()>,
1178 _scroll_cursor_center_top_bottom_task: Task<()>,
1179 serialize_selections: Task<()>,
1180 serialize_folds: Task<()>,
1181 mouse_cursor_hidden: bool,
1182 minimap: Option<Entity<Self>>,
1183 hide_mouse_mode: HideMouseMode,
1184 pub change_list: ChangeList,
1185 inline_value_cache: InlineValueCache,
1186
1187 selection_drag_state: SelectionDragState,
1188 colors: Option<LspColorData>,
1189 post_scroll_update: Task<()>,
1190 refresh_colors_task: Task<()>,
1191 inlay_hints: Option<LspInlayHintData>,
1192 folding_newlines: Task<()>,
1193 select_next_is_case_sensitive: Option<bool>,
1194 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1195}
1196
1197fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1198 if debounce_ms > 0 {
1199 Some(Duration::from_millis(debounce_ms))
1200 } else {
1201 None
1202 }
1203}
1204
1205#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1206enum NextScrollCursorCenterTopBottom {
1207 #[default]
1208 Center,
1209 Top,
1210 Bottom,
1211}
1212
1213impl NextScrollCursorCenterTopBottom {
1214 fn next(&self) -> Self {
1215 match self {
1216 Self::Center => Self::Top,
1217 Self::Top => Self::Bottom,
1218 Self::Bottom => Self::Center,
1219 }
1220 }
1221}
1222
1223#[derive(Clone)]
1224pub struct EditorSnapshot {
1225 pub mode: EditorMode,
1226 show_gutter: bool,
1227 show_line_numbers: Option<bool>,
1228 show_git_diff_gutter: Option<bool>,
1229 show_code_actions: Option<bool>,
1230 show_runnables: Option<bool>,
1231 show_breakpoints: Option<bool>,
1232 git_blame_gutter_max_author_length: Option<usize>,
1233 pub display_snapshot: DisplaySnapshot,
1234 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1235 is_focused: bool,
1236 scroll_anchor: ScrollAnchor,
1237 ongoing_scroll: OngoingScroll,
1238 current_line_highlight: CurrentLineHighlight,
1239 gutter_hovered: bool,
1240}
1241
1242#[derive(Default, Debug, Clone, Copy)]
1243pub struct GutterDimensions {
1244 pub left_padding: Pixels,
1245 pub right_padding: Pixels,
1246 pub width: Pixels,
1247 pub margin: Pixels,
1248 pub git_blame_entries_width: Option<Pixels>,
1249}
1250
1251impl GutterDimensions {
1252 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1253 Self {
1254 margin: Self::default_gutter_margin(font_id, font_size, cx),
1255 ..Default::default()
1256 }
1257 }
1258
1259 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1260 -cx.text_system().descent(font_id, font_size)
1261 }
1262 /// The full width of the space taken up by the gutter.
1263 pub fn full_width(&self) -> Pixels {
1264 self.margin + self.width
1265 }
1266
1267 /// The width of the space reserved for the fold indicators,
1268 /// use alongside 'justify_end' and `gutter_width` to
1269 /// right align content with the line numbers
1270 pub fn fold_area_width(&self) -> Pixels {
1271 self.margin + self.right_padding
1272 }
1273}
1274
1275struct CharacterDimensions {
1276 em_width: Pixels,
1277 em_advance: Pixels,
1278 line_height: Pixels,
1279}
1280
1281#[derive(Debug)]
1282pub struct RemoteSelection {
1283 pub replica_id: ReplicaId,
1284 pub selection: Selection<Anchor>,
1285 pub cursor_shape: CursorShape,
1286 pub collaborator_id: CollaboratorId,
1287 pub line_mode: bool,
1288 pub user_name: Option<SharedString>,
1289 pub color: PlayerColor,
1290}
1291
1292#[derive(Clone, Debug)]
1293struct SelectionHistoryEntry {
1294 selections: Arc<[Selection<Anchor>]>,
1295 select_next_state: Option<SelectNextState>,
1296 select_prev_state: Option<SelectNextState>,
1297 add_selections_state: Option<AddSelectionsState>,
1298}
1299
1300#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1301enum SelectionHistoryMode {
1302 Normal,
1303 Undoing,
1304 Redoing,
1305 Skipping,
1306}
1307
1308#[derive(Clone, PartialEq, Eq, Hash)]
1309struct HoveredCursor {
1310 replica_id: ReplicaId,
1311 selection_id: usize,
1312}
1313
1314impl Default for SelectionHistoryMode {
1315 fn default() -> Self {
1316 Self::Normal
1317 }
1318}
1319
1320#[derive(Debug)]
1321/// SelectionEffects controls the side-effects of updating the selection.
1322///
1323/// The default behaviour does "what you mostly want":
1324/// - it pushes to the nav history if the cursor moved by >10 lines
1325/// - it re-triggers completion requests
1326/// - it scrolls to fit
1327///
1328/// You might want to modify these behaviours. For example when doing a "jump"
1329/// like go to definition, we always want to add to nav history; but when scrolling
1330/// in vim mode we never do.
1331///
1332/// Similarly, you might want to disable scrolling if you don't want the viewport to
1333/// move.
1334#[derive(Clone)]
1335pub struct SelectionEffects {
1336 nav_history: Option<bool>,
1337 completions: bool,
1338 scroll: Option<Autoscroll>,
1339}
1340
1341impl Default for SelectionEffects {
1342 fn default() -> Self {
1343 Self {
1344 nav_history: None,
1345 completions: true,
1346 scroll: Some(Autoscroll::fit()),
1347 }
1348 }
1349}
1350impl SelectionEffects {
1351 pub fn scroll(scroll: Autoscroll) -> Self {
1352 Self {
1353 scroll: Some(scroll),
1354 ..Default::default()
1355 }
1356 }
1357
1358 pub fn no_scroll() -> Self {
1359 Self {
1360 scroll: None,
1361 ..Default::default()
1362 }
1363 }
1364
1365 pub fn completions(self, completions: bool) -> Self {
1366 Self {
1367 completions,
1368 ..self
1369 }
1370 }
1371
1372 pub fn nav_history(self, nav_history: bool) -> Self {
1373 Self {
1374 nav_history: Some(nav_history),
1375 ..self
1376 }
1377 }
1378}
1379
1380struct DeferredSelectionEffectsState {
1381 changed: bool,
1382 effects: SelectionEffects,
1383 old_cursor_position: Anchor,
1384 history_entry: SelectionHistoryEntry,
1385}
1386
1387#[derive(Default)]
1388struct SelectionHistory {
1389 #[allow(clippy::type_complexity)]
1390 selections_by_transaction:
1391 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1392 mode: SelectionHistoryMode,
1393 undo_stack: VecDeque<SelectionHistoryEntry>,
1394 redo_stack: VecDeque<SelectionHistoryEntry>,
1395}
1396
1397impl SelectionHistory {
1398 #[track_caller]
1399 fn insert_transaction(
1400 &mut self,
1401 transaction_id: TransactionId,
1402 selections: Arc<[Selection<Anchor>]>,
1403 ) {
1404 if selections.is_empty() {
1405 log::error!(
1406 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1407 std::panic::Location::caller()
1408 );
1409 return;
1410 }
1411 self.selections_by_transaction
1412 .insert(transaction_id, (selections, None));
1413 }
1414
1415 #[allow(clippy::type_complexity)]
1416 fn transaction(
1417 &self,
1418 transaction_id: TransactionId,
1419 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1420 self.selections_by_transaction.get(&transaction_id)
1421 }
1422
1423 #[allow(clippy::type_complexity)]
1424 fn transaction_mut(
1425 &mut self,
1426 transaction_id: TransactionId,
1427 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1428 self.selections_by_transaction.get_mut(&transaction_id)
1429 }
1430
1431 fn push(&mut self, entry: SelectionHistoryEntry) {
1432 if !entry.selections.is_empty() {
1433 match self.mode {
1434 SelectionHistoryMode::Normal => {
1435 self.push_undo(entry);
1436 self.redo_stack.clear();
1437 }
1438 SelectionHistoryMode::Undoing => self.push_redo(entry),
1439 SelectionHistoryMode::Redoing => self.push_undo(entry),
1440 SelectionHistoryMode::Skipping => {}
1441 }
1442 }
1443 }
1444
1445 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1446 if self
1447 .undo_stack
1448 .back()
1449 .is_none_or(|e| e.selections != entry.selections)
1450 {
1451 self.undo_stack.push_back(entry);
1452 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1453 self.undo_stack.pop_front();
1454 }
1455 }
1456 }
1457
1458 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1459 if self
1460 .redo_stack
1461 .back()
1462 .is_none_or(|e| e.selections != entry.selections)
1463 {
1464 self.redo_stack.push_back(entry);
1465 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1466 self.redo_stack.pop_front();
1467 }
1468 }
1469 }
1470}
1471
1472#[derive(Clone, Copy)]
1473pub struct RowHighlightOptions {
1474 pub autoscroll: bool,
1475 pub include_gutter: bool,
1476}
1477
1478impl Default for RowHighlightOptions {
1479 fn default() -> Self {
1480 Self {
1481 autoscroll: Default::default(),
1482 include_gutter: true,
1483 }
1484 }
1485}
1486
1487struct RowHighlight {
1488 index: usize,
1489 range: Range<Anchor>,
1490 color: Hsla,
1491 options: RowHighlightOptions,
1492 type_id: TypeId,
1493}
1494
1495#[derive(Clone, Debug)]
1496struct AddSelectionsState {
1497 groups: Vec<AddSelectionsGroup>,
1498}
1499
1500#[derive(Clone, Debug)]
1501struct AddSelectionsGroup {
1502 above: bool,
1503 stack: Vec<usize>,
1504}
1505
1506#[derive(Clone)]
1507struct SelectNextState {
1508 query: AhoCorasick,
1509 wordwise: bool,
1510 done: bool,
1511}
1512
1513impl std::fmt::Debug for SelectNextState {
1514 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1515 f.debug_struct(std::any::type_name::<Self>())
1516 .field("wordwise", &self.wordwise)
1517 .field("done", &self.done)
1518 .finish()
1519 }
1520}
1521
1522#[derive(Debug)]
1523struct AutocloseRegion {
1524 selection_id: usize,
1525 range: Range<Anchor>,
1526 pair: BracketPair,
1527}
1528
1529#[derive(Debug)]
1530struct SnippetState {
1531 ranges: Vec<Vec<Range<Anchor>>>,
1532 active_index: usize,
1533 choices: Vec<Option<Vec<String>>>,
1534}
1535
1536#[doc(hidden)]
1537pub struct RenameState {
1538 pub range: Range<Anchor>,
1539 pub old_name: Arc<str>,
1540 pub editor: Entity<Editor>,
1541 block_id: CustomBlockId,
1542}
1543
1544struct InvalidationStack<T>(Vec<T>);
1545
1546struct RegisteredEditPredictionProvider {
1547 provider: Arc<dyn EditPredictionProviderHandle>,
1548 _subscription: Subscription,
1549}
1550
1551#[derive(Debug, PartialEq, Eq)]
1552pub struct ActiveDiagnosticGroup {
1553 pub active_range: Range<Anchor>,
1554 pub active_message: String,
1555 pub group_id: usize,
1556 pub blocks: HashSet<CustomBlockId>,
1557}
1558
1559#[derive(Debug, PartialEq, Eq)]
1560
1561pub(crate) enum ActiveDiagnostic {
1562 None,
1563 All,
1564 Group(ActiveDiagnosticGroup),
1565}
1566
1567#[derive(Serialize, Deserialize, Clone, Debug)]
1568pub struct ClipboardSelection {
1569 /// The number of bytes in this selection.
1570 pub len: usize,
1571 /// Whether this was a full-line selection.
1572 pub is_entire_line: bool,
1573 /// The indentation of the first line when this content was originally copied.
1574 pub first_line_indent: u32,
1575}
1576
1577// selections, scroll behavior, was newest selection reversed
1578type SelectSyntaxNodeHistoryState = (
1579 Box<[Selection<usize>]>,
1580 SelectSyntaxNodeScrollBehavior,
1581 bool,
1582);
1583
1584#[derive(Default)]
1585struct SelectSyntaxNodeHistory {
1586 stack: Vec<SelectSyntaxNodeHistoryState>,
1587 // disable temporarily to allow changing selections without losing the stack
1588 pub disable_clearing: bool,
1589}
1590
1591impl SelectSyntaxNodeHistory {
1592 pub fn try_clear(&mut self) {
1593 if !self.disable_clearing {
1594 self.stack.clear();
1595 }
1596 }
1597
1598 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1599 self.stack.push(selection);
1600 }
1601
1602 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1603 self.stack.pop()
1604 }
1605}
1606
1607enum SelectSyntaxNodeScrollBehavior {
1608 CursorTop,
1609 FitSelection,
1610 CursorBottom,
1611}
1612
1613#[derive(Debug)]
1614pub(crate) struct NavigationData {
1615 cursor_anchor: Anchor,
1616 cursor_position: Point,
1617 scroll_anchor: ScrollAnchor,
1618 scroll_top_row: u32,
1619}
1620
1621#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1622pub enum GotoDefinitionKind {
1623 Symbol,
1624 Declaration,
1625 Type,
1626 Implementation,
1627}
1628
1629pub enum FormatTarget {
1630 Buffers(HashSet<Entity<Buffer>>),
1631 Ranges(Vec<Range<MultiBufferPoint>>),
1632}
1633
1634pub(crate) struct FocusedBlock {
1635 id: BlockId,
1636 focus_handle: WeakFocusHandle,
1637}
1638
1639#[derive(Clone)]
1640enum JumpData {
1641 MultiBufferRow {
1642 row: MultiBufferRow,
1643 line_offset_from_top: u32,
1644 },
1645 MultiBufferPoint {
1646 excerpt_id: ExcerptId,
1647 position: Point,
1648 anchor: text::Anchor,
1649 line_offset_from_top: u32,
1650 },
1651}
1652
1653pub enum MultibufferSelectionMode {
1654 First,
1655 All,
1656}
1657
1658#[derive(Clone, Copy, Debug, Default)]
1659pub struct RewrapOptions {
1660 pub override_language_settings: bool,
1661 pub preserve_existing_whitespace: bool,
1662}
1663
1664impl Editor {
1665 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1666 let buffer = cx.new(|cx| Buffer::local("", cx));
1667 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1668 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1669 }
1670
1671 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1672 let buffer = cx.new(|cx| Buffer::local("", cx));
1673 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1674 Self::new(EditorMode::full(), buffer, None, window, cx)
1675 }
1676
1677 pub fn auto_height(
1678 min_lines: usize,
1679 max_lines: usize,
1680 window: &mut Window,
1681 cx: &mut Context<Self>,
1682 ) -> Self {
1683 let buffer = cx.new(|cx| Buffer::local("", cx));
1684 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1685 Self::new(
1686 EditorMode::AutoHeight {
1687 min_lines,
1688 max_lines: Some(max_lines),
1689 },
1690 buffer,
1691 None,
1692 window,
1693 cx,
1694 )
1695 }
1696
1697 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1698 /// The editor grows as tall as needed to fit its content.
1699 pub fn auto_height_unbounded(
1700 min_lines: usize,
1701 window: &mut Window,
1702 cx: &mut Context<Self>,
1703 ) -> Self {
1704 let buffer = cx.new(|cx| Buffer::local("", cx));
1705 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1706 Self::new(
1707 EditorMode::AutoHeight {
1708 min_lines,
1709 max_lines: None,
1710 },
1711 buffer,
1712 None,
1713 window,
1714 cx,
1715 )
1716 }
1717
1718 pub fn for_buffer(
1719 buffer: Entity<Buffer>,
1720 project: Option<Entity<Project>>,
1721 window: &mut Window,
1722 cx: &mut Context<Self>,
1723 ) -> Self {
1724 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1725 Self::new(EditorMode::full(), buffer, project, window, cx)
1726 }
1727
1728 pub fn for_multibuffer(
1729 buffer: Entity<MultiBuffer>,
1730 project: Option<Entity<Project>>,
1731 window: &mut Window,
1732 cx: &mut Context<Self>,
1733 ) -> Self {
1734 Self::new(EditorMode::full(), buffer, project, window, cx)
1735 }
1736
1737 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1738 let mut clone = Self::new(
1739 self.mode.clone(),
1740 self.buffer.clone(),
1741 self.project.clone(),
1742 window,
1743 cx,
1744 );
1745 self.display_map.update(cx, |display_map, cx| {
1746 let snapshot = display_map.snapshot(cx);
1747 clone.display_map.update(cx, |display_map, cx| {
1748 display_map.set_state(&snapshot, cx);
1749 });
1750 });
1751 clone.folds_did_change(cx);
1752 clone.selections.clone_state(&self.selections);
1753 clone.scroll_manager.clone_state(&self.scroll_manager);
1754 clone.searchable = self.searchable;
1755 clone.read_only = self.read_only;
1756 clone
1757 }
1758
1759 pub fn new(
1760 mode: EditorMode,
1761 buffer: Entity<MultiBuffer>,
1762 project: Option<Entity<Project>>,
1763 window: &mut Window,
1764 cx: &mut Context<Self>,
1765 ) -> Self {
1766 Editor::new_internal(mode, buffer, project, None, window, cx)
1767 }
1768
1769 pub fn sticky_headers(&self, cx: &App) -> Option<Vec<OutlineItem<Anchor>>> {
1770 let multi_buffer = self.buffer().read(cx);
1771 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1772 let multi_buffer_visible_start = self
1773 .scroll_manager
1774 .anchor()
1775 .anchor
1776 .to_point(&multi_buffer_snapshot);
1777 let max_row = multi_buffer_snapshot.max_point().row;
1778
1779 let start_row = (multi_buffer_visible_start.row).min(max_row);
1780 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1781
1782 if let Some((excerpt_id, buffer_id, buffer)) = multi_buffer.read(cx).as_singleton() {
1783 let outline_items = buffer
1784 .outline_items_containing(
1785 Point::new(start_row, 0)..Point::new(end_row, 0),
1786 true,
1787 self.style().map(|style| style.syntax.as_ref()),
1788 )
1789 .into_iter()
1790 .map(|outline_item| OutlineItem {
1791 depth: outline_item.depth,
1792 range: Anchor::range_in_buffer(*excerpt_id, buffer_id, outline_item.range),
1793 source_range_for_text: Anchor::range_in_buffer(
1794 *excerpt_id,
1795 buffer_id,
1796 outline_item.source_range_for_text,
1797 ),
1798 text: outline_item.text,
1799 highlight_ranges: outline_item.highlight_ranges,
1800 name_ranges: outline_item.name_ranges,
1801 body_range: outline_item
1802 .body_range
1803 .map(|range| Anchor::range_in_buffer(*excerpt_id, buffer_id, range)),
1804 annotation_range: outline_item
1805 .annotation_range
1806 .map(|range| Anchor::range_in_buffer(*excerpt_id, buffer_id, range)),
1807 });
1808 return Some(outline_items.collect());
1809 }
1810
1811 None
1812 }
1813
1814 fn new_internal(
1815 mode: EditorMode,
1816 multi_buffer: Entity<MultiBuffer>,
1817 project: Option<Entity<Project>>,
1818 display_map: Option<Entity<DisplayMap>>,
1819 window: &mut Window,
1820 cx: &mut Context<Self>,
1821 ) -> Self {
1822 debug_assert!(
1823 display_map.is_none() || mode.is_minimap(),
1824 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1825 );
1826
1827 let full_mode = mode.is_full();
1828 let is_minimap = mode.is_minimap();
1829 let diagnostics_max_severity = if full_mode {
1830 EditorSettings::get_global(cx)
1831 .diagnostics_max_severity
1832 .unwrap_or(DiagnosticSeverity::Hint)
1833 } else {
1834 DiagnosticSeverity::Off
1835 };
1836 let style = window.text_style();
1837 let font_size = style.font_size.to_pixels(window.rem_size());
1838 let editor = cx.entity().downgrade();
1839 let fold_placeholder = FoldPlaceholder {
1840 constrain_width: false,
1841 render: Arc::new(move |fold_id, fold_range, cx| {
1842 let editor = editor.clone();
1843 div()
1844 .id(fold_id)
1845 .bg(cx.theme().colors().ghost_element_background)
1846 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1847 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1848 .rounded_xs()
1849 .size_full()
1850 .cursor_pointer()
1851 .child("⋯")
1852 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1853 .on_click(move |_, _window, cx| {
1854 editor
1855 .update(cx, |editor, cx| {
1856 editor.unfold_ranges(
1857 &[fold_range.start..fold_range.end],
1858 true,
1859 false,
1860 cx,
1861 );
1862 cx.stop_propagation();
1863 })
1864 .ok();
1865 })
1866 .into_any()
1867 }),
1868 merge_adjacent: true,
1869 ..FoldPlaceholder::default()
1870 };
1871 let display_map = display_map.unwrap_or_else(|| {
1872 cx.new(|cx| {
1873 DisplayMap::new(
1874 multi_buffer.clone(),
1875 style.font(),
1876 font_size,
1877 None,
1878 FILE_HEADER_HEIGHT,
1879 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1880 fold_placeholder,
1881 diagnostics_max_severity,
1882 cx,
1883 )
1884 })
1885 });
1886
1887 let selections = SelectionsCollection::new();
1888
1889 let blink_manager = cx.new(|cx| {
1890 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1891 if is_minimap {
1892 blink_manager.disable(cx);
1893 }
1894 blink_manager
1895 });
1896
1897 let soft_wrap_mode_override =
1898 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1899
1900 let mut project_subscriptions = Vec::new();
1901 if full_mode && let Some(project) = project.as_ref() {
1902 project_subscriptions.push(cx.subscribe_in(
1903 project,
1904 window,
1905 |editor, _, event, window, cx| match event {
1906 project::Event::RefreshCodeLens => {
1907 // we always query lens with actions, without storing them, always refreshing them
1908 }
1909 project::Event::RefreshInlayHints {
1910 server_id,
1911 request_id,
1912 } => {
1913 editor.refresh_inlay_hints(
1914 InlayHintRefreshReason::RefreshRequested {
1915 server_id: *server_id,
1916 request_id: *request_id,
1917 },
1918 cx,
1919 );
1920 }
1921 project::Event::LanguageServerRemoved(..) => {
1922 if editor.tasks_update_task.is_none() {
1923 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1924 }
1925 editor.registered_buffers.clear();
1926 editor.register_visible_buffers(cx);
1927 }
1928 project::Event::LanguageServerAdded(..) => {
1929 if editor.tasks_update_task.is_none() {
1930 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1931 }
1932 }
1933 project::Event::SnippetEdit(id, snippet_edits) => {
1934 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1935 let focus_handle = editor.focus_handle(cx);
1936 if focus_handle.is_focused(window) {
1937 let snapshot = buffer.read(cx).snapshot();
1938 for (range, snippet) in snippet_edits {
1939 let editor_range =
1940 language::range_from_lsp(*range).to_offset(&snapshot);
1941 editor
1942 .insert_snippet(
1943 &[editor_range],
1944 snippet.clone(),
1945 window,
1946 cx,
1947 )
1948 .ok();
1949 }
1950 }
1951 }
1952 }
1953 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1954 let buffer_id = *buffer_id;
1955 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1956 editor.register_buffer(buffer_id, cx);
1957 editor.update_lsp_data(Some(buffer_id), window, cx);
1958 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1959 refresh_linked_ranges(editor, window, cx);
1960 editor.refresh_code_actions(window, cx);
1961 editor.refresh_document_highlights(cx);
1962 }
1963 }
1964
1965 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
1966 let Some(workspace) = editor.workspace() else {
1967 return;
1968 };
1969 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1970 else {
1971 return;
1972 };
1973
1974 if active_editor.entity_id() == cx.entity_id() {
1975 let entity_id = cx.entity_id();
1976 workspace.update(cx, |this, cx| {
1977 this.panes_mut()
1978 .iter_mut()
1979 .filter(|pane| pane.entity_id() != entity_id)
1980 .for_each(|p| {
1981 p.update(cx, |pane, _| {
1982 pane.nav_history_mut().rename_item(
1983 entity_id,
1984 project_path.clone(),
1985 abs_path.clone().into(),
1986 );
1987 })
1988 });
1989 });
1990 let edited_buffers_already_open = {
1991 let other_editors: Vec<Entity<Editor>> = workspace
1992 .read(cx)
1993 .panes()
1994 .iter()
1995 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1996 .filter(|editor| editor.entity_id() != cx.entity_id())
1997 .collect();
1998
1999 transaction.0.keys().all(|buffer| {
2000 other_editors.iter().any(|editor| {
2001 let multi_buffer = editor.read(cx).buffer();
2002 multi_buffer.read(cx).is_singleton()
2003 && multi_buffer.read(cx).as_singleton().map_or(
2004 false,
2005 |singleton| {
2006 singleton.entity_id() == buffer.entity_id()
2007 },
2008 )
2009 })
2010 })
2011 };
2012 if !edited_buffers_already_open {
2013 let workspace = workspace.downgrade();
2014 let transaction = transaction.clone();
2015 cx.defer_in(window, move |_, window, cx| {
2016 cx.spawn_in(window, async move |editor, cx| {
2017 Self::open_project_transaction(
2018 &editor,
2019 workspace,
2020 transaction,
2021 "Rename".to_string(),
2022 cx,
2023 )
2024 .await
2025 .ok()
2026 })
2027 .detach();
2028 });
2029 }
2030 }
2031 }
2032
2033 _ => {}
2034 },
2035 ));
2036 if let Some(task_inventory) = project
2037 .read(cx)
2038 .task_store()
2039 .read(cx)
2040 .task_inventory()
2041 .cloned()
2042 {
2043 project_subscriptions.push(cx.observe_in(
2044 &task_inventory,
2045 window,
2046 |editor, _, window, cx| {
2047 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2048 },
2049 ));
2050 };
2051
2052 project_subscriptions.push(cx.subscribe_in(
2053 &project.read(cx).breakpoint_store(),
2054 window,
2055 |editor, _, event, window, cx| match event {
2056 BreakpointStoreEvent::ClearDebugLines => {
2057 editor.clear_row_highlights::<ActiveDebugLine>();
2058 editor.refresh_inline_values(cx);
2059 }
2060 BreakpointStoreEvent::SetDebugLine => {
2061 if editor.go_to_active_debug_line(window, cx) {
2062 cx.stop_propagation();
2063 }
2064
2065 editor.refresh_inline_values(cx);
2066 }
2067 _ => {}
2068 },
2069 ));
2070 let git_store = project.read(cx).git_store().clone();
2071 let project = project.clone();
2072 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2073 if let GitStoreEvent::RepositoryAdded = event {
2074 this.load_diff_task = Some(
2075 update_uncommitted_diff_for_buffer(
2076 cx.entity(),
2077 &project,
2078 this.buffer.read(cx).all_buffers(),
2079 this.buffer.clone(),
2080 cx,
2081 )
2082 .shared(),
2083 );
2084 }
2085 }));
2086 }
2087
2088 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2089
2090 let inlay_hint_settings =
2091 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2092 let focus_handle = cx.focus_handle();
2093 if !is_minimap {
2094 cx.on_focus(&focus_handle, window, Self::handle_focus)
2095 .detach();
2096 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2097 .detach();
2098 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2099 .detach();
2100 cx.on_blur(&focus_handle, window, Self::handle_blur)
2101 .detach();
2102 cx.observe_pending_input(window, Self::observe_pending_input)
2103 .detach();
2104 }
2105
2106 let show_indent_guides =
2107 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2108 Some(false)
2109 } else {
2110 None
2111 };
2112
2113 let breakpoint_store = match (&mode, project.as_ref()) {
2114 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2115 _ => None,
2116 };
2117
2118 let mut code_action_providers = Vec::new();
2119 let mut load_uncommitted_diff = None;
2120 if let Some(project) = project.clone() {
2121 load_uncommitted_diff = Some(
2122 update_uncommitted_diff_for_buffer(
2123 cx.entity(),
2124 &project,
2125 multi_buffer.read(cx).all_buffers(),
2126 multi_buffer.clone(),
2127 cx,
2128 )
2129 .shared(),
2130 );
2131 code_action_providers.push(Rc::new(project) as Rc<_>);
2132 }
2133
2134 let mut editor = Self {
2135 focus_handle,
2136 show_cursor_when_unfocused: false,
2137 last_focused_descendant: None,
2138 buffer: multi_buffer.clone(),
2139 display_map: display_map.clone(),
2140 placeholder_display_map: None,
2141 selections,
2142 scroll_manager: ScrollManager::new(cx),
2143 columnar_selection_state: None,
2144 add_selections_state: None,
2145 select_next_state: None,
2146 select_prev_state: None,
2147 selection_history: SelectionHistory::default(),
2148 defer_selection_effects: false,
2149 deferred_selection_effects_state: None,
2150 autoclose_regions: Vec::new(),
2151 snippet_stack: InvalidationStack::default(),
2152 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2153 ime_transaction: None,
2154 active_diagnostics: ActiveDiagnostic::None,
2155 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2156 inline_diagnostics_update: Task::ready(()),
2157 inline_diagnostics: Vec::new(),
2158 soft_wrap_mode_override,
2159 diagnostics_max_severity,
2160 hard_wrap: None,
2161 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2162 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2163 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2164 project,
2165 blink_manager: blink_manager.clone(),
2166 show_local_selections: true,
2167 show_scrollbars: ScrollbarAxes {
2168 horizontal: full_mode,
2169 vertical: full_mode,
2170 },
2171 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2172 offset_content: !matches!(mode, EditorMode::SingleLine),
2173 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2174 show_gutter: full_mode,
2175 show_line_numbers: (!full_mode).then_some(false),
2176 use_relative_line_numbers: None,
2177 disable_expand_excerpt_buttons: !full_mode,
2178 show_git_diff_gutter: None,
2179 show_code_actions: None,
2180 show_runnables: None,
2181 show_breakpoints: None,
2182 show_wrap_guides: None,
2183 show_indent_guides,
2184 highlight_order: 0,
2185 highlighted_rows: HashMap::default(),
2186 background_highlights: HashMap::default(),
2187 gutter_highlights: HashMap::default(),
2188 scrollbar_marker_state: ScrollbarMarkerState::default(),
2189 active_indent_guides_state: ActiveIndentGuidesState::default(),
2190 nav_history: None,
2191 context_menu: RefCell::new(None),
2192 context_menu_options: None,
2193 mouse_context_menu: None,
2194 completion_tasks: Vec::new(),
2195 inline_blame_popover: None,
2196 inline_blame_popover_show_task: None,
2197 signature_help_state: SignatureHelpState::default(),
2198 auto_signature_help: None,
2199 find_all_references_task_sources: Vec::new(),
2200 next_completion_id: 0,
2201 next_inlay_id: 0,
2202 code_action_providers,
2203 available_code_actions: None,
2204 code_actions_task: None,
2205 quick_selection_highlight_task: None,
2206 debounced_selection_highlight_task: None,
2207 document_highlights_task: None,
2208 linked_editing_range_task: None,
2209 pending_rename: None,
2210 searchable: !is_minimap,
2211 cursor_shape: EditorSettings::get_global(cx)
2212 .cursor_shape
2213 .unwrap_or_default(),
2214 current_line_highlight: None,
2215 autoindent_mode: Some(AutoindentMode::EachLine),
2216
2217 workspace: None,
2218 input_enabled: !is_minimap,
2219 use_modal_editing: full_mode,
2220 read_only: is_minimap,
2221 use_autoclose: true,
2222 use_auto_surround: true,
2223 auto_replace_emoji_shortcode: false,
2224 jsx_tag_auto_close_enabled_in_any_buffer: false,
2225 leader_id: None,
2226 remote_id: None,
2227 hover_state: HoverState::default(),
2228 pending_mouse_down: None,
2229 hovered_link_state: None,
2230 edit_prediction_provider: None,
2231 active_edit_prediction: None,
2232 stale_edit_prediction_in_menu: None,
2233 edit_prediction_preview: EditPredictionPreview::Inactive {
2234 released_too_fast: false,
2235 },
2236 inline_diagnostics_enabled: full_mode,
2237 diagnostics_enabled: full_mode,
2238 word_completions_enabled: full_mode,
2239 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2240 gutter_hovered: false,
2241 pixel_position_of_newest_cursor: None,
2242 last_bounds: None,
2243 last_position_map: None,
2244 expect_bounds_change: None,
2245 gutter_dimensions: GutterDimensions::default(),
2246 style: None,
2247 show_cursor_names: false,
2248 hovered_cursors: HashMap::default(),
2249 next_editor_action_id: EditorActionId::default(),
2250 editor_actions: Rc::default(),
2251 edit_predictions_hidden_for_vim_mode: false,
2252 show_edit_predictions_override: None,
2253 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2254 edit_prediction_settings: EditPredictionSettings::Disabled,
2255 edit_prediction_indent_conflict: false,
2256 edit_prediction_requires_modifier_in_indent_conflict: true,
2257 custom_context_menu: None,
2258 show_git_blame_gutter: false,
2259 show_git_blame_inline: false,
2260 show_selection_menu: None,
2261 show_git_blame_inline_delay_task: None,
2262 git_blame_inline_enabled: full_mode
2263 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2264 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2265 buffer_serialization: is_minimap.not().then(|| {
2266 BufferSerialization::new(
2267 ProjectSettings::get_global(cx)
2268 .session
2269 .restore_unsaved_buffers,
2270 )
2271 }),
2272 blame: None,
2273 blame_subscription: None,
2274 tasks: BTreeMap::default(),
2275
2276 breakpoint_store,
2277 gutter_breakpoint_indicator: (None, None),
2278 hovered_diff_hunk_row: None,
2279 _subscriptions: (!is_minimap)
2280 .then(|| {
2281 vec![
2282 cx.observe(&multi_buffer, Self::on_buffer_changed),
2283 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2284 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2285 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2286 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2287 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2288 cx.observe_window_activation(window, |editor, window, cx| {
2289 let active = window.is_window_active();
2290 editor.blink_manager.update(cx, |blink_manager, cx| {
2291 if active {
2292 blink_manager.enable(cx);
2293 } else {
2294 blink_manager.disable(cx);
2295 }
2296 });
2297 if active {
2298 editor.show_mouse_cursor(cx);
2299 }
2300 }),
2301 ]
2302 })
2303 .unwrap_or_default(),
2304 tasks_update_task: None,
2305 pull_diagnostics_task: Task::ready(()),
2306 colors: None,
2307 refresh_colors_task: Task::ready(()),
2308 inlay_hints: None,
2309 next_color_inlay_id: 0,
2310 post_scroll_update: Task::ready(()),
2311 linked_edit_ranges: Default::default(),
2312 in_project_search: false,
2313 previous_search_ranges: None,
2314 breadcrumb_header: None,
2315 focused_block: None,
2316 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2317 addons: HashMap::default(),
2318 registered_buffers: HashMap::default(),
2319 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2320 selection_mark_mode: false,
2321 toggle_fold_multiple_buffers: Task::ready(()),
2322 serialize_selections: Task::ready(()),
2323 serialize_folds: Task::ready(()),
2324 text_style_refinement: None,
2325 load_diff_task: load_uncommitted_diff,
2326 temporary_diff_override: false,
2327 mouse_cursor_hidden: false,
2328 minimap: None,
2329 hide_mouse_mode: EditorSettings::get_global(cx)
2330 .hide_mouse
2331 .unwrap_or_default(),
2332 change_list: ChangeList::new(),
2333 mode,
2334 selection_drag_state: SelectionDragState::None,
2335 folding_newlines: Task::ready(()),
2336 lookup_key: None,
2337 select_next_is_case_sensitive: None,
2338 };
2339
2340 if is_minimap {
2341 return editor;
2342 }
2343
2344 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2345 editor
2346 ._subscriptions
2347 .push(cx.observe(breakpoints, |_, _, cx| {
2348 cx.notify();
2349 }));
2350 }
2351 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2352 editor._subscriptions.extend(project_subscriptions);
2353
2354 editor._subscriptions.push(cx.subscribe_in(
2355 &cx.entity(),
2356 window,
2357 |editor, _, e: &EditorEvent, window, cx| match e {
2358 EditorEvent::ScrollPositionChanged { local, .. } => {
2359 if *local {
2360 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2361 editor.inline_blame_popover.take();
2362 let new_anchor = editor.scroll_manager.anchor();
2363 let snapshot = editor.snapshot(window, cx);
2364 editor.update_restoration_data(cx, move |data| {
2365 data.scroll_position = (
2366 new_anchor.top_row(snapshot.buffer_snapshot()),
2367 new_anchor.offset,
2368 );
2369 });
2370
2371 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2372 cx.background_executor()
2373 .timer(Duration::from_millis(50))
2374 .await;
2375 editor
2376 .update_in(cx, |editor, window, cx| {
2377 editor.register_visible_buffers(cx);
2378 editor.refresh_colors_for_visible_range(None, window, cx);
2379 editor.refresh_inlay_hints(
2380 InlayHintRefreshReason::NewLinesShown,
2381 cx,
2382 );
2383 })
2384 .ok();
2385 });
2386 }
2387 }
2388 EditorEvent::Edited { .. } => {
2389 if vim_flavor(cx).is_none() {
2390 let display_map = editor.display_snapshot(cx);
2391 let selections = editor.selections.all_adjusted_display(&display_map);
2392 let pop_state = editor
2393 .change_list
2394 .last()
2395 .map(|previous| {
2396 previous.len() == selections.len()
2397 && previous.iter().enumerate().all(|(ix, p)| {
2398 p.to_display_point(&display_map).row()
2399 == selections[ix].head().row()
2400 })
2401 })
2402 .unwrap_or(false);
2403 let new_positions = selections
2404 .into_iter()
2405 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2406 .collect();
2407 editor
2408 .change_list
2409 .push_to_change_list(pop_state, new_positions);
2410 }
2411 }
2412 _ => (),
2413 },
2414 ));
2415
2416 if let Some(dap_store) = editor
2417 .project
2418 .as_ref()
2419 .map(|project| project.read(cx).dap_store())
2420 {
2421 let weak_editor = cx.weak_entity();
2422
2423 editor
2424 ._subscriptions
2425 .push(
2426 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2427 let session_entity = cx.entity();
2428 weak_editor
2429 .update(cx, |editor, cx| {
2430 editor._subscriptions.push(
2431 cx.subscribe(&session_entity, Self::on_debug_session_event),
2432 );
2433 })
2434 .ok();
2435 }),
2436 );
2437
2438 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2439 editor
2440 ._subscriptions
2441 .push(cx.subscribe(&session, Self::on_debug_session_event));
2442 }
2443 }
2444
2445 // skip adding the initial selection to selection history
2446 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2447 editor.end_selection(window, cx);
2448 editor.selection_history.mode = SelectionHistoryMode::Normal;
2449
2450 editor.scroll_manager.show_scrollbars(window, cx);
2451 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2452
2453 if full_mode {
2454 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2455 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2456
2457 if editor.git_blame_inline_enabled {
2458 editor.start_git_blame_inline(false, window, cx);
2459 }
2460
2461 editor.go_to_active_debug_line(window, cx);
2462
2463 editor.minimap =
2464 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2465 editor.colors = Some(LspColorData::new(cx));
2466 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2467
2468 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2469 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2470 }
2471 editor.update_lsp_data(None, window, cx);
2472 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2473 }
2474
2475 editor
2476 }
2477
2478 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2479 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2480 }
2481
2482 pub fn deploy_mouse_context_menu(
2483 &mut self,
2484 position: gpui::Point<Pixels>,
2485 context_menu: Entity<ContextMenu>,
2486 window: &mut Window,
2487 cx: &mut Context<Self>,
2488 ) {
2489 self.mouse_context_menu = Some(MouseContextMenu::new(
2490 self,
2491 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2492 context_menu,
2493 window,
2494 cx,
2495 ));
2496 }
2497
2498 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2499 self.mouse_context_menu
2500 .as_ref()
2501 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2502 }
2503
2504 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2505 if self
2506 .selections
2507 .pending_anchor()
2508 .is_some_and(|pending_selection| {
2509 let snapshot = self.buffer().read(cx).snapshot(cx);
2510 pending_selection.range().includes(range, &snapshot)
2511 })
2512 {
2513 return true;
2514 }
2515
2516 self.selections
2517 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2518 .into_iter()
2519 .any(|selection| {
2520 // This is needed to cover a corner case, if we just check for an existing
2521 // selection in the fold range, having a cursor at the start of the fold
2522 // marks it as selected. Non-empty selections don't cause this.
2523 let length = selection.end - selection.start;
2524 length > 0
2525 })
2526 }
2527
2528 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2529 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2530 }
2531
2532 fn key_context_internal(
2533 &self,
2534 has_active_edit_prediction: bool,
2535 window: &mut Window,
2536 cx: &mut App,
2537 ) -> KeyContext {
2538 let mut key_context = KeyContext::new_with_defaults();
2539 key_context.add("Editor");
2540 let mode = match self.mode {
2541 EditorMode::SingleLine => "single_line",
2542 EditorMode::AutoHeight { .. } => "auto_height",
2543 EditorMode::Minimap { .. } => "minimap",
2544 EditorMode::Full { .. } => "full",
2545 };
2546
2547 if EditorSettings::jupyter_enabled(cx) {
2548 key_context.add("jupyter");
2549 }
2550
2551 key_context.set("mode", mode);
2552 if self.pending_rename.is_some() {
2553 key_context.add("renaming");
2554 }
2555
2556 if let Some(snippet_stack) = self.snippet_stack.last() {
2557 key_context.add("in_snippet");
2558
2559 if snippet_stack.active_index > 0 {
2560 key_context.add("has_previous_tabstop");
2561 }
2562
2563 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2564 key_context.add("has_next_tabstop");
2565 }
2566 }
2567
2568 match self.context_menu.borrow().as_ref() {
2569 Some(CodeContextMenu::Completions(menu)) => {
2570 if menu.visible() {
2571 key_context.add("menu");
2572 key_context.add("showing_completions");
2573 }
2574 }
2575 Some(CodeContextMenu::CodeActions(menu)) => {
2576 if menu.visible() {
2577 key_context.add("menu");
2578 key_context.add("showing_code_actions")
2579 }
2580 }
2581 None => {}
2582 }
2583
2584 if self.signature_help_state.has_multiple_signatures() {
2585 key_context.add("showing_signature_help");
2586 }
2587
2588 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2589 if !self.focus_handle(cx).contains_focused(window, cx)
2590 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2591 {
2592 for addon in self.addons.values() {
2593 addon.extend_key_context(&mut key_context, cx)
2594 }
2595 }
2596
2597 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2598 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2599 Some(
2600 file.full_path(cx)
2601 .extension()?
2602 .to_string_lossy()
2603 .into_owned(),
2604 )
2605 }) {
2606 key_context.set("extension", extension);
2607 }
2608 } else {
2609 key_context.add("multibuffer");
2610 }
2611
2612 if has_active_edit_prediction {
2613 if self.edit_prediction_in_conflict() {
2614 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2615 } else {
2616 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2617 key_context.add("copilot_suggestion");
2618 }
2619 }
2620
2621 if self.selection_mark_mode {
2622 key_context.add("selection_mode");
2623 }
2624
2625 let disjoint = self.selections.disjoint_anchors();
2626 let snapshot = self.snapshot(window, cx);
2627 let snapshot = snapshot.buffer_snapshot();
2628 if self.mode == EditorMode::SingleLine
2629 && let [selection] = disjoint
2630 && selection.start == selection.end
2631 && selection.end.to_offset(snapshot) == snapshot.len()
2632 {
2633 key_context.add("end_of_input");
2634 }
2635
2636 key_context
2637 }
2638
2639 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2640 self.last_bounds.as_ref()
2641 }
2642
2643 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2644 if self.mouse_cursor_hidden {
2645 self.mouse_cursor_hidden = false;
2646 cx.notify();
2647 }
2648 }
2649
2650 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2651 let hide_mouse_cursor = match origin {
2652 HideMouseCursorOrigin::TypingAction => {
2653 matches!(
2654 self.hide_mouse_mode,
2655 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2656 )
2657 }
2658 HideMouseCursorOrigin::MovementAction => {
2659 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2660 }
2661 };
2662 if self.mouse_cursor_hidden != hide_mouse_cursor {
2663 self.mouse_cursor_hidden = hide_mouse_cursor;
2664 cx.notify();
2665 }
2666 }
2667
2668 pub fn edit_prediction_in_conflict(&self) -> bool {
2669 if !self.show_edit_predictions_in_menu() {
2670 return false;
2671 }
2672
2673 let showing_completions = self
2674 .context_menu
2675 .borrow()
2676 .as_ref()
2677 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2678
2679 showing_completions
2680 || self.edit_prediction_requires_modifier()
2681 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2682 // bindings to insert tab characters.
2683 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2684 }
2685
2686 pub fn accept_edit_prediction_keybind(
2687 &self,
2688 accept_partial: bool,
2689 window: &mut Window,
2690 cx: &mut App,
2691 ) -> AcceptEditPredictionBinding {
2692 let key_context = self.key_context_internal(true, window, cx);
2693 let in_conflict = self.edit_prediction_in_conflict();
2694
2695 let bindings = if accept_partial {
2696 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2697 } else {
2698 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2699 };
2700
2701 // TODO: if the binding contains multiple keystrokes, display all of them, not
2702 // just the first one.
2703 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2704 !in_conflict
2705 || binding
2706 .keystrokes()
2707 .first()
2708 .is_some_and(|keystroke| keystroke.modifiers().modified())
2709 }))
2710 }
2711
2712 pub fn new_file(
2713 workspace: &mut Workspace,
2714 _: &workspace::NewFile,
2715 window: &mut Window,
2716 cx: &mut Context<Workspace>,
2717 ) {
2718 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2719 "Failed to create buffer",
2720 window,
2721 cx,
2722 |e, _, _| match e.error_code() {
2723 ErrorCode::RemoteUpgradeRequired => Some(format!(
2724 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2725 e.error_tag("required").unwrap_or("the latest version")
2726 )),
2727 _ => None,
2728 },
2729 );
2730 }
2731
2732 pub fn new_in_workspace(
2733 workspace: &mut Workspace,
2734 window: &mut Window,
2735 cx: &mut Context<Workspace>,
2736 ) -> Task<Result<Entity<Editor>>> {
2737 let project = workspace.project().clone();
2738 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2739
2740 cx.spawn_in(window, async move |workspace, cx| {
2741 let buffer = create.await?;
2742 workspace.update_in(cx, |workspace, window, cx| {
2743 let editor =
2744 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2745 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2746 editor
2747 })
2748 })
2749 }
2750
2751 fn new_file_vertical(
2752 workspace: &mut Workspace,
2753 _: &workspace::NewFileSplitVertical,
2754 window: &mut Window,
2755 cx: &mut Context<Workspace>,
2756 ) {
2757 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2758 }
2759
2760 fn new_file_horizontal(
2761 workspace: &mut Workspace,
2762 _: &workspace::NewFileSplitHorizontal,
2763 window: &mut Window,
2764 cx: &mut Context<Workspace>,
2765 ) {
2766 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2767 }
2768
2769 fn new_file_split(
2770 workspace: &mut Workspace,
2771 action: &workspace::NewFileSplit,
2772 window: &mut Window,
2773 cx: &mut Context<Workspace>,
2774 ) {
2775 Self::new_file_in_direction(workspace, action.0, window, cx)
2776 }
2777
2778 fn new_file_in_direction(
2779 workspace: &mut Workspace,
2780 direction: SplitDirection,
2781 window: &mut Window,
2782 cx: &mut Context<Workspace>,
2783 ) {
2784 let project = workspace.project().clone();
2785 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2786
2787 cx.spawn_in(window, async move |workspace, cx| {
2788 let buffer = create.await?;
2789 workspace.update_in(cx, move |workspace, window, cx| {
2790 workspace.split_item(
2791 direction,
2792 Box::new(
2793 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2794 ),
2795 window,
2796 cx,
2797 )
2798 })?;
2799 anyhow::Ok(())
2800 })
2801 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2802 match e.error_code() {
2803 ErrorCode::RemoteUpgradeRequired => Some(format!(
2804 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2805 e.error_tag("required").unwrap_or("the latest version")
2806 )),
2807 _ => None,
2808 }
2809 });
2810 }
2811
2812 pub fn leader_id(&self) -> Option<CollaboratorId> {
2813 self.leader_id
2814 }
2815
2816 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2817 &self.buffer
2818 }
2819
2820 pub fn project(&self) -> Option<&Entity<Project>> {
2821 self.project.as_ref()
2822 }
2823
2824 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2825 self.workspace.as_ref()?.0.upgrade()
2826 }
2827
2828 /// Returns the workspace serialization ID if this editor should be serialized.
2829 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2830 self.workspace
2831 .as_ref()
2832 .filter(|_| self.should_serialize_buffer())
2833 .and_then(|workspace| workspace.1)
2834 }
2835
2836 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2837 self.buffer().read(cx).title(cx)
2838 }
2839
2840 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2841 let git_blame_gutter_max_author_length = self
2842 .render_git_blame_gutter(cx)
2843 .then(|| {
2844 if let Some(blame) = self.blame.as_ref() {
2845 let max_author_length =
2846 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2847 Some(max_author_length)
2848 } else {
2849 None
2850 }
2851 })
2852 .flatten();
2853
2854 EditorSnapshot {
2855 mode: self.mode.clone(),
2856 show_gutter: self.show_gutter,
2857 show_line_numbers: self.show_line_numbers,
2858 show_git_diff_gutter: self.show_git_diff_gutter,
2859 show_code_actions: self.show_code_actions,
2860 show_runnables: self.show_runnables,
2861 show_breakpoints: self.show_breakpoints,
2862 git_blame_gutter_max_author_length,
2863 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2864 placeholder_display_snapshot: self
2865 .placeholder_display_map
2866 .as_ref()
2867 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2868 scroll_anchor: self.scroll_manager.anchor(),
2869 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2870 is_focused: self.focus_handle.is_focused(window),
2871 current_line_highlight: self
2872 .current_line_highlight
2873 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2874 gutter_hovered: self.gutter_hovered,
2875 }
2876 }
2877
2878 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2879 self.buffer.read(cx).language_at(point, cx)
2880 }
2881
2882 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2883 self.buffer.read(cx).read(cx).file_at(point).cloned()
2884 }
2885
2886 pub fn active_excerpt(
2887 &self,
2888 cx: &App,
2889 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2890 self.buffer
2891 .read(cx)
2892 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2893 }
2894
2895 pub fn mode(&self) -> &EditorMode {
2896 &self.mode
2897 }
2898
2899 pub fn set_mode(&mut self, mode: EditorMode) {
2900 self.mode = mode;
2901 }
2902
2903 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2904 self.collaboration_hub.as_deref()
2905 }
2906
2907 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2908 self.collaboration_hub = Some(hub);
2909 }
2910
2911 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2912 self.in_project_search = in_project_search;
2913 }
2914
2915 pub fn set_custom_context_menu(
2916 &mut self,
2917 f: impl 'static
2918 + Fn(
2919 &mut Self,
2920 DisplayPoint,
2921 &mut Window,
2922 &mut Context<Self>,
2923 ) -> Option<Entity<ui::ContextMenu>>,
2924 ) {
2925 self.custom_context_menu = Some(Box::new(f))
2926 }
2927
2928 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2929 self.completion_provider = provider;
2930 }
2931
2932 #[cfg(any(test, feature = "test-support"))]
2933 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2934 self.completion_provider.clone()
2935 }
2936
2937 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2938 self.semantics_provider.clone()
2939 }
2940
2941 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2942 self.semantics_provider = provider;
2943 }
2944
2945 pub fn set_edit_prediction_provider<T>(
2946 &mut self,
2947 provider: Option<Entity<T>>,
2948 window: &mut Window,
2949 cx: &mut Context<Self>,
2950 ) where
2951 T: EditPredictionProvider,
2952 {
2953 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2954 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2955 if this.focus_handle.is_focused(window) {
2956 this.update_visible_edit_prediction(window, cx);
2957 }
2958 }),
2959 provider: Arc::new(provider),
2960 });
2961 self.update_edit_prediction_settings(cx);
2962 self.refresh_edit_prediction(false, false, window, cx);
2963 }
2964
2965 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2966 self.placeholder_display_map
2967 .as_ref()
2968 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2969 }
2970
2971 pub fn set_placeholder_text(
2972 &mut self,
2973 placeholder_text: &str,
2974 window: &mut Window,
2975 cx: &mut Context<Self>,
2976 ) {
2977 let multibuffer = cx
2978 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2979
2980 let style = window.text_style();
2981
2982 self.placeholder_display_map = Some(cx.new(|cx| {
2983 DisplayMap::new(
2984 multibuffer,
2985 style.font(),
2986 style.font_size.to_pixels(window.rem_size()),
2987 None,
2988 FILE_HEADER_HEIGHT,
2989 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2990 Default::default(),
2991 DiagnosticSeverity::Off,
2992 cx,
2993 )
2994 }));
2995 cx.notify();
2996 }
2997
2998 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2999 self.cursor_shape = cursor_shape;
3000
3001 // Disrupt blink for immediate user feedback that the cursor shape has changed
3002 self.blink_manager.update(cx, BlinkManager::show_cursor);
3003
3004 cx.notify();
3005 }
3006
3007 pub fn set_current_line_highlight(
3008 &mut self,
3009 current_line_highlight: Option<CurrentLineHighlight>,
3010 ) {
3011 self.current_line_highlight = current_line_highlight;
3012 }
3013
3014 pub fn range_for_match<T: std::marker::Copy>(
3015 &self,
3016 range: &Range<T>,
3017 collapse: bool,
3018 ) -> Range<T> {
3019 if collapse {
3020 return range.start..range.start;
3021 }
3022 range.clone()
3023 }
3024
3025 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3026 if self.display_map.read(cx).clip_at_line_ends != clip {
3027 self.display_map
3028 .update(cx, |map, _| map.clip_at_line_ends = clip);
3029 }
3030 }
3031
3032 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3033 self.input_enabled = input_enabled;
3034 }
3035
3036 pub fn set_edit_predictions_hidden_for_vim_mode(
3037 &mut self,
3038 hidden: bool,
3039 window: &mut Window,
3040 cx: &mut Context<Self>,
3041 ) {
3042 if hidden != self.edit_predictions_hidden_for_vim_mode {
3043 self.edit_predictions_hidden_for_vim_mode = hidden;
3044 if hidden {
3045 self.update_visible_edit_prediction(window, cx);
3046 } else {
3047 self.refresh_edit_prediction(true, false, window, cx);
3048 }
3049 }
3050 }
3051
3052 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3053 self.menu_edit_predictions_policy = value;
3054 }
3055
3056 pub fn set_autoindent(&mut self, autoindent: bool) {
3057 if autoindent {
3058 self.autoindent_mode = Some(AutoindentMode::EachLine);
3059 } else {
3060 self.autoindent_mode = None;
3061 }
3062 }
3063
3064 pub fn read_only(&self, cx: &App) -> bool {
3065 self.read_only || self.buffer.read(cx).read_only()
3066 }
3067
3068 pub fn set_read_only(&mut self, read_only: bool) {
3069 self.read_only = read_only;
3070 }
3071
3072 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3073 self.use_autoclose = autoclose;
3074 }
3075
3076 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3077 self.use_auto_surround = auto_surround;
3078 }
3079
3080 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3081 self.auto_replace_emoji_shortcode = auto_replace;
3082 }
3083
3084 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3085 self.buffer_serialization = should_serialize.then(|| {
3086 BufferSerialization::new(
3087 ProjectSettings::get_global(cx)
3088 .session
3089 .restore_unsaved_buffers,
3090 )
3091 })
3092 }
3093
3094 fn should_serialize_buffer(&self) -> bool {
3095 self.buffer_serialization.is_some()
3096 }
3097
3098 pub fn toggle_edit_predictions(
3099 &mut self,
3100 _: &ToggleEditPrediction,
3101 window: &mut Window,
3102 cx: &mut Context<Self>,
3103 ) {
3104 if self.show_edit_predictions_override.is_some() {
3105 self.set_show_edit_predictions(None, window, cx);
3106 } else {
3107 let show_edit_predictions = !self.edit_predictions_enabled();
3108 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3109 }
3110 }
3111
3112 pub fn set_show_edit_predictions(
3113 &mut self,
3114 show_edit_predictions: Option<bool>,
3115 window: &mut Window,
3116 cx: &mut Context<Self>,
3117 ) {
3118 self.show_edit_predictions_override = show_edit_predictions;
3119 self.update_edit_prediction_settings(cx);
3120
3121 if let Some(false) = show_edit_predictions {
3122 self.discard_edit_prediction(false, cx);
3123 } else {
3124 self.refresh_edit_prediction(false, true, window, cx);
3125 }
3126 }
3127
3128 fn edit_predictions_disabled_in_scope(
3129 &self,
3130 buffer: &Entity<Buffer>,
3131 buffer_position: language::Anchor,
3132 cx: &App,
3133 ) -> bool {
3134 let snapshot = buffer.read(cx).snapshot();
3135 let settings = snapshot.settings_at(buffer_position, cx);
3136
3137 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3138 return false;
3139 };
3140
3141 scope.override_name().is_some_and(|scope_name| {
3142 settings
3143 .edit_predictions_disabled_in
3144 .iter()
3145 .any(|s| s == scope_name)
3146 })
3147 }
3148
3149 pub fn set_use_modal_editing(&mut self, to: bool) {
3150 self.use_modal_editing = to;
3151 }
3152
3153 pub fn use_modal_editing(&self) -> bool {
3154 self.use_modal_editing
3155 }
3156
3157 fn selections_did_change(
3158 &mut self,
3159 local: bool,
3160 old_cursor_position: &Anchor,
3161 effects: SelectionEffects,
3162 window: &mut Window,
3163 cx: &mut Context<Self>,
3164 ) {
3165 window.invalidate_character_coordinates();
3166
3167 // Copy selections to primary selection buffer
3168 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3169 if local {
3170 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3171 let buffer_handle = self.buffer.read(cx).read(cx);
3172
3173 let mut text = String::new();
3174 for (index, selection) in selections.iter().enumerate() {
3175 let text_for_selection = buffer_handle
3176 .text_for_range(selection.start..selection.end)
3177 .collect::<String>();
3178
3179 text.push_str(&text_for_selection);
3180 if index != selections.len() - 1 {
3181 text.push('\n');
3182 }
3183 }
3184
3185 if !text.is_empty() {
3186 cx.write_to_primary(ClipboardItem::new_string(text));
3187 }
3188 }
3189
3190 let selection_anchors = self.selections.disjoint_anchors_arc();
3191
3192 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3193 self.buffer.update(cx, |buffer, cx| {
3194 buffer.set_active_selections(
3195 &selection_anchors,
3196 self.selections.line_mode(),
3197 self.cursor_shape,
3198 cx,
3199 )
3200 });
3201 }
3202 let display_map = self
3203 .display_map
3204 .update(cx, |display_map, cx| display_map.snapshot(cx));
3205 let buffer = display_map.buffer_snapshot();
3206 if self.selections.count() == 1 {
3207 self.add_selections_state = None;
3208 }
3209 self.select_next_state = None;
3210 self.select_prev_state = None;
3211 self.select_syntax_node_history.try_clear();
3212 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3213 self.snippet_stack.invalidate(&selection_anchors, buffer);
3214 self.take_rename(false, window, cx);
3215
3216 let newest_selection = self.selections.newest_anchor();
3217 let new_cursor_position = newest_selection.head();
3218 let selection_start = newest_selection.start;
3219
3220 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3221 self.push_to_nav_history(
3222 *old_cursor_position,
3223 Some(new_cursor_position.to_point(buffer)),
3224 false,
3225 effects.nav_history == Some(true),
3226 cx,
3227 );
3228 }
3229
3230 if local {
3231 if let Some(buffer_id) = new_cursor_position.buffer_id {
3232 self.register_buffer(buffer_id, cx);
3233 }
3234
3235 let mut context_menu = self.context_menu.borrow_mut();
3236 let completion_menu = match context_menu.as_ref() {
3237 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3238 Some(CodeContextMenu::CodeActions(_)) => {
3239 *context_menu = None;
3240 None
3241 }
3242 None => None,
3243 };
3244 let completion_position = completion_menu.map(|menu| menu.initial_position);
3245 drop(context_menu);
3246
3247 if effects.completions
3248 && let Some(completion_position) = completion_position
3249 {
3250 let start_offset = selection_start.to_offset(buffer);
3251 let position_matches = start_offset == completion_position.to_offset(buffer);
3252 let continue_showing = if position_matches {
3253 if self.snippet_stack.is_empty() {
3254 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3255 == Some(CharKind::Word)
3256 } else {
3257 // Snippet choices can be shown even when the cursor is in whitespace.
3258 // Dismissing the menu with actions like backspace is handled by
3259 // invalidation regions.
3260 true
3261 }
3262 } else {
3263 false
3264 };
3265
3266 if continue_showing {
3267 self.open_or_update_completions_menu(None, None, false, window, cx);
3268 } else {
3269 self.hide_context_menu(window, cx);
3270 }
3271 }
3272
3273 hide_hover(self, cx);
3274
3275 if old_cursor_position.to_display_point(&display_map).row()
3276 != new_cursor_position.to_display_point(&display_map).row()
3277 {
3278 self.available_code_actions.take();
3279 }
3280 self.refresh_code_actions(window, cx);
3281 self.refresh_document_highlights(cx);
3282 refresh_linked_ranges(self, window, cx);
3283
3284 self.refresh_selected_text_highlights(false, window, cx);
3285 self.refresh_matching_bracket_highlights(window, cx);
3286 self.update_visible_edit_prediction(window, cx);
3287 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3288 self.inline_blame_popover.take();
3289 if self.git_blame_inline_enabled {
3290 self.start_inline_blame_timer(window, cx);
3291 }
3292 }
3293
3294 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3295 cx.emit(EditorEvent::SelectionsChanged { local });
3296
3297 let selections = &self.selections.disjoint_anchors_arc();
3298 if selections.len() == 1 {
3299 cx.emit(SearchEvent::ActiveMatchChanged)
3300 }
3301 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3302 let inmemory_selections = selections
3303 .iter()
3304 .map(|s| {
3305 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3306 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3307 })
3308 .collect();
3309 self.update_restoration_data(cx, |data| {
3310 data.selections = inmemory_selections;
3311 });
3312
3313 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3314 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3315 {
3316 let snapshot = self.buffer().read(cx).snapshot(cx);
3317 let selections = selections.clone();
3318 let background_executor = cx.background_executor().clone();
3319 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3320 self.serialize_selections = cx.background_spawn(async move {
3321 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3322 let db_selections = selections
3323 .iter()
3324 .map(|selection| {
3325 (
3326 selection.start.to_offset(&snapshot),
3327 selection.end.to_offset(&snapshot),
3328 )
3329 })
3330 .collect();
3331
3332 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3333 .await
3334 .with_context(|| {
3335 format!(
3336 "persisting editor selections for editor {editor_id}, \
3337 workspace {workspace_id:?}"
3338 )
3339 })
3340 .log_err();
3341 });
3342 }
3343 }
3344
3345 cx.notify();
3346 }
3347
3348 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3349 use text::ToOffset as _;
3350 use text::ToPoint as _;
3351
3352 if self.mode.is_minimap()
3353 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3354 {
3355 return;
3356 }
3357
3358 if !self.buffer().read(cx).is_singleton() {
3359 return;
3360 }
3361
3362 let display_snapshot = self
3363 .display_map
3364 .update(cx, |display_map, cx| display_map.snapshot(cx));
3365 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3366 return;
3367 };
3368 let inmemory_folds = display_snapshot
3369 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3370 .map(|fold| {
3371 fold.range.start.text_anchor.to_point(&snapshot)
3372 ..fold.range.end.text_anchor.to_point(&snapshot)
3373 })
3374 .collect();
3375 self.update_restoration_data(cx, |data| {
3376 data.folds = inmemory_folds;
3377 });
3378
3379 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3380 return;
3381 };
3382 let background_executor = cx.background_executor().clone();
3383 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3384 let db_folds = display_snapshot
3385 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3386 .map(|fold| {
3387 (
3388 fold.range.start.text_anchor.to_offset(&snapshot),
3389 fold.range.end.text_anchor.to_offset(&snapshot),
3390 )
3391 })
3392 .collect();
3393 self.serialize_folds = cx.background_spawn(async move {
3394 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3395 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3396 .await
3397 .with_context(|| {
3398 format!(
3399 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3400 )
3401 })
3402 .log_err();
3403 });
3404 }
3405
3406 pub fn sync_selections(
3407 &mut self,
3408 other: Entity<Editor>,
3409 cx: &mut Context<Self>,
3410 ) -> gpui::Subscription {
3411 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3412 if !other_selections.is_empty() {
3413 self.selections
3414 .change_with(&self.display_snapshot(cx), |selections| {
3415 selections.select_anchors(other_selections);
3416 });
3417 }
3418
3419 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3420 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3421 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3422 if other_selections.is_empty() {
3423 return;
3424 }
3425 let snapshot = this.display_snapshot(cx);
3426 this.selections.change_with(&snapshot, |selections| {
3427 selections.select_anchors(other_selections);
3428 });
3429 }
3430 });
3431
3432 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3433 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3434 let these_selections = this.selections.disjoint_anchors().to_vec();
3435 if these_selections.is_empty() {
3436 return;
3437 }
3438 other.update(cx, |other_editor, cx| {
3439 let snapshot = other_editor.display_snapshot(cx);
3440 other_editor
3441 .selections
3442 .change_with(&snapshot, |selections| {
3443 selections.select_anchors(these_selections);
3444 })
3445 });
3446 }
3447 });
3448
3449 Subscription::join(other_subscription, this_subscription)
3450 }
3451
3452 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3453 if self.buffer().read(cx).is_singleton() {
3454 return;
3455 }
3456 let snapshot = self.buffer.read(cx).snapshot(cx);
3457 let buffer_ids: HashSet<BufferId> = self
3458 .selections
3459 .disjoint_anchor_ranges()
3460 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3461 .collect();
3462 for buffer_id in buffer_ids {
3463 self.unfold_buffer(buffer_id, cx);
3464 }
3465 }
3466
3467 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3468 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3469 /// effects of selection change occur at the end of the transaction.
3470 pub fn change_selections<R>(
3471 &mut self,
3472 effects: SelectionEffects,
3473 window: &mut Window,
3474 cx: &mut Context<Self>,
3475 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3476 ) -> R {
3477 let snapshot = self.display_snapshot(cx);
3478 if let Some(state) = &mut self.deferred_selection_effects_state {
3479 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3480 state.effects.completions = effects.completions;
3481 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3482 let (changed, result) = self.selections.change_with(&snapshot, change);
3483 state.changed |= changed;
3484 return result;
3485 }
3486 let mut state = DeferredSelectionEffectsState {
3487 changed: false,
3488 effects,
3489 old_cursor_position: self.selections.newest_anchor().head(),
3490 history_entry: SelectionHistoryEntry {
3491 selections: self.selections.disjoint_anchors_arc(),
3492 select_next_state: self.select_next_state.clone(),
3493 select_prev_state: self.select_prev_state.clone(),
3494 add_selections_state: self.add_selections_state.clone(),
3495 },
3496 };
3497 let (changed, result) = self.selections.change_with(&snapshot, change);
3498 state.changed = state.changed || changed;
3499 if self.defer_selection_effects {
3500 self.deferred_selection_effects_state = Some(state);
3501 } else {
3502 self.apply_selection_effects(state, window, cx);
3503 }
3504 result
3505 }
3506
3507 /// Defers the effects of selection change, so that the effects of multiple calls to
3508 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3509 /// to selection history and the state of popovers based on selection position aren't
3510 /// erroneously updated.
3511 pub fn with_selection_effects_deferred<R>(
3512 &mut self,
3513 window: &mut Window,
3514 cx: &mut Context<Self>,
3515 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3516 ) -> R {
3517 let already_deferred = self.defer_selection_effects;
3518 self.defer_selection_effects = true;
3519 let result = update(self, window, cx);
3520 if !already_deferred {
3521 self.defer_selection_effects = false;
3522 if let Some(state) = self.deferred_selection_effects_state.take() {
3523 self.apply_selection_effects(state, window, cx);
3524 }
3525 }
3526 result
3527 }
3528
3529 fn apply_selection_effects(
3530 &mut self,
3531 state: DeferredSelectionEffectsState,
3532 window: &mut Window,
3533 cx: &mut Context<Self>,
3534 ) {
3535 if state.changed {
3536 self.selection_history.push(state.history_entry);
3537
3538 if let Some(autoscroll) = state.effects.scroll {
3539 self.request_autoscroll(autoscroll, cx);
3540 }
3541
3542 let old_cursor_position = &state.old_cursor_position;
3543
3544 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3545
3546 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3547 self.show_signature_help(&ShowSignatureHelp, window, cx);
3548 }
3549 }
3550 }
3551
3552 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3553 where
3554 I: IntoIterator<Item = (Range<S>, T)>,
3555 S: ToOffset,
3556 T: Into<Arc<str>>,
3557 {
3558 if self.read_only(cx) {
3559 return;
3560 }
3561
3562 self.buffer
3563 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3564 }
3565
3566 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3567 where
3568 I: IntoIterator<Item = (Range<S>, T)>,
3569 S: ToOffset,
3570 T: Into<Arc<str>>,
3571 {
3572 if self.read_only(cx) {
3573 return;
3574 }
3575
3576 self.buffer.update(cx, |buffer, cx| {
3577 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3578 });
3579 }
3580
3581 pub fn edit_with_block_indent<I, S, T>(
3582 &mut self,
3583 edits: I,
3584 original_indent_columns: Vec<Option<u32>>,
3585 cx: &mut Context<Self>,
3586 ) where
3587 I: IntoIterator<Item = (Range<S>, T)>,
3588 S: ToOffset,
3589 T: Into<Arc<str>>,
3590 {
3591 if self.read_only(cx) {
3592 return;
3593 }
3594
3595 self.buffer.update(cx, |buffer, cx| {
3596 buffer.edit(
3597 edits,
3598 Some(AutoindentMode::Block {
3599 original_indent_columns,
3600 }),
3601 cx,
3602 )
3603 });
3604 }
3605
3606 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3607 self.hide_context_menu(window, cx);
3608
3609 match phase {
3610 SelectPhase::Begin {
3611 position,
3612 add,
3613 click_count,
3614 } => self.begin_selection(position, add, click_count, window, cx),
3615 SelectPhase::BeginColumnar {
3616 position,
3617 goal_column,
3618 reset,
3619 mode,
3620 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3621 SelectPhase::Extend {
3622 position,
3623 click_count,
3624 } => self.extend_selection(position, click_count, window, cx),
3625 SelectPhase::Update {
3626 position,
3627 goal_column,
3628 scroll_delta,
3629 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3630 SelectPhase::End => self.end_selection(window, cx),
3631 }
3632 }
3633
3634 fn extend_selection(
3635 &mut self,
3636 position: DisplayPoint,
3637 click_count: usize,
3638 window: &mut Window,
3639 cx: &mut Context<Self>,
3640 ) {
3641 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3642 let tail = self.selections.newest::<usize>(&display_map).tail();
3643 let click_count = click_count.max(match self.selections.select_mode() {
3644 SelectMode::Character => 1,
3645 SelectMode::Word(_) => 2,
3646 SelectMode::Line(_) => 3,
3647 SelectMode::All => 4,
3648 });
3649 self.begin_selection(position, false, click_count, window, cx);
3650
3651 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3652
3653 let current_selection = match self.selections.select_mode() {
3654 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3655 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3656 };
3657
3658 let mut pending_selection = self
3659 .selections
3660 .pending_anchor()
3661 .cloned()
3662 .expect("extend_selection not called with pending selection");
3663
3664 if pending_selection
3665 .start
3666 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3667 == Ordering::Greater
3668 {
3669 pending_selection.start = current_selection.start;
3670 }
3671 if pending_selection
3672 .end
3673 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3674 == Ordering::Less
3675 {
3676 pending_selection.end = current_selection.end;
3677 pending_selection.reversed = true;
3678 }
3679
3680 let mut pending_mode = self.selections.pending_mode().unwrap();
3681 match &mut pending_mode {
3682 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3683 _ => {}
3684 }
3685
3686 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3687 SelectionEffects::scroll(Autoscroll::fit())
3688 } else {
3689 SelectionEffects::no_scroll()
3690 };
3691
3692 self.change_selections(effects, window, cx, |s| {
3693 s.set_pending(pending_selection.clone(), pending_mode);
3694 s.set_is_extending(true);
3695 });
3696 }
3697
3698 fn begin_selection(
3699 &mut self,
3700 position: DisplayPoint,
3701 add: bool,
3702 click_count: usize,
3703 window: &mut Window,
3704 cx: &mut Context<Self>,
3705 ) {
3706 if !self.focus_handle.is_focused(window) {
3707 self.last_focused_descendant = None;
3708 window.focus(&self.focus_handle);
3709 }
3710
3711 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3712 let buffer = display_map.buffer_snapshot();
3713 let position = display_map.clip_point(position, Bias::Left);
3714
3715 let start;
3716 let end;
3717 let mode;
3718 let mut auto_scroll;
3719 match click_count {
3720 1 => {
3721 start = buffer.anchor_before(position.to_point(&display_map));
3722 end = start;
3723 mode = SelectMode::Character;
3724 auto_scroll = true;
3725 }
3726 2 => {
3727 let position = display_map
3728 .clip_point(position, Bias::Left)
3729 .to_offset(&display_map, Bias::Left);
3730 let (range, _) = buffer.surrounding_word(position, None);
3731 start = buffer.anchor_before(range.start);
3732 end = buffer.anchor_before(range.end);
3733 mode = SelectMode::Word(start..end);
3734 auto_scroll = true;
3735 }
3736 3 => {
3737 let position = display_map
3738 .clip_point(position, Bias::Left)
3739 .to_point(&display_map);
3740 let line_start = display_map.prev_line_boundary(position).0;
3741 let next_line_start = buffer.clip_point(
3742 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3743 Bias::Left,
3744 );
3745 start = buffer.anchor_before(line_start);
3746 end = buffer.anchor_before(next_line_start);
3747 mode = SelectMode::Line(start..end);
3748 auto_scroll = true;
3749 }
3750 _ => {
3751 start = buffer.anchor_before(0);
3752 end = buffer.anchor_before(buffer.len());
3753 mode = SelectMode::All;
3754 auto_scroll = false;
3755 }
3756 }
3757 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3758
3759 let point_to_delete: Option<usize> = {
3760 let selected_points: Vec<Selection<Point>> =
3761 self.selections.disjoint_in_range(start..end, &display_map);
3762
3763 if !add || click_count > 1 {
3764 None
3765 } else if !selected_points.is_empty() {
3766 Some(selected_points[0].id)
3767 } else {
3768 let clicked_point_already_selected =
3769 self.selections.disjoint_anchors().iter().find(|selection| {
3770 selection.start.to_point(buffer) == start.to_point(buffer)
3771 || selection.end.to_point(buffer) == end.to_point(buffer)
3772 });
3773
3774 clicked_point_already_selected.map(|selection| selection.id)
3775 }
3776 };
3777
3778 let selections_count = self.selections.count();
3779 let effects = if auto_scroll {
3780 SelectionEffects::default()
3781 } else {
3782 SelectionEffects::no_scroll()
3783 };
3784
3785 self.change_selections(effects, window, cx, |s| {
3786 if let Some(point_to_delete) = point_to_delete {
3787 s.delete(point_to_delete);
3788
3789 if selections_count == 1 {
3790 s.set_pending_anchor_range(start..end, mode);
3791 }
3792 } else {
3793 if !add {
3794 s.clear_disjoint();
3795 }
3796
3797 s.set_pending_anchor_range(start..end, mode);
3798 }
3799 });
3800 }
3801
3802 fn begin_columnar_selection(
3803 &mut self,
3804 position: DisplayPoint,
3805 goal_column: u32,
3806 reset: bool,
3807 mode: ColumnarMode,
3808 window: &mut Window,
3809 cx: &mut Context<Self>,
3810 ) {
3811 if !self.focus_handle.is_focused(window) {
3812 self.last_focused_descendant = None;
3813 window.focus(&self.focus_handle);
3814 }
3815
3816 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3817
3818 if reset {
3819 let pointer_position = display_map
3820 .buffer_snapshot()
3821 .anchor_before(position.to_point(&display_map));
3822
3823 self.change_selections(
3824 SelectionEffects::scroll(Autoscroll::newest()),
3825 window,
3826 cx,
3827 |s| {
3828 s.clear_disjoint();
3829 s.set_pending_anchor_range(
3830 pointer_position..pointer_position,
3831 SelectMode::Character,
3832 );
3833 },
3834 );
3835 };
3836
3837 let tail = self.selections.newest::<Point>(&display_map).tail();
3838 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3839 self.columnar_selection_state = match mode {
3840 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3841 selection_tail: selection_anchor,
3842 display_point: if reset {
3843 if position.column() != goal_column {
3844 Some(DisplayPoint::new(position.row(), goal_column))
3845 } else {
3846 None
3847 }
3848 } else {
3849 None
3850 },
3851 }),
3852 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3853 selection_tail: selection_anchor,
3854 }),
3855 };
3856
3857 if !reset {
3858 self.select_columns(position, goal_column, &display_map, window, cx);
3859 }
3860 }
3861
3862 fn update_selection(
3863 &mut self,
3864 position: DisplayPoint,
3865 goal_column: u32,
3866 scroll_delta: gpui::Point<f32>,
3867 window: &mut Window,
3868 cx: &mut Context<Self>,
3869 ) {
3870 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3871
3872 if self.columnar_selection_state.is_some() {
3873 self.select_columns(position, goal_column, &display_map, window, cx);
3874 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3875 let buffer = display_map.buffer_snapshot();
3876 let head;
3877 let tail;
3878 let mode = self.selections.pending_mode().unwrap();
3879 match &mode {
3880 SelectMode::Character => {
3881 head = position.to_point(&display_map);
3882 tail = pending.tail().to_point(buffer);
3883 }
3884 SelectMode::Word(original_range) => {
3885 let offset = display_map
3886 .clip_point(position, Bias::Left)
3887 .to_offset(&display_map, Bias::Left);
3888 let original_range = original_range.to_offset(buffer);
3889
3890 let head_offset = if buffer.is_inside_word(offset, None)
3891 || original_range.contains(&offset)
3892 {
3893 let (word_range, _) = buffer.surrounding_word(offset, None);
3894 if word_range.start < original_range.start {
3895 word_range.start
3896 } else {
3897 word_range.end
3898 }
3899 } else {
3900 offset
3901 };
3902
3903 head = head_offset.to_point(buffer);
3904 if head_offset <= original_range.start {
3905 tail = original_range.end.to_point(buffer);
3906 } else {
3907 tail = original_range.start.to_point(buffer);
3908 }
3909 }
3910 SelectMode::Line(original_range) => {
3911 let original_range = original_range.to_point(display_map.buffer_snapshot());
3912
3913 let position = display_map
3914 .clip_point(position, Bias::Left)
3915 .to_point(&display_map);
3916 let line_start = display_map.prev_line_boundary(position).0;
3917 let next_line_start = buffer.clip_point(
3918 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3919 Bias::Left,
3920 );
3921
3922 if line_start < original_range.start {
3923 head = line_start
3924 } else {
3925 head = next_line_start
3926 }
3927
3928 if head <= original_range.start {
3929 tail = original_range.end;
3930 } else {
3931 tail = original_range.start;
3932 }
3933 }
3934 SelectMode::All => {
3935 return;
3936 }
3937 };
3938
3939 if head < tail {
3940 pending.start = buffer.anchor_before(head);
3941 pending.end = buffer.anchor_before(tail);
3942 pending.reversed = true;
3943 } else {
3944 pending.start = buffer.anchor_before(tail);
3945 pending.end = buffer.anchor_before(head);
3946 pending.reversed = false;
3947 }
3948
3949 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3950 s.set_pending(pending.clone(), mode);
3951 });
3952 } else {
3953 log::error!("update_selection dispatched with no pending selection");
3954 return;
3955 }
3956
3957 self.apply_scroll_delta(scroll_delta, window, cx);
3958 cx.notify();
3959 }
3960
3961 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3962 self.columnar_selection_state.take();
3963 if let Some(pending_mode) = self.selections.pending_mode() {
3964 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3965 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3966 s.select(selections);
3967 s.clear_pending();
3968 if s.is_extending() {
3969 s.set_is_extending(false);
3970 } else {
3971 s.set_select_mode(pending_mode);
3972 }
3973 });
3974 }
3975 }
3976
3977 fn select_columns(
3978 &mut self,
3979 head: DisplayPoint,
3980 goal_column: u32,
3981 display_map: &DisplaySnapshot,
3982 window: &mut Window,
3983 cx: &mut Context<Self>,
3984 ) {
3985 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3986 return;
3987 };
3988
3989 let tail = match columnar_state {
3990 ColumnarSelectionState::FromMouse {
3991 selection_tail,
3992 display_point,
3993 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3994 ColumnarSelectionState::FromSelection { selection_tail } => {
3995 selection_tail.to_display_point(display_map)
3996 }
3997 };
3998
3999 let start_row = cmp::min(tail.row(), head.row());
4000 let end_row = cmp::max(tail.row(), head.row());
4001 let start_column = cmp::min(tail.column(), goal_column);
4002 let end_column = cmp::max(tail.column(), goal_column);
4003 let reversed = start_column < tail.column();
4004
4005 let selection_ranges = (start_row.0..=end_row.0)
4006 .map(DisplayRow)
4007 .filter_map(|row| {
4008 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4009 || start_column <= display_map.line_len(row))
4010 && !display_map.is_block_line(row)
4011 {
4012 let start = display_map
4013 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4014 .to_point(display_map);
4015 let end = display_map
4016 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4017 .to_point(display_map);
4018 if reversed {
4019 Some(end..start)
4020 } else {
4021 Some(start..end)
4022 }
4023 } else {
4024 None
4025 }
4026 })
4027 .collect::<Vec<_>>();
4028 if selection_ranges.is_empty() {
4029 return;
4030 }
4031
4032 let ranges = match columnar_state {
4033 ColumnarSelectionState::FromMouse { .. } => {
4034 let mut non_empty_ranges = selection_ranges
4035 .iter()
4036 .filter(|selection_range| selection_range.start != selection_range.end)
4037 .peekable();
4038 if non_empty_ranges.peek().is_some() {
4039 non_empty_ranges.cloned().collect()
4040 } else {
4041 selection_ranges
4042 }
4043 }
4044 _ => selection_ranges,
4045 };
4046
4047 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4048 s.select_ranges(ranges);
4049 });
4050 cx.notify();
4051 }
4052
4053 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4054 self.selections
4055 .all_adjusted(snapshot)
4056 .iter()
4057 .any(|selection| !selection.is_empty())
4058 }
4059
4060 pub fn has_pending_nonempty_selection(&self) -> bool {
4061 let pending_nonempty_selection = match self.selections.pending_anchor() {
4062 Some(Selection { start, end, .. }) => start != end,
4063 None => false,
4064 };
4065
4066 pending_nonempty_selection
4067 || (self.columnar_selection_state.is_some()
4068 && self.selections.disjoint_anchors().len() > 1)
4069 }
4070
4071 pub fn has_pending_selection(&self) -> bool {
4072 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4073 }
4074
4075 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4076 self.selection_mark_mode = false;
4077 self.selection_drag_state = SelectionDragState::None;
4078
4079 if self.dismiss_menus_and_popups(true, window, cx) {
4080 cx.notify();
4081 return;
4082 }
4083 if self.clear_expanded_diff_hunks(cx) {
4084 cx.notify();
4085 return;
4086 }
4087 if self.show_git_blame_gutter {
4088 self.show_git_blame_gutter = false;
4089 cx.notify();
4090 return;
4091 }
4092
4093 if self.mode.is_full()
4094 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4095 {
4096 cx.notify();
4097 return;
4098 }
4099
4100 cx.propagate();
4101 }
4102
4103 pub fn dismiss_menus_and_popups(
4104 &mut self,
4105 is_user_requested: bool,
4106 window: &mut Window,
4107 cx: &mut Context<Self>,
4108 ) -> bool {
4109 if self.take_rename(false, window, cx).is_some() {
4110 return true;
4111 }
4112
4113 if self.hide_blame_popover(true, cx) {
4114 return true;
4115 }
4116
4117 if hide_hover(self, cx) {
4118 return true;
4119 }
4120
4121 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
4122 return true;
4123 }
4124
4125 if self.hide_context_menu(window, cx).is_some() {
4126 return true;
4127 }
4128
4129 if self.mouse_context_menu.take().is_some() {
4130 return true;
4131 }
4132
4133 if is_user_requested && self.discard_edit_prediction(true, cx) {
4134 return true;
4135 }
4136
4137 if self.snippet_stack.pop().is_some() {
4138 return true;
4139 }
4140
4141 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4142 self.dismiss_diagnostics(cx);
4143 return true;
4144 }
4145
4146 false
4147 }
4148
4149 fn linked_editing_ranges_for(
4150 &self,
4151 selection: Range<text::Anchor>,
4152 cx: &App,
4153 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4154 if self.linked_edit_ranges.is_empty() {
4155 return None;
4156 }
4157 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4158 selection.end.buffer_id.and_then(|end_buffer_id| {
4159 if selection.start.buffer_id != Some(end_buffer_id) {
4160 return None;
4161 }
4162 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4163 let snapshot = buffer.read(cx).snapshot();
4164 self.linked_edit_ranges
4165 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4166 .map(|ranges| (ranges, snapshot, buffer))
4167 })?;
4168 use text::ToOffset as TO;
4169 // find offset from the start of current range to current cursor position
4170 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4171
4172 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4173 let start_difference = start_offset - start_byte_offset;
4174 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4175 let end_difference = end_offset - start_byte_offset;
4176 // Current range has associated linked ranges.
4177 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4178 for range in linked_ranges.iter() {
4179 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4180 let end_offset = start_offset + end_difference;
4181 let start_offset = start_offset + start_difference;
4182 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4183 continue;
4184 }
4185 if self.selections.disjoint_anchor_ranges().any(|s| {
4186 if s.start.buffer_id != selection.start.buffer_id
4187 || s.end.buffer_id != selection.end.buffer_id
4188 {
4189 return false;
4190 }
4191 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4192 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4193 }) {
4194 continue;
4195 }
4196 let start = buffer_snapshot.anchor_after(start_offset);
4197 let end = buffer_snapshot.anchor_after(end_offset);
4198 linked_edits
4199 .entry(buffer.clone())
4200 .or_default()
4201 .push(start..end);
4202 }
4203 Some(linked_edits)
4204 }
4205
4206 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4207 let text: Arc<str> = text.into();
4208
4209 if self.read_only(cx) {
4210 return;
4211 }
4212
4213 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4214
4215 self.unfold_buffers_with_selections(cx);
4216
4217 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4218 let mut bracket_inserted = false;
4219 let mut edits = Vec::new();
4220 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4221 let mut new_selections = Vec::with_capacity(selections.len());
4222 let mut new_autoclose_regions = Vec::new();
4223 let snapshot = self.buffer.read(cx).read(cx);
4224 let mut clear_linked_edit_ranges = false;
4225
4226 for (selection, autoclose_region) in
4227 self.selections_with_autoclose_regions(selections, &snapshot)
4228 {
4229 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4230 // Determine if the inserted text matches the opening or closing
4231 // bracket of any of this language's bracket pairs.
4232 let mut bracket_pair = None;
4233 let mut is_bracket_pair_start = false;
4234 let mut is_bracket_pair_end = false;
4235 if !text.is_empty() {
4236 let mut bracket_pair_matching_end = None;
4237 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4238 // and they are removing the character that triggered IME popup.
4239 for (pair, enabled) in scope.brackets() {
4240 if !pair.close && !pair.surround {
4241 continue;
4242 }
4243
4244 if enabled && pair.start.ends_with(text.as_ref()) {
4245 let prefix_len = pair.start.len() - text.len();
4246 let preceding_text_matches_prefix = prefix_len == 0
4247 || (selection.start.column >= (prefix_len as u32)
4248 && snapshot.contains_str_at(
4249 Point::new(
4250 selection.start.row,
4251 selection.start.column - (prefix_len as u32),
4252 ),
4253 &pair.start[..prefix_len],
4254 ));
4255 if preceding_text_matches_prefix {
4256 bracket_pair = Some(pair.clone());
4257 is_bracket_pair_start = true;
4258 break;
4259 }
4260 }
4261 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4262 {
4263 // take first bracket pair matching end, but don't break in case a later bracket
4264 // pair matches start
4265 bracket_pair_matching_end = Some(pair.clone());
4266 }
4267 }
4268 if let Some(end) = bracket_pair_matching_end
4269 && bracket_pair.is_none()
4270 {
4271 bracket_pair = Some(end);
4272 is_bracket_pair_end = true;
4273 }
4274 }
4275
4276 if let Some(bracket_pair) = bracket_pair {
4277 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4278 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4279 let auto_surround =
4280 self.use_auto_surround && snapshot_settings.use_auto_surround;
4281 if selection.is_empty() {
4282 if is_bracket_pair_start {
4283 // If the inserted text is a suffix of an opening bracket and the
4284 // selection is preceded by the rest of the opening bracket, then
4285 // insert the closing bracket.
4286 let following_text_allows_autoclose = snapshot
4287 .chars_at(selection.start)
4288 .next()
4289 .is_none_or(|c| scope.should_autoclose_before(c));
4290
4291 let preceding_text_allows_autoclose = selection.start.column == 0
4292 || snapshot
4293 .reversed_chars_at(selection.start)
4294 .next()
4295 .is_none_or(|c| {
4296 bracket_pair.start != bracket_pair.end
4297 || !snapshot
4298 .char_classifier_at(selection.start)
4299 .is_word(c)
4300 });
4301
4302 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4303 && bracket_pair.start.len() == 1
4304 {
4305 let target = bracket_pair.start.chars().next().unwrap();
4306 let current_line_count = snapshot
4307 .reversed_chars_at(selection.start)
4308 .take_while(|&c| c != '\n')
4309 .filter(|&c| c == target)
4310 .count();
4311 current_line_count % 2 == 1
4312 } else {
4313 false
4314 };
4315
4316 if autoclose
4317 && bracket_pair.close
4318 && following_text_allows_autoclose
4319 && preceding_text_allows_autoclose
4320 && !is_closing_quote
4321 {
4322 let anchor = snapshot.anchor_before(selection.end);
4323 new_selections.push((selection.map(|_| anchor), text.len()));
4324 new_autoclose_regions.push((
4325 anchor,
4326 text.len(),
4327 selection.id,
4328 bracket_pair.clone(),
4329 ));
4330 edits.push((
4331 selection.range(),
4332 format!("{}{}", text, bracket_pair.end).into(),
4333 ));
4334 bracket_inserted = true;
4335 continue;
4336 }
4337 }
4338
4339 if let Some(region) = autoclose_region {
4340 // If the selection is followed by an auto-inserted closing bracket,
4341 // then don't insert that closing bracket again; just move the selection
4342 // past the closing bracket.
4343 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4344 && text.as_ref() == region.pair.end.as_str()
4345 && snapshot.contains_str_at(region.range.end, text.as_ref());
4346 if should_skip {
4347 let anchor = snapshot.anchor_after(selection.end);
4348 new_selections
4349 .push((selection.map(|_| anchor), region.pair.end.len()));
4350 continue;
4351 }
4352 }
4353
4354 let always_treat_brackets_as_autoclosed = snapshot
4355 .language_settings_at(selection.start, cx)
4356 .always_treat_brackets_as_autoclosed;
4357 if always_treat_brackets_as_autoclosed
4358 && is_bracket_pair_end
4359 && snapshot.contains_str_at(selection.end, text.as_ref())
4360 {
4361 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4362 // and the inserted text is a closing bracket and the selection is followed
4363 // by the closing bracket then move the selection past the closing bracket.
4364 let anchor = snapshot.anchor_after(selection.end);
4365 new_selections.push((selection.map(|_| anchor), text.len()));
4366 continue;
4367 }
4368 }
4369 // If an opening bracket is 1 character long and is typed while
4370 // text is selected, then surround that text with the bracket pair.
4371 else if auto_surround
4372 && bracket_pair.surround
4373 && is_bracket_pair_start
4374 && bracket_pair.start.chars().count() == 1
4375 {
4376 edits.push((selection.start..selection.start, text.clone()));
4377 edits.push((
4378 selection.end..selection.end,
4379 bracket_pair.end.as_str().into(),
4380 ));
4381 bracket_inserted = true;
4382 new_selections.push((
4383 Selection {
4384 id: selection.id,
4385 start: snapshot.anchor_after(selection.start),
4386 end: snapshot.anchor_before(selection.end),
4387 reversed: selection.reversed,
4388 goal: selection.goal,
4389 },
4390 0,
4391 ));
4392 continue;
4393 }
4394 }
4395 }
4396
4397 if self.auto_replace_emoji_shortcode
4398 && selection.is_empty()
4399 && text.as_ref().ends_with(':')
4400 && let Some(possible_emoji_short_code) =
4401 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4402 && !possible_emoji_short_code.is_empty()
4403 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4404 {
4405 let emoji_shortcode_start = Point::new(
4406 selection.start.row,
4407 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4408 );
4409
4410 // Remove shortcode from buffer
4411 edits.push((
4412 emoji_shortcode_start..selection.start,
4413 "".to_string().into(),
4414 ));
4415 new_selections.push((
4416 Selection {
4417 id: selection.id,
4418 start: snapshot.anchor_after(emoji_shortcode_start),
4419 end: snapshot.anchor_before(selection.start),
4420 reversed: selection.reversed,
4421 goal: selection.goal,
4422 },
4423 0,
4424 ));
4425
4426 // Insert emoji
4427 let selection_start_anchor = snapshot.anchor_after(selection.start);
4428 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4429 edits.push((selection.start..selection.end, emoji.to_string().into()));
4430
4431 continue;
4432 }
4433
4434 // If not handling any auto-close operation, then just replace the selected
4435 // text with the given input and move the selection to the end of the
4436 // newly inserted text.
4437 let anchor = snapshot.anchor_after(selection.end);
4438 if !self.linked_edit_ranges.is_empty() {
4439 let start_anchor = snapshot.anchor_before(selection.start);
4440
4441 let is_word_char = text.chars().next().is_none_or(|char| {
4442 let classifier = snapshot
4443 .char_classifier_at(start_anchor.to_offset(&snapshot))
4444 .scope_context(Some(CharScopeContext::LinkedEdit));
4445 classifier.is_word(char)
4446 });
4447
4448 if is_word_char {
4449 if let Some(ranges) = self
4450 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4451 {
4452 for (buffer, edits) in ranges {
4453 linked_edits
4454 .entry(buffer.clone())
4455 .or_default()
4456 .extend(edits.into_iter().map(|range| (range, text.clone())));
4457 }
4458 }
4459 } else {
4460 clear_linked_edit_ranges = true;
4461 }
4462 }
4463
4464 new_selections.push((selection.map(|_| anchor), 0));
4465 edits.push((selection.start..selection.end, text.clone()));
4466 }
4467
4468 drop(snapshot);
4469
4470 self.transact(window, cx, |this, window, cx| {
4471 if clear_linked_edit_ranges {
4472 this.linked_edit_ranges.clear();
4473 }
4474 let initial_buffer_versions =
4475 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4476
4477 this.buffer.update(cx, |buffer, cx| {
4478 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4479 });
4480 for (buffer, edits) in linked_edits {
4481 buffer.update(cx, |buffer, cx| {
4482 let snapshot = buffer.snapshot();
4483 let edits = edits
4484 .into_iter()
4485 .map(|(range, text)| {
4486 use text::ToPoint as TP;
4487 let end_point = TP::to_point(&range.end, &snapshot);
4488 let start_point = TP::to_point(&range.start, &snapshot);
4489 (start_point..end_point, text)
4490 })
4491 .sorted_by_key(|(range, _)| range.start);
4492 buffer.edit(edits, None, cx);
4493 })
4494 }
4495 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4496 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4497 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4498 let new_selections =
4499 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4500 .zip(new_selection_deltas)
4501 .map(|(selection, delta)| Selection {
4502 id: selection.id,
4503 start: selection.start + delta,
4504 end: selection.end + delta,
4505 reversed: selection.reversed,
4506 goal: SelectionGoal::None,
4507 })
4508 .collect::<Vec<_>>();
4509
4510 let mut i = 0;
4511 for (position, delta, selection_id, pair) in new_autoclose_regions {
4512 let position = position.to_offset(map.buffer_snapshot()) + delta;
4513 let start = map.buffer_snapshot().anchor_before(position);
4514 let end = map.buffer_snapshot().anchor_after(position);
4515 while let Some(existing_state) = this.autoclose_regions.get(i) {
4516 match existing_state
4517 .range
4518 .start
4519 .cmp(&start, map.buffer_snapshot())
4520 {
4521 Ordering::Less => i += 1,
4522 Ordering::Greater => break,
4523 Ordering::Equal => {
4524 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4525 Ordering::Less => i += 1,
4526 Ordering::Equal => break,
4527 Ordering::Greater => break,
4528 }
4529 }
4530 }
4531 }
4532 this.autoclose_regions.insert(
4533 i,
4534 AutocloseRegion {
4535 selection_id,
4536 range: start..end,
4537 pair,
4538 },
4539 );
4540 }
4541
4542 let had_active_edit_prediction = this.has_active_edit_prediction();
4543 this.change_selections(
4544 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4545 window,
4546 cx,
4547 |s| s.select(new_selections),
4548 );
4549
4550 if !bracket_inserted
4551 && let Some(on_type_format_task) =
4552 this.trigger_on_type_formatting(text.to_string(), window, cx)
4553 {
4554 on_type_format_task.detach_and_log_err(cx);
4555 }
4556
4557 let editor_settings = EditorSettings::get_global(cx);
4558 if bracket_inserted
4559 && (editor_settings.auto_signature_help
4560 || editor_settings.show_signature_help_after_edits)
4561 {
4562 this.show_signature_help(&ShowSignatureHelp, window, cx);
4563 }
4564
4565 let trigger_in_words =
4566 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4567 if this.hard_wrap.is_some() {
4568 let latest: Range<Point> = this.selections.newest(&map).range();
4569 if latest.is_empty()
4570 && this
4571 .buffer()
4572 .read(cx)
4573 .snapshot(cx)
4574 .line_len(MultiBufferRow(latest.start.row))
4575 == latest.start.column
4576 {
4577 this.rewrap_impl(
4578 RewrapOptions {
4579 override_language_settings: true,
4580 preserve_existing_whitespace: true,
4581 },
4582 cx,
4583 )
4584 }
4585 }
4586 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4587 refresh_linked_ranges(this, window, cx);
4588 this.refresh_edit_prediction(true, false, window, cx);
4589 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4590 });
4591 }
4592
4593 fn find_possible_emoji_shortcode_at_position(
4594 snapshot: &MultiBufferSnapshot,
4595 position: Point,
4596 ) -> Option<String> {
4597 let mut chars = Vec::new();
4598 let mut found_colon = false;
4599 for char in snapshot.reversed_chars_at(position).take(100) {
4600 // Found a possible emoji shortcode in the middle of the buffer
4601 if found_colon {
4602 if char.is_whitespace() {
4603 chars.reverse();
4604 return Some(chars.iter().collect());
4605 }
4606 // If the previous character is not a whitespace, we are in the middle of a word
4607 // and we only want to complete the shortcode if the word is made up of other emojis
4608 let mut containing_word = String::new();
4609 for ch in snapshot
4610 .reversed_chars_at(position)
4611 .skip(chars.len() + 1)
4612 .take(100)
4613 {
4614 if ch.is_whitespace() {
4615 break;
4616 }
4617 containing_word.push(ch);
4618 }
4619 let containing_word = containing_word.chars().rev().collect::<String>();
4620 if util::word_consists_of_emojis(containing_word.as_str()) {
4621 chars.reverse();
4622 return Some(chars.iter().collect());
4623 }
4624 }
4625
4626 if char.is_whitespace() || !char.is_ascii() {
4627 return None;
4628 }
4629 if char == ':' {
4630 found_colon = true;
4631 } else {
4632 chars.push(char);
4633 }
4634 }
4635 // Found a possible emoji shortcode at the beginning of the buffer
4636 chars.reverse();
4637 Some(chars.iter().collect())
4638 }
4639
4640 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4641 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4642 self.transact(window, cx, |this, window, cx| {
4643 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4644 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4645 let multi_buffer = this.buffer.read(cx);
4646 let buffer = multi_buffer.snapshot(cx);
4647 selections
4648 .iter()
4649 .map(|selection| {
4650 let start_point = selection.start.to_point(&buffer);
4651 let mut existing_indent =
4652 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4653 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4654 let start = selection.start;
4655 let end = selection.end;
4656 let selection_is_empty = start == end;
4657 let language_scope = buffer.language_scope_at(start);
4658 let (
4659 comment_delimiter,
4660 doc_delimiter,
4661 insert_extra_newline,
4662 indent_on_newline,
4663 indent_on_extra_newline,
4664 ) = if let Some(language) = &language_scope {
4665 let mut insert_extra_newline =
4666 insert_extra_newline_brackets(&buffer, start..end, language)
4667 || insert_extra_newline_tree_sitter(&buffer, start..end);
4668
4669 // Comment extension on newline is allowed only for cursor selections
4670 let comment_delimiter = maybe!({
4671 if !selection_is_empty {
4672 return None;
4673 }
4674
4675 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4676 return None;
4677 }
4678
4679 let delimiters = language.line_comment_prefixes();
4680 let max_len_of_delimiter =
4681 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4682 let (snapshot, range) =
4683 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4684
4685 let num_of_whitespaces = snapshot
4686 .chars_for_range(range.clone())
4687 .take_while(|c| c.is_whitespace())
4688 .count();
4689 let comment_candidate = snapshot
4690 .chars_for_range(range.clone())
4691 .skip(num_of_whitespaces)
4692 .take(max_len_of_delimiter)
4693 .collect::<String>();
4694 let (delimiter, trimmed_len) = delimiters
4695 .iter()
4696 .filter_map(|delimiter| {
4697 let prefix = delimiter.trim_end();
4698 if comment_candidate.starts_with(prefix) {
4699 Some((delimiter, prefix.len()))
4700 } else {
4701 None
4702 }
4703 })
4704 .max_by_key(|(_, len)| *len)?;
4705
4706 if let Some(BlockCommentConfig {
4707 start: block_start, ..
4708 }) = language.block_comment()
4709 {
4710 let block_start_trimmed = block_start.trim_end();
4711 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4712 let line_content = snapshot
4713 .chars_for_range(range)
4714 .skip(num_of_whitespaces)
4715 .take(block_start_trimmed.len())
4716 .collect::<String>();
4717
4718 if line_content.starts_with(block_start_trimmed) {
4719 return None;
4720 }
4721 }
4722 }
4723
4724 let cursor_is_placed_after_comment_marker =
4725 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4726 if cursor_is_placed_after_comment_marker {
4727 Some(delimiter.clone())
4728 } else {
4729 None
4730 }
4731 });
4732
4733 let mut indent_on_newline = IndentSize::spaces(0);
4734 let mut indent_on_extra_newline = IndentSize::spaces(0);
4735
4736 let doc_delimiter = maybe!({
4737 if !selection_is_empty {
4738 return None;
4739 }
4740
4741 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4742 return None;
4743 }
4744
4745 let BlockCommentConfig {
4746 start: start_tag,
4747 end: end_tag,
4748 prefix: delimiter,
4749 tab_size: len,
4750 } = language.documentation_comment()?;
4751 let is_within_block_comment = buffer
4752 .language_scope_at(start_point)
4753 .is_some_and(|scope| scope.override_name() == Some("comment"));
4754 if !is_within_block_comment {
4755 return None;
4756 }
4757
4758 let (snapshot, range) =
4759 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4760
4761 let num_of_whitespaces = snapshot
4762 .chars_for_range(range.clone())
4763 .take_while(|c| c.is_whitespace())
4764 .count();
4765
4766 // 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.
4767 let column = start_point.column;
4768 let cursor_is_after_start_tag = {
4769 let start_tag_len = start_tag.len();
4770 let start_tag_line = snapshot
4771 .chars_for_range(range.clone())
4772 .skip(num_of_whitespaces)
4773 .take(start_tag_len)
4774 .collect::<String>();
4775 if start_tag_line.starts_with(start_tag.as_ref()) {
4776 num_of_whitespaces + start_tag_len <= column as usize
4777 } else {
4778 false
4779 }
4780 };
4781
4782 let cursor_is_after_delimiter = {
4783 let delimiter_trim = delimiter.trim_end();
4784 let delimiter_line = snapshot
4785 .chars_for_range(range.clone())
4786 .skip(num_of_whitespaces)
4787 .take(delimiter_trim.len())
4788 .collect::<String>();
4789 if delimiter_line.starts_with(delimiter_trim) {
4790 num_of_whitespaces + delimiter_trim.len() <= column as usize
4791 } else {
4792 false
4793 }
4794 };
4795
4796 let cursor_is_before_end_tag_if_exists = {
4797 let mut char_position = 0u32;
4798 let mut end_tag_offset = None;
4799
4800 'outer: for chunk in snapshot.text_for_range(range) {
4801 if let Some(byte_pos) = chunk.find(&**end_tag) {
4802 let chars_before_match =
4803 chunk[..byte_pos].chars().count() as u32;
4804 end_tag_offset =
4805 Some(char_position + chars_before_match);
4806 break 'outer;
4807 }
4808 char_position += chunk.chars().count() as u32;
4809 }
4810
4811 if let Some(end_tag_offset) = end_tag_offset {
4812 let cursor_is_before_end_tag = column <= end_tag_offset;
4813 if cursor_is_after_start_tag {
4814 if cursor_is_before_end_tag {
4815 insert_extra_newline = true;
4816 }
4817 let cursor_is_at_start_of_end_tag =
4818 column == end_tag_offset;
4819 if cursor_is_at_start_of_end_tag {
4820 indent_on_extra_newline.len = *len;
4821 }
4822 }
4823 cursor_is_before_end_tag
4824 } else {
4825 true
4826 }
4827 };
4828
4829 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4830 && cursor_is_before_end_tag_if_exists
4831 {
4832 if cursor_is_after_start_tag {
4833 indent_on_newline.len = *len;
4834 }
4835 Some(delimiter.clone())
4836 } else {
4837 None
4838 }
4839 });
4840
4841 (
4842 comment_delimiter,
4843 doc_delimiter,
4844 insert_extra_newline,
4845 indent_on_newline,
4846 indent_on_extra_newline,
4847 )
4848 } else {
4849 (
4850 None,
4851 None,
4852 false,
4853 IndentSize::default(),
4854 IndentSize::default(),
4855 )
4856 };
4857
4858 let prevent_auto_indent = doc_delimiter.is_some();
4859 let delimiter = comment_delimiter.or(doc_delimiter);
4860
4861 let capacity_for_delimiter =
4862 delimiter.as_deref().map(str::len).unwrap_or_default();
4863 let mut new_text = String::with_capacity(
4864 1 + capacity_for_delimiter
4865 + existing_indent.len as usize
4866 + indent_on_newline.len as usize
4867 + indent_on_extra_newline.len as usize,
4868 );
4869 new_text.push('\n');
4870 new_text.extend(existing_indent.chars());
4871 new_text.extend(indent_on_newline.chars());
4872
4873 if let Some(delimiter) = &delimiter {
4874 new_text.push_str(delimiter);
4875 }
4876
4877 if insert_extra_newline {
4878 new_text.push('\n');
4879 new_text.extend(existing_indent.chars());
4880 new_text.extend(indent_on_extra_newline.chars());
4881 }
4882
4883 let anchor = buffer.anchor_after(end);
4884 let new_selection = selection.map(|_| anchor);
4885 (
4886 ((start..end, new_text), prevent_auto_indent),
4887 (insert_extra_newline, new_selection),
4888 )
4889 })
4890 .unzip()
4891 };
4892
4893 let mut auto_indent_edits = Vec::new();
4894 let mut edits = Vec::new();
4895 for (edit, prevent_auto_indent) in edits_with_flags {
4896 if prevent_auto_indent {
4897 edits.push(edit);
4898 } else {
4899 auto_indent_edits.push(edit);
4900 }
4901 }
4902 if !edits.is_empty() {
4903 this.edit(edits, cx);
4904 }
4905 if !auto_indent_edits.is_empty() {
4906 this.edit_with_autoindent(auto_indent_edits, cx);
4907 }
4908
4909 let buffer = this.buffer.read(cx).snapshot(cx);
4910 let new_selections = selection_info
4911 .into_iter()
4912 .map(|(extra_newline_inserted, new_selection)| {
4913 let mut cursor = new_selection.end.to_point(&buffer);
4914 if extra_newline_inserted {
4915 cursor.row -= 1;
4916 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4917 }
4918 new_selection.map(|_| cursor)
4919 })
4920 .collect();
4921
4922 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4923 this.refresh_edit_prediction(true, false, window, cx);
4924 });
4925 }
4926
4927 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4928 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4929
4930 let buffer = self.buffer.read(cx);
4931 let snapshot = buffer.snapshot(cx);
4932
4933 let mut edits = Vec::new();
4934 let mut rows = Vec::new();
4935
4936 for (rows_inserted, selection) in self
4937 .selections
4938 .all_adjusted(&self.display_snapshot(cx))
4939 .into_iter()
4940 .enumerate()
4941 {
4942 let cursor = selection.head();
4943 let row = cursor.row;
4944
4945 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4946
4947 let newline = "\n".to_string();
4948 edits.push((start_of_line..start_of_line, newline));
4949
4950 rows.push(row + rows_inserted as u32);
4951 }
4952
4953 self.transact(window, cx, |editor, window, cx| {
4954 editor.edit(edits, cx);
4955
4956 editor.change_selections(Default::default(), window, cx, |s| {
4957 let mut index = 0;
4958 s.move_cursors_with(|map, _, _| {
4959 let row = rows[index];
4960 index += 1;
4961
4962 let point = Point::new(row, 0);
4963 let boundary = map.next_line_boundary(point).1;
4964 let clipped = map.clip_point(boundary, Bias::Left);
4965
4966 (clipped, SelectionGoal::None)
4967 });
4968 });
4969
4970 let mut indent_edits = Vec::new();
4971 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4972 for row in rows {
4973 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4974 for (row, indent) in indents {
4975 if indent.len == 0 {
4976 continue;
4977 }
4978
4979 let text = match indent.kind {
4980 IndentKind::Space => " ".repeat(indent.len as usize),
4981 IndentKind::Tab => "\t".repeat(indent.len as usize),
4982 };
4983 let point = Point::new(row.0, 0);
4984 indent_edits.push((point..point, text));
4985 }
4986 }
4987 editor.edit(indent_edits, cx);
4988 });
4989 }
4990
4991 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4992 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4993
4994 let buffer = self.buffer.read(cx);
4995 let snapshot = buffer.snapshot(cx);
4996
4997 let mut edits = Vec::new();
4998 let mut rows = Vec::new();
4999 let mut rows_inserted = 0;
5000
5001 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5002 let cursor = selection.head();
5003 let row = cursor.row;
5004
5005 let point = Point::new(row + 1, 0);
5006 let start_of_line = snapshot.clip_point(point, Bias::Left);
5007
5008 let newline = "\n".to_string();
5009 edits.push((start_of_line..start_of_line, newline));
5010
5011 rows_inserted += 1;
5012 rows.push(row + rows_inserted);
5013 }
5014
5015 self.transact(window, cx, |editor, window, cx| {
5016 editor.edit(edits, cx);
5017
5018 editor.change_selections(Default::default(), window, cx, |s| {
5019 let mut index = 0;
5020 s.move_cursors_with(|map, _, _| {
5021 let row = rows[index];
5022 index += 1;
5023
5024 let point = Point::new(row, 0);
5025 let boundary = map.next_line_boundary(point).1;
5026 let clipped = map.clip_point(boundary, Bias::Left);
5027
5028 (clipped, SelectionGoal::None)
5029 });
5030 });
5031
5032 let mut indent_edits = Vec::new();
5033 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5034 for row in rows {
5035 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5036 for (row, indent) in indents {
5037 if indent.len == 0 {
5038 continue;
5039 }
5040
5041 let text = match indent.kind {
5042 IndentKind::Space => " ".repeat(indent.len as usize),
5043 IndentKind::Tab => "\t".repeat(indent.len as usize),
5044 };
5045 let point = Point::new(row.0, 0);
5046 indent_edits.push((point..point, text));
5047 }
5048 }
5049 editor.edit(indent_edits, cx);
5050 });
5051 }
5052
5053 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5054 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5055 original_indent_columns: Vec::new(),
5056 });
5057 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5058 }
5059
5060 fn insert_with_autoindent_mode(
5061 &mut self,
5062 text: &str,
5063 autoindent_mode: Option<AutoindentMode>,
5064 window: &mut Window,
5065 cx: &mut Context<Self>,
5066 ) {
5067 if self.read_only(cx) {
5068 return;
5069 }
5070
5071 let text: Arc<str> = text.into();
5072 self.transact(window, cx, |this, window, cx| {
5073 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5074 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5075 let anchors = {
5076 let snapshot = buffer.read(cx);
5077 old_selections
5078 .iter()
5079 .map(|s| {
5080 let anchor = snapshot.anchor_after(s.head());
5081 s.map(|_| anchor)
5082 })
5083 .collect::<Vec<_>>()
5084 };
5085 buffer.edit(
5086 old_selections
5087 .iter()
5088 .map(|s| (s.start..s.end, text.clone())),
5089 autoindent_mode,
5090 cx,
5091 );
5092 anchors
5093 });
5094
5095 this.change_selections(Default::default(), window, cx, |s| {
5096 s.select_anchors(selection_anchors);
5097 });
5098
5099 cx.notify();
5100 });
5101 }
5102
5103 fn trigger_completion_on_input(
5104 &mut self,
5105 text: &str,
5106 trigger_in_words: bool,
5107 window: &mut Window,
5108 cx: &mut Context<Self>,
5109 ) {
5110 let completions_source = self
5111 .context_menu
5112 .borrow()
5113 .as_ref()
5114 .and_then(|menu| match menu {
5115 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5116 CodeContextMenu::CodeActions(_) => None,
5117 });
5118
5119 match completions_source {
5120 Some(CompletionsMenuSource::Words { .. }) => {
5121 self.open_or_update_completions_menu(
5122 Some(CompletionsMenuSource::Words {
5123 ignore_threshold: false,
5124 }),
5125 None,
5126 trigger_in_words,
5127 window,
5128 cx,
5129 );
5130 }
5131 _ => self.open_or_update_completions_menu(
5132 None,
5133 Some(text.to_owned()).filter(|x| !x.is_empty()),
5134 true,
5135 window,
5136 cx,
5137 ),
5138 }
5139 }
5140
5141 /// If any empty selections is touching the start of its innermost containing autoclose
5142 /// region, expand it to select the brackets.
5143 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5144 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5145 let buffer = self.buffer.read(cx).read(cx);
5146 let new_selections = self
5147 .selections_with_autoclose_regions(selections, &buffer)
5148 .map(|(mut selection, region)| {
5149 if !selection.is_empty() {
5150 return selection;
5151 }
5152
5153 if let Some(region) = region {
5154 let mut range = region.range.to_offset(&buffer);
5155 if selection.start == range.start && range.start >= region.pair.start.len() {
5156 range.start -= region.pair.start.len();
5157 if buffer.contains_str_at(range.start, ®ion.pair.start)
5158 && buffer.contains_str_at(range.end, ®ion.pair.end)
5159 {
5160 range.end += region.pair.end.len();
5161 selection.start = range.start;
5162 selection.end = range.end;
5163
5164 return selection;
5165 }
5166 }
5167 }
5168
5169 let always_treat_brackets_as_autoclosed = buffer
5170 .language_settings_at(selection.start, cx)
5171 .always_treat_brackets_as_autoclosed;
5172
5173 if !always_treat_brackets_as_autoclosed {
5174 return selection;
5175 }
5176
5177 if let Some(scope) = buffer.language_scope_at(selection.start) {
5178 for (pair, enabled) in scope.brackets() {
5179 if !enabled || !pair.close {
5180 continue;
5181 }
5182
5183 if buffer.contains_str_at(selection.start, &pair.end) {
5184 let pair_start_len = pair.start.len();
5185 if buffer.contains_str_at(
5186 selection.start.saturating_sub(pair_start_len),
5187 &pair.start,
5188 ) {
5189 selection.start -= pair_start_len;
5190 selection.end += pair.end.len();
5191
5192 return selection;
5193 }
5194 }
5195 }
5196 }
5197
5198 selection
5199 })
5200 .collect();
5201
5202 drop(buffer);
5203 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5204 selections.select(new_selections)
5205 });
5206 }
5207
5208 /// Iterate the given selections, and for each one, find the smallest surrounding
5209 /// autoclose region. This uses the ordering of the selections and the autoclose
5210 /// regions to avoid repeated comparisons.
5211 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5212 &'a self,
5213 selections: impl IntoIterator<Item = Selection<D>>,
5214 buffer: &'a MultiBufferSnapshot,
5215 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5216 let mut i = 0;
5217 let mut regions = self.autoclose_regions.as_slice();
5218 selections.into_iter().map(move |selection| {
5219 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5220
5221 let mut enclosing = None;
5222 while let Some(pair_state) = regions.get(i) {
5223 if pair_state.range.end.to_offset(buffer) < range.start {
5224 regions = ®ions[i + 1..];
5225 i = 0;
5226 } else if pair_state.range.start.to_offset(buffer) > range.end {
5227 break;
5228 } else {
5229 if pair_state.selection_id == selection.id {
5230 enclosing = Some(pair_state);
5231 }
5232 i += 1;
5233 }
5234 }
5235
5236 (selection, enclosing)
5237 })
5238 }
5239
5240 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5241 fn invalidate_autoclose_regions(
5242 &mut self,
5243 mut selections: &[Selection<Anchor>],
5244 buffer: &MultiBufferSnapshot,
5245 ) {
5246 self.autoclose_regions.retain(|state| {
5247 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5248 return false;
5249 }
5250
5251 let mut i = 0;
5252 while let Some(selection) = selections.get(i) {
5253 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5254 selections = &selections[1..];
5255 continue;
5256 }
5257 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5258 break;
5259 }
5260 if selection.id == state.selection_id {
5261 return true;
5262 } else {
5263 i += 1;
5264 }
5265 }
5266 false
5267 });
5268 }
5269
5270 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5271 let offset = position.to_offset(buffer);
5272 let (word_range, kind) =
5273 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5274 if offset > word_range.start && kind == Some(CharKind::Word) {
5275 Some(
5276 buffer
5277 .text_for_range(word_range.start..offset)
5278 .collect::<String>(),
5279 )
5280 } else {
5281 None
5282 }
5283 }
5284
5285 pub fn visible_excerpts(
5286 &self,
5287 cx: &mut Context<Editor>,
5288 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5289 let Some(project) = self.project() else {
5290 return HashMap::default();
5291 };
5292 let project = project.read(cx);
5293 let multi_buffer = self.buffer().read(cx);
5294 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5295 let multi_buffer_visible_start = self
5296 .scroll_manager
5297 .anchor()
5298 .anchor
5299 .to_point(&multi_buffer_snapshot);
5300 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5301 multi_buffer_visible_start
5302 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5303 Bias::Left,
5304 );
5305 multi_buffer_snapshot
5306 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5307 .into_iter()
5308 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5309 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5310 let buffer_file = project::File::from_dyn(buffer.file())?;
5311 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5312 let worktree_entry = buffer_worktree
5313 .read(cx)
5314 .entry_for_id(buffer_file.project_entry_id()?)?;
5315 if worktree_entry.is_ignored {
5316 None
5317 } else {
5318 Some((
5319 excerpt_id,
5320 (
5321 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5322 buffer.version().clone(),
5323 excerpt_visible_range,
5324 ),
5325 ))
5326 }
5327 })
5328 .collect()
5329 }
5330
5331 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5332 TextLayoutDetails {
5333 text_system: window.text_system().clone(),
5334 editor_style: self.style.clone().unwrap(),
5335 rem_size: window.rem_size(),
5336 scroll_anchor: self.scroll_manager.anchor(),
5337 visible_rows: self.visible_line_count(),
5338 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5339 }
5340 }
5341
5342 fn trigger_on_type_formatting(
5343 &self,
5344 input: String,
5345 window: &mut Window,
5346 cx: &mut Context<Self>,
5347 ) -> Option<Task<Result<()>>> {
5348 if input.len() != 1 {
5349 return None;
5350 }
5351
5352 let project = self.project()?;
5353 let position = self.selections.newest_anchor().head();
5354 let (buffer, buffer_position) = self
5355 .buffer
5356 .read(cx)
5357 .text_anchor_for_position(position, cx)?;
5358
5359 let settings = language_settings::language_settings(
5360 buffer
5361 .read(cx)
5362 .language_at(buffer_position)
5363 .map(|l| l.name()),
5364 buffer.read(cx).file(),
5365 cx,
5366 );
5367 if !settings.use_on_type_format {
5368 return None;
5369 }
5370
5371 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5372 // hence we do LSP request & edit on host side only — add formats to host's history.
5373 let push_to_lsp_host_history = true;
5374 // If this is not the host, append its history with new edits.
5375 let push_to_client_history = project.read(cx).is_via_collab();
5376
5377 let on_type_formatting = project.update(cx, |project, cx| {
5378 project.on_type_format(
5379 buffer.clone(),
5380 buffer_position,
5381 input,
5382 push_to_lsp_host_history,
5383 cx,
5384 )
5385 });
5386 Some(cx.spawn_in(window, async move |editor, cx| {
5387 if let Some(transaction) = on_type_formatting.await? {
5388 if push_to_client_history {
5389 buffer
5390 .update(cx, |buffer, _| {
5391 buffer.push_transaction(transaction, Instant::now());
5392 buffer.finalize_last_transaction();
5393 })
5394 .ok();
5395 }
5396 editor.update(cx, |editor, cx| {
5397 editor.refresh_document_highlights(cx);
5398 })?;
5399 }
5400 Ok(())
5401 }))
5402 }
5403
5404 pub fn show_word_completions(
5405 &mut self,
5406 _: &ShowWordCompletions,
5407 window: &mut Window,
5408 cx: &mut Context<Self>,
5409 ) {
5410 self.open_or_update_completions_menu(
5411 Some(CompletionsMenuSource::Words {
5412 ignore_threshold: true,
5413 }),
5414 None,
5415 false,
5416 window,
5417 cx,
5418 );
5419 }
5420
5421 pub fn show_completions(
5422 &mut self,
5423 _: &ShowCompletions,
5424 window: &mut Window,
5425 cx: &mut Context<Self>,
5426 ) {
5427 self.open_or_update_completions_menu(None, None, false, window, cx);
5428 }
5429
5430 fn open_or_update_completions_menu(
5431 &mut self,
5432 requested_source: Option<CompletionsMenuSource>,
5433 trigger: Option<String>,
5434 trigger_in_words: bool,
5435 window: &mut Window,
5436 cx: &mut Context<Self>,
5437 ) {
5438 if self.pending_rename.is_some() {
5439 return;
5440 }
5441
5442 let completions_source = self
5443 .context_menu
5444 .borrow()
5445 .as_ref()
5446 .and_then(|menu| match menu {
5447 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5448 CodeContextMenu::CodeActions(_) => None,
5449 });
5450
5451 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5452
5453 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5454 // inserted and selected. To handle that case, the start of the selection is used so that
5455 // the menu starts with all choices.
5456 let position = self
5457 .selections
5458 .newest_anchor()
5459 .start
5460 .bias_right(&multibuffer_snapshot);
5461 if position.diff_base_anchor.is_some() {
5462 return;
5463 }
5464 let buffer_position = multibuffer_snapshot.anchor_before(position);
5465 let Some(buffer) = buffer_position
5466 .buffer_id
5467 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5468 else {
5469 return;
5470 };
5471 let buffer_snapshot = buffer.read(cx).snapshot();
5472
5473 let query: Option<Arc<String>> =
5474 Self::completion_query(&multibuffer_snapshot, buffer_position)
5475 .map(|query| query.into());
5476
5477 drop(multibuffer_snapshot);
5478
5479 // Hide the current completions menu when query is empty. Without this, cached
5480 // completions from before the trigger char may be reused (#32774).
5481 if query.is_none() {
5482 let menu_is_open = matches!(
5483 self.context_menu.borrow().as_ref(),
5484 Some(CodeContextMenu::Completions(_))
5485 );
5486 if menu_is_open {
5487 self.hide_context_menu(window, cx);
5488 }
5489 }
5490
5491 let mut ignore_word_threshold = false;
5492 let provider = match requested_source {
5493 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5494 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5495 ignore_word_threshold = ignore_threshold;
5496 None
5497 }
5498 Some(CompletionsMenuSource::SnippetChoices)
5499 | Some(CompletionsMenuSource::SnippetsOnly) => {
5500 log::error!("bug: SnippetChoices requested_source is not handled");
5501 None
5502 }
5503 };
5504
5505 let sort_completions = provider
5506 .as_ref()
5507 .is_some_and(|provider| provider.sort_completions());
5508
5509 let filter_completions = provider
5510 .as_ref()
5511 .is_none_or(|provider| provider.filter_completions());
5512
5513 let was_snippets_only = matches!(
5514 completions_source,
5515 Some(CompletionsMenuSource::SnippetsOnly)
5516 );
5517
5518 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5519 if filter_completions {
5520 menu.filter(query.clone(), provider.clone(), window, cx);
5521 }
5522 // When `is_incomplete` is false, no need to re-query completions when the current query
5523 // is a suffix of the initial query.
5524 let was_complete = !menu.is_incomplete;
5525 if was_complete && !was_snippets_only {
5526 // If the new query is a suffix of the old query (typing more characters) and
5527 // the previous result was complete, the existing completions can be filtered.
5528 //
5529 // Note that this is always true for snippet completions.
5530 let query_matches = match (&menu.initial_query, &query) {
5531 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5532 (None, _) => true,
5533 _ => false,
5534 };
5535 if query_matches {
5536 let position_matches = if menu.initial_position == position {
5537 true
5538 } else {
5539 let snapshot = self.buffer.read(cx).read(cx);
5540 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5541 };
5542 if position_matches {
5543 return;
5544 }
5545 }
5546 }
5547 };
5548
5549 let Anchor {
5550 excerpt_id: buffer_excerpt_id,
5551 text_anchor: buffer_position,
5552 ..
5553 } = buffer_position;
5554
5555 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5556 buffer_snapshot.surrounding_word(buffer_position, None)
5557 {
5558 let word_to_exclude = buffer_snapshot
5559 .text_for_range(word_range.clone())
5560 .collect::<String>();
5561 (
5562 buffer_snapshot.anchor_before(word_range.start)
5563 ..buffer_snapshot.anchor_after(buffer_position),
5564 Some(word_to_exclude),
5565 )
5566 } else {
5567 (buffer_position..buffer_position, None)
5568 };
5569
5570 let language = buffer_snapshot
5571 .language_at(buffer_position)
5572 .map(|language| language.name());
5573
5574 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5575 .completions
5576 .clone();
5577
5578 let show_completion_documentation = buffer_snapshot
5579 .settings_at(buffer_position, cx)
5580 .show_completion_documentation;
5581
5582 // The document can be large, so stay in reasonable bounds when searching for words,
5583 // otherwise completion pop-up might be slow to appear.
5584 const WORD_LOOKUP_ROWS: u32 = 5_000;
5585 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5586 let min_word_search = buffer_snapshot.clip_point(
5587 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5588 Bias::Left,
5589 );
5590 let max_word_search = buffer_snapshot.clip_point(
5591 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5592 Bias::Right,
5593 );
5594 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5595 ..buffer_snapshot.point_to_offset(max_word_search);
5596
5597 let skip_digits = query
5598 .as_ref()
5599 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5600
5601 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5602 trigger.as_ref().is_none_or(|trigger| {
5603 provider.is_completion_trigger(
5604 &buffer,
5605 position.text_anchor,
5606 trigger,
5607 trigger_in_words,
5608 completions_source.is_some(),
5609 cx,
5610 )
5611 })
5612 });
5613
5614 let provider_responses = if let Some(provider) = &provider
5615 && load_provider_completions
5616 {
5617 let trigger_character =
5618 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5619 let completion_context = CompletionContext {
5620 trigger_kind: match &trigger_character {
5621 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5622 None => CompletionTriggerKind::INVOKED,
5623 },
5624 trigger_character,
5625 };
5626
5627 provider.completions(
5628 buffer_excerpt_id,
5629 &buffer,
5630 buffer_position,
5631 completion_context,
5632 window,
5633 cx,
5634 )
5635 } else {
5636 Task::ready(Ok(Vec::new()))
5637 };
5638
5639 let load_word_completions = if !self.word_completions_enabled {
5640 false
5641 } else if requested_source
5642 == Some(CompletionsMenuSource::Words {
5643 ignore_threshold: true,
5644 })
5645 {
5646 true
5647 } else {
5648 load_provider_completions
5649 && completion_settings.words != WordsCompletionMode::Disabled
5650 && (ignore_word_threshold || {
5651 let words_min_length = completion_settings.words_min_length;
5652 // check whether word has at least `words_min_length` characters
5653 let query_chars = query.iter().flat_map(|q| q.chars());
5654 query_chars.take(words_min_length).count() == words_min_length
5655 })
5656 };
5657
5658 let mut words = if load_word_completions {
5659 cx.background_spawn(async move {
5660 buffer_snapshot.words_in_range(WordsQuery {
5661 fuzzy_contents: None,
5662 range: word_search_range,
5663 skip_digits,
5664 })
5665 })
5666 } else {
5667 Task::ready(BTreeMap::default())
5668 };
5669
5670 let snippets = if let Some(provider) = &provider
5671 && provider.show_snippets()
5672 && let Some(project) = self.project()
5673 {
5674 project.update(cx, |project, cx| {
5675 snippet_completions(project, &buffer, buffer_position, cx)
5676 })
5677 } else {
5678 Task::ready(Ok(CompletionResponse {
5679 completions: Vec::new(),
5680 display_options: Default::default(),
5681 is_incomplete: false,
5682 }))
5683 };
5684
5685 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5686
5687 let id = post_inc(&mut self.next_completion_id);
5688 let task = cx.spawn_in(window, async move |editor, cx| {
5689 let Ok(()) = editor.update(cx, |this, _| {
5690 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5691 }) else {
5692 return;
5693 };
5694
5695 // TODO: Ideally completions from different sources would be selectively re-queried, so
5696 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5697 let mut completions = Vec::new();
5698 let mut is_incomplete = false;
5699 let mut display_options: Option<CompletionDisplayOptions> = None;
5700 if let Some(provider_responses) = provider_responses.await.log_err()
5701 && !provider_responses.is_empty()
5702 {
5703 for response in provider_responses {
5704 completions.extend(response.completions);
5705 is_incomplete = is_incomplete || response.is_incomplete;
5706 match display_options.as_mut() {
5707 None => {
5708 display_options = Some(response.display_options);
5709 }
5710 Some(options) => options.merge(&response.display_options),
5711 }
5712 }
5713 if completion_settings.words == WordsCompletionMode::Fallback {
5714 words = Task::ready(BTreeMap::default());
5715 }
5716 }
5717 let display_options = display_options.unwrap_or_default();
5718
5719 let mut words = words.await;
5720 if let Some(word_to_exclude) = &word_to_exclude {
5721 words.remove(word_to_exclude);
5722 }
5723 for lsp_completion in &completions {
5724 words.remove(&lsp_completion.new_text);
5725 }
5726 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5727 replace_range: word_replace_range.clone(),
5728 new_text: word.clone(),
5729 label: CodeLabel::plain(word, None),
5730 icon_path: None,
5731 documentation: None,
5732 source: CompletionSource::BufferWord {
5733 word_range,
5734 resolved: false,
5735 },
5736 insert_text_mode: Some(InsertTextMode::AS_IS),
5737 confirm: None,
5738 }));
5739
5740 completions.extend(
5741 snippets
5742 .await
5743 .into_iter()
5744 .flat_map(|response| response.completions),
5745 );
5746
5747 let menu = if completions.is_empty() {
5748 None
5749 } else {
5750 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5751 let languages = editor
5752 .workspace
5753 .as_ref()
5754 .and_then(|(workspace, _)| workspace.upgrade())
5755 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5756 let menu = CompletionsMenu::new(
5757 id,
5758 requested_source.unwrap_or(if load_provider_completions {
5759 CompletionsMenuSource::Normal
5760 } else {
5761 CompletionsMenuSource::SnippetsOnly
5762 }),
5763 sort_completions,
5764 show_completion_documentation,
5765 position,
5766 query.clone(),
5767 is_incomplete,
5768 buffer.clone(),
5769 completions.into(),
5770 display_options,
5771 snippet_sort_order,
5772 languages,
5773 language,
5774 cx,
5775 );
5776
5777 let query = if filter_completions { query } else { None };
5778 let matches_task = if let Some(query) = query {
5779 menu.do_async_filtering(query, cx)
5780 } else {
5781 Task::ready(menu.unfiltered_matches())
5782 };
5783 (menu, matches_task)
5784 }) else {
5785 return;
5786 };
5787
5788 let matches = matches_task.await;
5789
5790 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5791 // Newer menu already set, so exit.
5792 if let Some(CodeContextMenu::Completions(prev_menu)) =
5793 editor.context_menu.borrow().as_ref()
5794 && prev_menu.id > id
5795 {
5796 return;
5797 };
5798
5799 // Only valid to take prev_menu because it the new menu is immediately set
5800 // below, or the menu is hidden.
5801 if let Some(CodeContextMenu::Completions(prev_menu)) =
5802 editor.context_menu.borrow_mut().take()
5803 {
5804 let position_matches =
5805 if prev_menu.initial_position == menu.initial_position {
5806 true
5807 } else {
5808 let snapshot = editor.buffer.read(cx).read(cx);
5809 prev_menu.initial_position.to_offset(&snapshot)
5810 == menu.initial_position.to_offset(&snapshot)
5811 };
5812 if position_matches {
5813 // Preserve markdown cache before `set_filter_results` because it will
5814 // try to populate the documentation cache.
5815 menu.preserve_markdown_cache(prev_menu);
5816 }
5817 };
5818
5819 menu.set_filter_results(matches, provider, window, cx);
5820 }) else {
5821 return;
5822 };
5823
5824 menu.visible().then_some(menu)
5825 };
5826
5827 editor
5828 .update_in(cx, |editor, window, cx| {
5829 if editor.focus_handle.is_focused(window)
5830 && let Some(menu) = menu
5831 {
5832 *editor.context_menu.borrow_mut() =
5833 Some(CodeContextMenu::Completions(menu));
5834
5835 crate::hover_popover::hide_hover(editor, cx);
5836 if editor.show_edit_predictions_in_menu() {
5837 editor.update_visible_edit_prediction(window, cx);
5838 } else {
5839 editor.discard_edit_prediction(false, cx);
5840 }
5841
5842 cx.notify();
5843 return;
5844 }
5845
5846 if editor.completion_tasks.len() <= 1 {
5847 // If there are no more completion tasks and the last menu was empty, we should hide it.
5848 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5849 // If it was already hidden and we don't show edit predictions in the menu,
5850 // we should also show the edit prediction when available.
5851 if was_hidden && editor.show_edit_predictions_in_menu() {
5852 editor.update_visible_edit_prediction(window, cx);
5853 }
5854 }
5855 })
5856 .ok();
5857 });
5858
5859 self.completion_tasks.push((id, task));
5860 }
5861
5862 #[cfg(feature = "test-support")]
5863 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5864 let menu = self.context_menu.borrow();
5865 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5866 let completions = menu.completions.borrow();
5867 Some(completions.to_vec())
5868 } else {
5869 None
5870 }
5871 }
5872
5873 pub fn with_completions_menu_matching_id<R>(
5874 &self,
5875 id: CompletionId,
5876 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5877 ) -> R {
5878 let mut context_menu = self.context_menu.borrow_mut();
5879 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5880 return f(None);
5881 };
5882 if completions_menu.id != id {
5883 return f(None);
5884 }
5885 f(Some(completions_menu))
5886 }
5887
5888 pub fn confirm_completion(
5889 &mut self,
5890 action: &ConfirmCompletion,
5891 window: &mut Window,
5892 cx: &mut Context<Self>,
5893 ) -> Option<Task<Result<()>>> {
5894 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5895 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5896 }
5897
5898 pub fn confirm_completion_insert(
5899 &mut self,
5900 _: &ConfirmCompletionInsert,
5901 window: &mut Window,
5902 cx: &mut Context<Self>,
5903 ) -> Option<Task<Result<()>>> {
5904 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5905 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5906 }
5907
5908 pub fn confirm_completion_replace(
5909 &mut self,
5910 _: &ConfirmCompletionReplace,
5911 window: &mut Window,
5912 cx: &mut Context<Self>,
5913 ) -> Option<Task<Result<()>>> {
5914 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5915 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5916 }
5917
5918 pub fn compose_completion(
5919 &mut self,
5920 action: &ComposeCompletion,
5921 window: &mut Window,
5922 cx: &mut Context<Self>,
5923 ) -> Option<Task<Result<()>>> {
5924 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5925 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5926 }
5927
5928 fn do_completion(
5929 &mut self,
5930 item_ix: Option<usize>,
5931 intent: CompletionIntent,
5932 window: &mut Window,
5933 cx: &mut Context<Editor>,
5934 ) -> Option<Task<Result<()>>> {
5935 use language::ToOffset as _;
5936
5937 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5938 else {
5939 return None;
5940 };
5941
5942 let candidate_id = {
5943 let entries = completions_menu.entries.borrow();
5944 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5945 if self.show_edit_predictions_in_menu() {
5946 self.discard_edit_prediction(true, cx);
5947 }
5948 mat.candidate_id
5949 };
5950
5951 let completion = completions_menu
5952 .completions
5953 .borrow()
5954 .get(candidate_id)?
5955 .clone();
5956 cx.stop_propagation();
5957
5958 let buffer_handle = completions_menu.buffer.clone();
5959
5960 let CompletionEdit {
5961 new_text,
5962 snippet,
5963 replace_range,
5964 } = process_completion_for_edit(
5965 &completion,
5966 intent,
5967 &buffer_handle,
5968 &completions_menu.initial_position.text_anchor,
5969 cx,
5970 );
5971
5972 let buffer = buffer_handle.read(cx);
5973 let snapshot = self.buffer.read(cx).snapshot(cx);
5974 let newest_anchor = self.selections.newest_anchor();
5975 let replace_range_multibuffer = {
5976 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5977 excerpt.map_range_from_buffer(replace_range.clone())
5978 };
5979 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5980 return None;
5981 }
5982
5983 let old_text = buffer
5984 .text_for_range(replace_range.clone())
5985 .collect::<String>();
5986 let lookbehind = newest_anchor
5987 .start
5988 .text_anchor
5989 .to_offset(buffer)
5990 .saturating_sub(replace_range.start);
5991 let lookahead = replace_range
5992 .end
5993 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5994 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5995 let suffix = &old_text[lookbehind.min(old_text.len())..];
5996
5997 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5998 let mut ranges = Vec::new();
5999 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6000
6001 for selection in &selections {
6002 let range = if selection.id == newest_anchor.id {
6003 replace_range_multibuffer.clone()
6004 } else {
6005 let mut range = selection.range();
6006
6007 // if prefix is present, don't duplicate it
6008 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6009 range.start = range.start.saturating_sub(lookbehind);
6010
6011 // if suffix is also present, mimic the newest cursor and replace it
6012 if selection.id != newest_anchor.id
6013 && snapshot.contains_str_at(range.end, suffix)
6014 {
6015 range.end += lookahead;
6016 }
6017 }
6018 range
6019 };
6020
6021 ranges.push(range.clone());
6022
6023 if !self.linked_edit_ranges.is_empty() {
6024 let start_anchor = snapshot.anchor_before(range.start);
6025 let end_anchor = snapshot.anchor_after(range.end);
6026 if let Some(ranges) = self
6027 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6028 {
6029 for (buffer, edits) in ranges {
6030 linked_edits
6031 .entry(buffer.clone())
6032 .or_default()
6033 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6034 }
6035 }
6036 }
6037 }
6038
6039 let common_prefix_len = old_text
6040 .chars()
6041 .zip(new_text.chars())
6042 .take_while(|(a, b)| a == b)
6043 .map(|(a, _)| a.len_utf8())
6044 .sum::<usize>();
6045
6046 cx.emit(EditorEvent::InputHandled {
6047 utf16_range_to_replace: None,
6048 text: new_text[common_prefix_len..].into(),
6049 });
6050
6051 self.transact(window, cx, |editor, window, cx| {
6052 if let Some(mut snippet) = snippet {
6053 snippet.text = new_text.to_string();
6054 editor
6055 .insert_snippet(&ranges, snippet, window, cx)
6056 .log_err();
6057 } else {
6058 editor.buffer.update(cx, |multi_buffer, cx| {
6059 let auto_indent = match completion.insert_text_mode {
6060 Some(InsertTextMode::AS_IS) => None,
6061 _ => editor.autoindent_mode.clone(),
6062 };
6063 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6064 multi_buffer.edit(edits, auto_indent, cx);
6065 });
6066 }
6067 for (buffer, edits) in linked_edits {
6068 buffer.update(cx, |buffer, cx| {
6069 let snapshot = buffer.snapshot();
6070 let edits = edits
6071 .into_iter()
6072 .map(|(range, text)| {
6073 use text::ToPoint as TP;
6074 let end_point = TP::to_point(&range.end, &snapshot);
6075 let start_point = TP::to_point(&range.start, &snapshot);
6076 (start_point..end_point, text)
6077 })
6078 .sorted_by_key(|(range, _)| range.start);
6079 buffer.edit(edits, None, cx);
6080 })
6081 }
6082
6083 editor.refresh_edit_prediction(true, false, window, cx);
6084 });
6085 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6086
6087 let show_new_completions_on_confirm = completion
6088 .confirm
6089 .as_ref()
6090 .is_some_and(|confirm| confirm(intent, window, cx));
6091 if show_new_completions_on_confirm {
6092 self.open_or_update_completions_menu(None, None, false, window, cx);
6093 }
6094
6095 let provider = self.completion_provider.as_ref()?;
6096 drop(completion);
6097 let apply_edits = provider.apply_additional_edits_for_completion(
6098 buffer_handle,
6099 completions_menu.completions.clone(),
6100 candidate_id,
6101 true,
6102 cx,
6103 );
6104
6105 let editor_settings = EditorSettings::get_global(cx);
6106 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6107 // After the code completion is finished, users often want to know what signatures are needed.
6108 // so we should automatically call signature_help
6109 self.show_signature_help(&ShowSignatureHelp, window, cx);
6110 }
6111
6112 Some(cx.foreground_executor().spawn(async move {
6113 apply_edits.await?;
6114 Ok(())
6115 }))
6116 }
6117
6118 pub fn toggle_code_actions(
6119 &mut self,
6120 action: &ToggleCodeActions,
6121 window: &mut Window,
6122 cx: &mut Context<Self>,
6123 ) {
6124 let quick_launch = action.quick_launch;
6125 let mut context_menu = self.context_menu.borrow_mut();
6126 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6127 if code_actions.deployed_from == action.deployed_from {
6128 // Toggle if we're selecting the same one
6129 *context_menu = None;
6130 cx.notify();
6131 return;
6132 } else {
6133 // Otherwise, clear it and start a new one
6134 *context_menu = None;
6135 cx.notify();
6136 }
6137 }
6138 drop(context_menu);
6139 let snapshot = self.snapshot(window, cx);
6140 let deployed_from = action.deployed_from.clone();
6141 let action = action.clone();
6142 self.completion_tasks.clear();
6143 self.discard_edit_prediction(false, cx);
6144
6145 let multibuffer_point = match &action.deployed_from {
6146 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6147 DisplayPoint::new(*row, 0).to_point(&snapshot)
6148 }
6149 _ => self
6150 .selections
6151 .newest::<Point>(&snapshot.display_snapshot)
6152 .head(),
6153 };
6154 let Some((buffer, buffer_row)) = snapshot
6155 .buffer_snapshot()
6156 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6157 .and_then(|(buffer_snapshot, range)| {
6158 self.buffer()
6159 .read(cx)
6160 .buffer(buffer_snapshot.remote_id())
6161 .map(|buffer| (buffer, range.start.row))
6162 })
6163 else {
6164 return;
6165 };
6166 let buffer_id = buffer.read(cx).remote_id();
6167 let tasks = self
6168 .tasks
6169 .get(&(buffer_id, buffer_row))
6170 .map(|t| Arc::new(t.to_owned()));
6171
6172 if !self.focus_handle.is_focused(window) {
6173 return;
6174 }
6175 let project = self.project.clone();
6176
6177 let code_actions_task = match deployed_from {
6178 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6179 _ => self.code_actions(buffer_row, window, cx),
6180 };
6181
6182 let runnable_task = match deployed_from {
6183 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6184 _ => {
6185 let mut task_context_task = Task::ready(None);
6186 if let Some(tasks) = &tasks
6187 && let Some(project) = project
6188 {
6189 task_context_task =
6190 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6191 }
6192
6193 cx.spawn_in(window, {
6194 let buffer = buffer.clone();
6195 async move |editor, cx| {
6196 let task_context = task_context_task.await;
6197
6198 let resolved_tasks =
6199 tasks
6200 .zip(task_context.clone())
6201 .map(|(tasks, task_context)| ResolvedTasks {
6202 templates: tasks.resolve(&task_context).collect(),
6203 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6204 multibuffer_point.row,
6205 tasks.column,
6206 )),
6207 });
6208 let debug_scenarios = editor
6209 .update(cx, |editor, cx| {
6210 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6211 })?
6212 .await;
6213 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6214 }
6215 })
6216 }
6217 };
6218
6219 cx.spawn_in(window, async move |editor, cx| {
6220 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6221 let code_actions = code_actions_task.await;
6222 let spawn_straight_away = quick_launch
6223 && resolved_tasks
6224 .as_ref()
6225 .is_some_and(|tasks| tasks.templates.len() == 1)
6226 && code_actions
6227 .as_ref()
6228 .is_none_or(|actions| actions.is_empty())
6229 && debug_scenarios.is_empty();
6230
6231 editor.update_in(cx, |editor, window, cx| {
6232 crate::hover_popover::hide_hover(editor, cx);
6233 let actions = CodeActionContents::new(
6234 resolved_tasks,
6235 code_actions,
6236 debug_scenarios,
6237 task_context.unwrap_or_default(),
6238 );
6239
6240 // Don't show the menu if there are no actions available
6241 if actions.is_empty() {
6242 cx.notify();
6243 return Task::ready(Ok(()));
6244 }
6245
6246 *editor.context_menu.borrow_mut() =
6247 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6248 buffer,
6249 actions,
6250 selected_item: Default::default(),
6251 scroll_handle: UniformListScrollHandle::default(),
6252 deployed_from,
6253 }));
6254 cx.notify();
6255 if spawn_straight_away
6256 && let Some(task) = editor.confirm_code_action(
6257 &ConfirmCodeAction { item_ix: Some(0) },
6258 window,
6259 cx,
6260 )
6261 {
6262 return task;
6263 }
6264
6265 Task::ready(Ok(()))
6266 })
6267 })
6268 .detach_and_log_err(cx);
6269 }
6270
6271 fn debug_scenarios(
6272 &mut self,
6273 resolved_tasks: &Option<ResolvedTasks>,
6274 buffer: &Entity<Buffer>,
6275 cx: &mut App,
6276 ) -> Task<Vec<task::DebugScenario>> {
6277 maybe!({
6278 let project = self.project()?;
6279 let dap_store = project.read(cx).dap_store();
6280 let mut scenarios = vec![];
6281 let resolved_tasks = resolved_tasks.as_ref()?;
6282 let buffer = buffer.read(cx);
6283 let language = buffer.language()?;
6284 let file = buffer.file();
6285 let debug_adapter = language_settings(language.name().into(), file, cx)
6286 .debuggers
6287 .first()
6288 .map(SharedString::from)
6289 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6290
6291 dap_store.update(cx, |dap_store, cx| {
6292 for (_, task) in &resolved_tasks.templates {
6293 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6294 task.original_task().clone(),
6295 debug_adapter.clone().into(),
6296 task.display_label().to_owned().into(),
6297 cx,
6298 );
6299 scenarios.push(maybe_scenario);
6300 }
6301 });
6302 Some(cx.background_spawn(async move {
6303 futures::future::join_all(scenarios)
6304 .await
6305 .into_iter()
6306 .flatten()
6307 .collect::<Vec<_>>()
6308 }))
6309 })
6310 .unwrap_or_else(|| Task::ready(vec![]))
6311 }
6312
6313 fn code_actions(
6314 &mut self,
6315 buffer_row: u32,
6316 window: &mut Window,
6317 cx: &mut Context<Self>,
6318 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6319 let mut task = self.code_actions_task.take();
6320 cx.spawn_in(window, async move |editor, cx| {
6321 while let Some(prev_task) = task {
6322 prev_task.await.log_err();
6323 task = editor
6324 .update(cx, |this, _| this.code_actions_task.take())
6325 .ok()?;
6326 }
6327
6328 editor
6329 .update(cx, |editor, cx| {
6330 editor
6331 .available_code_actions
6332 .clone()
6333 .and_then(|(location, code_actions)| {
6334 let snapshot = location.buffer.read(cx).snapshot();
6335 let point_range = location.range.to_point(&snapshot);
6336 let point_range = point_range.start.row..=point_range.end.row;
6337 if point_range.contains(&buffer_row) {
6338 Some(code_actions)
6339 } else {
6340 None
6341 }
6342 })
6343 })
6344 .ok()
6345 .flatten()
6346 })
6347 }
6348
6349 pub fn confirm_code_action(
6350 &mut self,
6351 action: &ConfirmCodeAction,
6352 window: &mut Window,
6353 cx: &mut Context<Self>,
6354 ) -> Option<Task<Result<()>>> {
6355 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6356
6357 let actions_menu =
6358 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6359 menu
6360 } else {
6361 return None;
6362 };
6363
6364 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6365 let action = actions_menu.actions.get(action_ix)?;
6366 let title = action.label();
6367 let buffer = actions_menu.buffer;
6368 let workspace = self.workspace()?;
6369
6370 match action {
6371 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6372 workspace.update(cx, |workspace, cx| {
6373 workspace.schedule_resolved_task(
6374 task_source_kind,
6375 resolved_task,
6376 false,
6377 window,
6378 cx,
6379 );
6380
6381 Some(Task::ready(Ok(())))
6382 })
6383 }
6384 CodeActionsItem::CodeAction {
6385 excerpt_id,
6386 action,
6387 provider,
6388 } => {
6389 let apply_code_action =
6390 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6391 let workspace = workspace.downgrade();
6392 Some(cx.spawn_in(window, async move |editor, cx| {
6393 let project_transaction = apply_code_action.await?;
6394 Self::open_project_transaction(
6395 &editor,
6396 workspace,
6397 project_transaction,
6398 title,
6399 cx,
6400 )
6401 .await
6402 }))
6403 }
6404 CodeActionsItem::DebugScenario(scenario) => {
6405 let context = actions_menu.actions.context;
6406
6407 workspace.update(cx, |workspace, cx| {
6408 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6409 workspace.start_debug_session(
6410 scenario,
6411 context,
6412 Some(buffer),
6413 None,
6414 window,
6415 cx,
6416 );
6417 });
6418 Some(Task::ready(Ok(())))
6419 }
6420 }
6421 }
6422
6423 pub async fn open_project_transaction(
6424 editor: &WeakEntity<Editor>,
6425 workspace: WeakEntity<Workspace>,
6426 transaction: ProjectTransaction,
6427 title: String,
6428 cx: &mut AsyncWindowContext,
6429 ) -> Result<()> {
6430 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6431 cx.update(|_, cx| {
6432 entries.sort_unstable_by_key(|(buffer, _)| {
6433 buffer.read(cx).file().map(|f| f.path().clone())
6434 });
6435 })?;
6436 if entries.is_empty() {
6437 return Ok(());
6438 }
6439
6440 // If the project transaction's edits are all contained within this editor, then
6441 // avoid opening a new editor to display them.
6442
6443 if let [(buffer, transaction)] = &*entries {
6444 let excerpt = editor.update(cx, |editor, cx| {
6445 editor
6446 .buffer()
6447 .read(cx)
6448 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6449 })?;
6450 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6451 && excerpted_buffer == *buffer
6452 {
6453 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6454 let excerpt_range = excerpt_range.to_offset(buffer);
6455 buffer
6456 .edited_ranges_for_transaction::<usize>(transaction)
6457 .all(|range| {
6458 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6459 })
6460 })?;
6461
6462 if all_edits_within_excerpt {
6463 return Ok(());
6464 }
6465 }
6466 }
6467
6468 let mut ranges_to_highlight = Vec::new();
6469 let excerpt_buffer = cx.new(|cx| {
6470 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6471 for (buffer_handle, transaction) in &entries {
6472 let edited_ranges = buffer_handle
6473 .read(cx)
6474 .edited_ranges_for_transaction::<Point>(transaction)
6475 .collect::<Vec<_>>();
6476 let (ranges, _) = multibuffer.set_excerpts_for_path(
6477 PathKey::for_buffer(buffer_handle, cx),
6478 buffer_handle.clone(),
6479 edited_ranges,
6480 multibuffer_context_lines(cx),
6481 cx,
6482 );
6483
6484 ranges_to_highlight.extend(ranges);
6485 }
6486 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6487 multibuffer
6488 })?;
6489
6490 workspace.update_in(cx, |workspace, window, cx| {
6491 let project = workspace.project().clone();
6492 let editor =
6493 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6494 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6495 editor.update(cx, |editor, cx| {
6496 editor.highlight_background::<Self>(
6497 &ranges_to_highlight,
6498 |theme| theme.colors().editor_highlighted_line_background,
6499 cx,
6500 );
6501 });
6502 })?;
6503
6504 Ok(())
6505 }
6506
6507 pub fn clear_code_action_providers(&mut self) {
6508 self.code_action_providers.clear();
6509 self.available_code_actions.take();
6510 }
6511
6512 pub fn add_code_action_provider(
6513 &mut self,
6514 provider: Rc<dyn CodeActionProvider>,
6515 window: &mut Window,
6516 cx: &mut Context<Self>,
6517 ) {
6518 if self
6519 .code_action_providers
6520 .iter()
6521 .any(|existing_provider| existing_provider.id() == provider.id())
6522 {
6523 return;
6524 }
6525
6526 self.code_action_providers.push(provider);
6527 self.refresh_code_actions(window, cx);
6528 }
6529
6530 pub fn remove_code_action_provider(
6531 &mut self,
6532 id: Arc<str>,
6533 window: &mut Window,
6534 cx: &mut Context<Self>,
6535 ) {
6536 self.code_action_providers
6537 .retain(|provider| provider.id() != id);
6538 self.refresh_code_actions(window, cx);
6539 }
6540
6541 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6542 !self.code_action_providers.is_empty()
6543 && EditorSettings::get_global(cx).toolbar.code_actions
6544 }
6545
6546 pub fn has_available_code_actions(&self) -> bool {
6547 self.available_code_actions
6548 .as_ref()
6549 .is_some_and(|(_, actions)| !actions.is_empty())
6550 }
6551
6552 fn render_inline_code_actions(
6553 &self,
6554 icon_size: ui::IconSize,
6555 display_row: DisplayRow,
6556 is_active: bool,
6557 cx: &mut Context<Self>,
6558 ) -> AnyElement {
6559 let show_tooltip = !self.context_menu_visible();
6560 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6561 .icon_size(icon_size)
6562 .shape(ui::IconButtonShape::Square)
6563 .icon_color(ui::Color::Hidden)
6564 .toggle_state(is_active)
6565 .when(show_tooltip, |this| {
6566 this.tooltip({
6567 let focus_handle = self.focus_handle.clone();
6568 move |_window, cx| {
6569 Tooltip::for_action_in(
6570 "Toggle Code Actions",
6571 &ToggleCodeActions {
6572 deployed_from: None,
6573 quick_launch: false,
6574 },
6575 &focus_handle,
6576 cx,
6577 )
6578 }
6579 })
6580 })
6581 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6582 window.focus(&editor.focus_handle(cx));
6583 editor.toggle_code_actions(
6584 &crate::actions::ToggleCodeActions {
6585 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6586 display_row,
6587 )),
6588 quick_launch: false,
6589 },
6590 window,
6591 cx,
6592 );
6593 }))
6594 .into_any_element()
6595 }
6596
6597 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6598 &self.context_menu
6599 }
6600
6601 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6602 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6603 cx.background_executor()
6604 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6605 .await;
6606
6607 let (start_buffer, start, _, end, newest_selection) = this
6608 .update(cx, |this, cx| {
6609 let newest_selection = this.selections.newest_anchor().clone();
6610 if newest_selection.head().diff_base_anchor.is_some() {
6611 return None;
6612 }
6613 let display_snapshot = this.display_snapshot(cx);
6614 let newest_selection_adjusted =
6615 this.selections.newest_adjusted(&display_snapshot);
6616 let buffer = this.buffer.read(cx);
6617
6618 let (start_buffer, start) =
6619 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6620 let (end_buffer, end) =
6621 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6622
6623 Some((start_buffer, start, end_buffer, end, newest_selection))
6624 })?
6625 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6626 .context(
6627 "Expected selection to lie in a single buffer when refreshing code actions",
6628 )?;
6629 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6630 let providers = this.code_action_providers.clone();
6631 let tasks = this
6632 .code_action_providers
6633 .iter()
6634 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6635 .collect::<Vec<_>>();
6636 (providers, tasks)
6637 })?;
6638
6639 let mut actions = Vec::new();
6640 for (provider, provider_actions) in
6641 providers.into_iter().zip(future::join_all(tasks).await)
6642 {
6643 if let Some(provider_actions) = provider_actions.log_err() {
6644 actions.extend(provider_actions.into_iter().map(|action| {
6645 AvailableCodeAction {
6646 excerpt_id: newest_selection.start.excerpt_id,
6647 action,
6648 provider: provider.clone(),
6649 }
6650 }));
6651 }
6652 }
6653
6654 this.update(cx, |this, cx| {
6655 this.available_code_actions = if actions.is_empty() {
6656 None
6657 } else {
6658 Some((
6659 Location {
6660 buffer: start_buffer,
6661 range: start..end,
6662 },
6663 actions.into(),
6664 ))
6665 };
6666 cx.notify();
6667 })
6668 }));
6669 }
6670
6671 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6672 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6673 self.show_git_blame_inline = false;
6674
6675 self.show_git_blame_inline_delay_task =
6676 Some(cx.spawn_in(window, async move |this, cx| {
6677 cx.background_executor().timer(delay).await;
6678
6679 this.update(cx, |this, cx| {
6680 this.show_git_blame_inline = true;
6681 cx.notify();
6682 })
6683 .log_err();
6684 }));
6685 }
6686 }
6687
6688 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6689 let snapshot = self.snapshot(window, cx);
6690 let cursor = self
6691 .selections
6692 .newest::<Point>(&snapshot.display_snapshot)
6693 .head();
6694 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6695 else {
6696 return;
6697 };
6698
6699 let Some(blame) = self.blame.as_ref() else {
6700 return;
6701 };
6702
6703 let row_info = RowInfo {
6704 buffer_id: Some(buffer.remote_id()),
6705 buffer_row: Some(point.row),
6706 ..Default::default()
6707 };
6708 let Some((buffer, blame_entry)) = blame
6709 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6710 .flatten()
6711 else {
6712 return;
6713 };
6714
6715 let anchor = self.selections.newest_anchor().head();
6716 let position = self.to_pixel_point(anchor, &snapshot, window);
6717 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6718 self.show_blame_popover(
6719 buffer,
6720 &blame_entry,
6721 position + last_bounds.origin,
6722 true,
6723 cx,
6724 );
6725 };
6726 }
6727
6728 fn show_blame_popover(
6729 &mut self,
6730 buffer: BufferId,
6731 blame_entry: &BlameEntry,
6732 position: gpui::Point<Pixels>,
6733 ignore_timeout: bool,
6734 cx: &mut Context<Self>,
6735 ) {
6736 if let Some(state) = &mut self.inline_blame_popover {
6737 state.hide_task.take();
6738 } else {
6739 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6740 let blame_entry = blame_entry.clone();
6741 let show_task = cx.spawn(async move |editor, cx| {
6742 if !ignore_timeout {
6743 cx.background_executor()
6744 .timer(std::time::Duration::from_millis(blame_popover_delay))
6745 .await;
6746 }
6747 editor
6748 .update(cx, |editor, cx| {
6749 editor.inline_blame_popover_show_task.take();
6750 let Some(blame) = editor.blame.as_ref() else {
6751 return;
6752 };
6753 let blame = blame.read(cx);
6754 let details = blame.details_for_entry(buffer, &blame_entry);
6755 let markdown = cx.new(|cx| {
6756 Markdown::new(
6757 details
6758 .as_ref()
6759 .map(|message| message.message.clone())
6760 .unwrap_or_default(),
6761 None,
6762 None,
6763 cx,
6764 )
6765 });
6766 editor.inline_blame_popover = Some(InlineBlamePopover {
6767 position,
6768 hide_task: None,
6769 popover_bounds: None,
6770 popover_state: InlineBlamePopoverState {
6771 scroll_handle: ScrollHandle::new(),
6772 commit_message: details,
6773 markdown,
6774 },
6775 keyboard_grace: ignore_timeout,
6776 });
6777 cx.notify();
6778 })
6779 .ok();
6780 });
6781 self.inline_blame_popover_show_task = Some(show_task);
6782 }
6783 }
6784
6785 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6786 self.inline_blame_popover_show_task.take();
6787 if let Some(state) = &mut self.inline_blame_popover {
6788 let hide_task = cx.spawn(async move |editor, cx| {
6789 if !ignore_timeout {
6790 cx.background_executor()
6791 .timer(std::time::Duration::from_millis(100))
6792 .await;
6793 }
6794 editor
6795 .update(cx, |editor, cx| {
6796 editor.inline_blame_popover.take();
6797 cx.notify();
6798 })
6799 .ok();
6800 });
6801 state.hide_task = Some(hide_task);
6802 true
6803 } else {
6804 false
6805 }
6806 }
6807
6808 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6809 if self.pending_rename.is_some() {
6810 return None;
6811 }
6812
6813 let provider = self.semantics_provider.clone()?;
6814 let buffer = self.buffer.read(cx);
6815 let newest_selection = self.selections.newest_anchor().clone();
6816 let cursor_position = newest_selection.head();
6817 let (cursor_buffer, cursor_buffer_position) =
6818 buffer.text_anchor_for_position(cursor_position, cx)?;
6819 let (tail_buffer, tail_buffer_position) =
6820 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6821 if cursor_buffer != tail_buffer {
6822 return None;
6823 }
6824
6825 let snapshot = cursor_buffer.read(cx).snapshot();
6826 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6827 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6828 if start_word_range != end_word_range {
6829 self.document_highlights_task.take();
6830 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6831 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6832 return None;
6833 }
6834
6835 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6836 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6837 cx.background_executor()
6838 .timer(Duration::from_millis(debounce))
6839 .await;
6840
6841 let highlights = if let Some(highlights) = cx
6842 .update(|cx| {
6843 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6844 })
6845 .ok()
6846 .flatten()
6847 {
6848 highlights.await.log_err()
6849 } else {
6850 None
6851 };
6852
6853 if let Some(highlights) = highlights {
6854 this.update(cx, |this, cx| {
6855 if this.pending_rename.is_some() {
6856 return;
6857 }
6858
6859 let buffer = this.buffer.read(cx);
6860 if buffer
6861 .text_anchor_for_position(cursor_position, cx)
6862 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6863 {
6864 return;
6865 }
6866
6867 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6868 let mut write_ranges = Vec::new();
6869 let mut read_ranges = Vec::new();
6870 for highlight in highlights {
6871 let buffer_id = cursor_buffer.read(cx).remote_id();
6872 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6873 {
6874 let start = highlight
6875 .range
6876 .start
6877 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6878 let end = highlight
6879 .range
6880 .end
6881 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6882 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6883 continue;
6884 }
6885
6886 let range =
6887 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6888 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6889 write_ranges.push(range);
6890 } else {
6891 read_ranges.push(range);
6892 }
6893 }
6894 }
6895
6896 this.highlight_background::<DocumentHighlightRead>(
6897 &read_ranges,
6898 |theme| theme.colors().editor_document_highlight_read_background,
6899 cx,
6900 );
6901 this.highlight_background::<DocumentHighlightWrite>(
6902 &write_ranges,
6903 |theme| theme.colors().editor_document_highlight_write_background,
6904 cx,
6905 );
6906 cx.notify();
6907 })
6908 .log_err();
6909 }
6910 }));
6911 None
6912 }
6913
6914 fn prepare_highlight_query_from_selection(
6915 &mut self,
6916 window: &Window,
6917 cx: &mut Context<Editor>,
6918 ) -> Option<(String, Range<Anchor>)> {
6919 if matches!(self.mode, EditorMode::SingleLine) {
6920 return None;
6921 }
6922 if !EditorSettings::get_global(cx).selection_highlight {
6923 return None;
6924 }
6925 if self.selections.count() != 1 || self.selections.line_mode() {
6926 return None;
6927 }
6928 let snapshot = self.snapshot(window, cx);
6929 let selection = self.selections.newest::<Point>(&snapshot);
6930 // If the selection spans multiple rows OR it is empty
6931 if selection.start.row != selection.end.row
6932 || selection.start.column == selection.end.column
6933 {
6934 return None;
6935 }
6936 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6937 let query = snapshot
6938 .buffer_snapshot()
6939 .text_for_range(selection_anchor_range.clone())
6940 .collect::<String>();
6941 if query.trim().is_empty() {
6942 return None;
6943 }
6944 Some((query, selection_anchor_range))
6945 }
6946
6947 fn update_selection_occurrence_highlights(
6948 &mut self,
6949 query_text: String,
6950 query_range: Range<Anchor>,
6951 multi_buffer_range_to_query: Range<Point>,
6952 use_debounce: bool,
6953 window: &mut Window,
6954 cx: &mut Context<Editor>,
6955 ) -> Task<()> {
6956 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6957 cx.spawn_in(window, async move |editor, cx| {
6958 if use_debounce {
6959 cx.background_executor()
6960 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6961 .await;
6962 }
6963 let match_task = cx.background_spawn(async move {
6964 let buffer_ranges = multi_buffer_snapshot
6965 .range_to_buffer_ranges(multi_buffer_range_to_query)
6966 .into_iter()
6967 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6968 let mut match_ranges = Vec::new();
6969 let Ok(regex) = project::search::SearchQuery::text(
6970 query_text.clone(),
6971 false,
6972 false,
6973 false,
6974 Default::default(),
6975 Default::default(),
6976 false,
6977 None,
6978 ) else {
6979 return Vec::default();
6980 };
6981 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6982 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6983 match_ranges.extend(
6984 regex
6985 .search(buffer_snapshot, Some(search_range.clone()))
6986 .await
6987 .into_iter()
6988 .filter_map(|match_range| {
6989 let match_start = buffer_snapshot
6990 .anchor_after(search_range.start + match_range.start);
6991 let match_end = buffer_snapshot
6992 .anchor_before(search_range.start + match_range.end);
6993 let match_anchor_range = Anchor::range_in_buffer(
6994 excerpt_id,
6995 buffer_snapshot.remote_id(),
6996 match_start..match_end,
6997 );
6998 (match_anchor_range != query_range).then_some(match_anchor_range)
6999 }),
7000 );
7001 }
7002 match_ranges
7003 });
7004 let match_ranges = match_task.await;
7005 editor
7006 .update_in(cx, |editor, _, cx| {
7007 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7008 if !match_ranges.is_empty() {
7009 editor.highlight_background::<SelectedTextHighlight>(
7010 &match_ranges,
7011 |theme| theme.colors().editor_document_highlight_bracket_background,
7012 cx,
7013 )
7014 }
7015 })
7016 .log_err();
7017 })
7018 }
7019
7020 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7021 struct NewlineFold;
7022 let type_id = std::any::TypeId::of::<NewlineFold>();
7023 if !self.mode.is_single_line() {
7024 return;
7025 }
7026 let snapshot = self.snapshot(window, cx);
7027 if snapshot.buffer_snapshot().max_point().row == 0 {
7028 return;
7029 }
7030 let task = cx.background_spawn(async move {
7031 let new_newlines = snapshot
7032 .buffer_chars_at(0)
7033 .filter_map(|(c, i)| {
7034 if c == '\n' {
7035 Some(
7036 snapshot.buffer_snapshot().anchor_after(i)
7037 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7038 )
7039 } else {
7040 None
7041 }
7042 })
7043 .collect::<Vec<_>>();
7044 let existing_newlines = snapshot
7045 .folds_in_range(0..snapshot.buffer_snapshot().len())
7046 .filter_map(|fold| {
7047 if fold.placeholder.type_tag == Some(type_id) {
7048 Some(fold.range.start..fold.range.end)
7049 } else {
7050 None
7051 }
7052 })
7053 .collect::<Vec<_>>();
7054
7055 (new_newlines, existing_newlines)
7056 });
7057 self.folding_newlines = cx.spawn(async move |this, cx| {
7058 let (new_newlines, existing_newlines) = task.await;
7059 if new_newlines == existing_newlines {
7060 return;
7061 }
7062 let placeholder = FoldPlaceholder {
7063 render: Arc::new(move |_, _, cx| {
7064 div()
7065 .bg(cx.theme().status().hint_background)
7066 .border_b_1()
7067 .size_full()
7068 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7069 .border_color(cx.theme().status().hint)
7070 .child("\\n")
7071 .into_any()
7072 }),
7073 constrain_width: false,
7074 merge_adjacent: false,
7075 type_tag: Some(type_id),
7076 };
7077 let creases = new_newlines
7078 .into_iter()
7079 .map(|range| Crease::simple(range, placeholder.clone()))
7080 .collect();
7081 this.update(cx, |this, cx| {
7082 this.display_map.update(cx, |display_map, cx| {
7083 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7084 display_map.fold(creases, cx);
7085 });
7086 })
7087 .ok();
7088 });
7089 }
7090
7091 fn refresh_selected_text_highlights(
7092 &mut self,
7093 on_buffer_edit: bool,
7094 window: &mut Window,
7095 cx: &mut Context<Editor>,
7096 ) {
7097 let Some((query_text, query_range)) =
7098 self.prepare_highlight_query_from_selection(window, cx)
7099 else {
7100 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7101 self.quick_selection_highlight_task.take();
7102 self.debounced_selection_highlight_task.take();
7103 return;
7104 };
7105 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7106 if on_buffer_edit
7107 || self
7108 .quick_selection_highlight_task
7109 .as_ref()
7110 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7111 {
7112 let multi_buffer_visible_start = self
7113 .scroll_manager
7114 .anchor()
7115 .anchor
7116 .to_point(&multi_buffer_snapshot);
7117 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7118 multi_buffer_visible_start
7119 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7120 Bias::Left,
7121 );
7122 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7123 self.quick_selection_highlight_task = Some((
7124 query_range.clone(),
7125 self.update_selection_occurrence_highlights(
7126 query_text.clone(),
7127 query_range.clone(),
7128 multi_buffer_visible_range,
7129 false,
7130 window,
7131 cx,
7132 ),
7133 ));
7134 }
7135 if on_buffer_edit
7136 || self
7137 .debounced_selection_highlight_task
7138 .as_ref()
7139 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7140 {
7141 let multi_buffer_start = multi_buffer_snapshot
7142 .anchor_before(0)
7143 .to_point(&multi_buffer_snapshot);
7144 let multi_buffer_end = multi_buffer_snapshot
7145 .anchor_after(multi_buffer_snapshot.len())
7146 .to_point(&multi_buffer_snapshot);
7147 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7148 self.debounced_selection_highlight_task = Some((
7149 query_range.clone(),
7150 self.update_selection_occurrence_highlights(
7151 query_text,
7152 query_range,
7153 multi_buffer_full_range,
7154 true,
7155 window,
7156 cx,
7157 ),
7158 ));
7159 }
7160 }
7161
7162 pub fn refresh_edit_prediction(
7163 &mut self,
7164 debounce: bool,
7165 user_requested: bool,
7166 window: &mut Window,
7167 cx: &mut Context<Self>,
7168 ) -> Option<()> {
7169 if DisableAiSettings::get_global(cx).disable_ai {
7170 return None;
7171 }
7172
7173 let provider = self.edit_prediction_provider()?;
7174 let cursor = self.selections.newest_anchor().head();
7175 let (buffer, cursor_buffer_position) =
7176 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7177
7178 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7179 self.discard_edit_prediction(false, cx);
7180 return None;
7181 }
7182
7183 self.update_visible_edit_prediction(window, cx);
7184
7185 if !user_requested
7186 && (!self.should_show_edit_predictions()
7187 || !self.is_focused(window)
7188 || buffer.read(cx).is_empty())
7189 {
7190 self.discard_edit_prediction(false, cx);
7191 return None;
7192 }
7193
7194 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7195 Some(())
7196 }
7197
7198 fn show_edit_predictions_in_menu(&self) -> bool {
7199 match self.edit_prediction_settings {
7200 EditPredictionSettings::Disabled => false,
7201 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7202 }
7203 }
7204
7205 pub fn edit_predictions_enabled(&self) -> bool {
7206 match self.edit_prediction_settings {
7207 EditPredictionSettings::Disabled => false,
7208 EditPredictionSettings::Enabled { .. } => true,
7209 }
7210 }
7211
7212 fn edit_prediction_requires_modifier(&self) -> bool {
7213 match self.edit_prediction_settings {
7214 EditPredictionSettings::Disabled => false,
7215 EditPredictionSettings::Enabled {
7216 preview_requires_modifier,
7217 ..
7218 } => preview_requires_modifier,
7219 }
7220 }
7221
7222 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7223 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7224 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7225 self.discard_edit_prediction(false, cx);
7226 } else {
7227 let selection = self.selections.newest_anchor();
7228 let cursor = selection.head();
7229
7230 if let Some((buffer, cursor_buffer_position)) =
7231 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7232 {
7233 self.edit_prediction_settings =
7234 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7235 }
7236 }
7237 }
7238
7239 fn edit_prediction_settings_at_position(
7240 &self,
7241 buffer: &Entity<Buffer>,
7242 buffer_position: language::Anchor,
7243 cx: &App,
7244 ) -> EditPredictionSettings {
7245 if !self.mode.is_full()
7246 || !self.show_edit_predictions_override.unwrap_or(true)
7247 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7248 {
7249 return EditPredictionSettings::Disabled;
7250 }
7251
7252 let buffer = buffer.read(cx);
7253
7254 let file = buffer.file();
7255
7256 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7257 return EditPredictionSettings::Disabled;
7258 };
7259
7260 let by_provider = matches!(
7261 self.menu_edit_predictions_policy,
7262 MenuEditPredictionsPolicy::ByProvider
7263 );
7264
7265 let show_in_menu = by_provider
7266 && self
7267 .edit_prediction_provider
7268 .as_ref()
7269 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7270
7271 let preview_requires_modifier =
7272 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7273
7274 EditPredictionSettings::Enabled {
7275 show_in_menu,
7276 preview_requires_modifier,
7277 }
7278 }
7279
7280 fn should_show_edit_predictions(&self) -> bool {
7281 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7282 }
7283
7284 pub fn edit_prediction_preview_is_active(&self) -> bool {
7285 matches!(
7286 self.edit_prediction_preview,
7287 EditPredictionPreview::Active { .. }
7288 )
7289 }
7290
7291 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7292 let cursor = self.selections.newest_anchor().head();
7293 if let Some((buffer, cursor_position)) =
7294 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7295 {
7296 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7297 } else {
7298 false
7299 }
7300 }
7301
7302 pub fn supports_minimap(&self, cx: &App) -> bool {
7303 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7304 }
7305
7306 fn edit_predictions_enabled_in_buffer(
7307 &self,
7308 buffer: &Entity<Buffer>,
7309 buffer_position: language::Anchor,
7310 cx: &App,
7311 ) -> bool {
7312 maybe!({
7313 if self.read_only(cx) {
7314 return Some(false);
7315 }
7316 let provider = self.edit_prediction_provider()?;
7317 if !provider.is_enabled(buffer, buffer_position, cx) {
7318 return Some(false);
7319 }
7320 let buffer = buffer.read(cx);
7321 let Some(file) = buffer.file() else {
7322 return Some(true);
7323 };
7324 let settings = all_language_settings(Some(file), cx);
7325 Some(settings.edit_predictions_enabled_for_file(file, cx))
7326 })
7327 .unwrap_or(false)
7328 }
7329
7330 fn cycle_edit_prediction(
7331 &mut self,
7332 direction: Direction,
7333 window: &mut Window,
7334 cx: &mut Context<Self>,
7335 ) -> Option<()> {
7336 let provider = self.edit_prediction_provider()?;
7337 let cursor = self.selections.newest_anchor().head();
7338 let (buffer, cursor_buffer_position) =
7339 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7340 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7341 return None;
7342 }
7343
7344 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7345 self.update_visible_edit_prediction(window, cx);
7346
7347 Some(())
7348 }
7349
7350 pub fn show_edit_prediction(
7351 &mut self,
7352 _: &ShowEditPrediction,
7353 window: &mut Window,
7354 cx: &mut Context<Self>,
7355 ) {
7356 if !self.has_active_edit_prediction() {
7357 self.refresh_edit_prediction(false, true, window, cx);
7358 return;
7359 }
7360
7361 self.update_visible_edit_prediction(window, cx);
7362 }
7363
7364 pub fn display_cursor_names(
7365 &mut self,
7366 _: &DisplayCursorNames,
7367 window: &mut Window,
7368 cx: &mut Context<Self>,
7369 ) {
7370 self.show_cursor_names(window, cx);
7371 }
7372
7373 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7374 self.show_cursor_names = true;
7375 cx.notify();
7376 cx.spawn_in(window, async move |this, cx| {
7377 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7378 this.update(cx, |this, cx| {
7379 this.show_cursor_names = false;
7380 cx.notify()
7381 })
7382 .ok()
7383 })
7384 .detach();
7385 }
7386
7387 pub fn next_edit_prediction(
7388 &mut self,
7389 _: &NextEditPrediction,
7390 window: &mut Window,
7391 cx: &mut Context<Self>,
7392 ) {
7393 if self.has_active_edit_prediction() {
7394 self.cycle_edit_prediction(Direction::Next, window, cx);
7395 } else {
7396 let is_copilot_disabled = self
7397 .refresh_edit_prediction(false, true, window, cx)
7398 .is_none();
7399 if is_copilot_disabled {
7400 cx.propagate();
7401 }
7402 }
7403 }
7404
7405 pub fn previous_edit_prediction(
7406 &mut self,
7407 _: &PreviousEditPrediction,
7408 window: &mut Window,
7409 cx: &mut Context<Self>,
7410 ) {
7411 if self.has_active_edit_prediction() {
7412 self.cycle_edit_prediction(Direction::Prev, window, cx);
7413 } else {
7414 let is_copilot_disabled = self
7415 .refresh_edit_prediction(false, true, window, cx)
7416 .is_none();
7417 if is_copilot_disabled {
7418 cx.propagate();
7419 }
7420 }
7421 }
7422
7423 pub fn accept_edit_prediction(
7424 &mut self,
7425 _: &AcceptEditPrediction,
7426 window: &mut Window,
7427 cx: &mut Context<Self>,
7428 ) {
7429 if self.show_edit_predictions_in_menu() {
7430 self.hide_context_menu(window, cx);
7431 }
7432
7433 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7434 return;
7435 };
7436
7437 match &active_edit_prediction.completion {
7438 EditPrediction::MoveWithin { target, .. } => {
7439 let target = *target;
7440
7441 if let Some(position_map) = &self.last_position_map {
7442 if position_map
7443 .visible_row_range
7444 .contains(&target.to_display_point(&position_map.snapshot).row())
7445 || !self.edit_prediction_requires_modifier()
7446 {
7447 self.unfold_ranges(&[target..target], true, false, cx);
7448 // Note that this is also done in vim's handler of the Tab action.
7449 self.change_selections(
7450 SelectionEffects::scroll(Autoscroll::newest()),
7451 window,
7452 cx,
7453 |selections| {
7454 selections.select_anchor_ranges([target..target]);
7455 },
7456 );
7457 self.clear_row_highlights::<EditPredictionPreview>();
7458
7459 self.edit_prediction_preview
7460 .set_previous_scroll_position(None);
7461 } else {
7462 self.edit_prediction_preview
7463 .set_previous_scroll_position(Some(
7464 position_map.snapshot.scroll_anchor,
7465 ));
7466
7467 self.highlight_rows::<EditPredictionPreview>(
7468 target..target,
7469 cx.theme().colors().editor_highlighted_line_background,
7470 RowHighlightOptions {
7471 autoscroll: true,
7472 ..Default::default()
7473 },
7474 cx,
7475 );
7476 self.request_autoscroll(Autoscroll::fit(), cx);
7477 }
7478 }
7479 }
7480 EditPrediction::MoveOutside { snapshot, target } => {
7481 if let Some(workspace) = self.workspace() {
7482 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7483 .detach_and_log_err(cx);
7484 }
7485 }
7486 EditPrediction::Edit { edits, .. } => {
7487 self.report_edit_prediction_event(
7488 active_edit_prediction.completion_id.clone(),
7489 true,
7490 cx,
7491 );
7492
7493 if let Some(provider) = self.edit_prediction_provider() {
7494 provider.accept(cx);
7495 }
7496
7497 // Store the transaction ID and selections before applying the edit
7498 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7499
7500 let snapshot = self.buffer.read(cx).snapshot(cx);
7501 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7502
7503 self.buffer.update(cx, |buffer, cx| {
7504 buffer.edit(edits.iter().cloned(), None, cx)
7505 });
7506
7507 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7508 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7509 });
7510
7511 let selections = self.selections.disjoint_anchors_arc();
7512 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7513 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7514 if has_new_transaction {
7515 self.selection_history
7516 .insert_transaction(transaction_id_now, selections);
7517 }
7518 }
7519
7520 self.update_visible_edit_prediction(window, cx);
7521 if self.active_edit_prediction.is_none() {
7522 self.refresh_edit_prediction(true, true, window, cx);
7523 }
7524
7525 cx.notify();
7526 }
7527 }
7528
7529 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7530 }
7531
7532 pub fn accept_partial_edit_prediction(
7533 &mut self,
7534 _: &AcceptPartialEditPrediction,
7535 window: &mut Window,
7536 cx: &mut Context<Self>,
7537 ) {
7538 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7539 return;
7540 };
7541 if self.selections.count() != 1 {
7542 return;
7543 }
7544
7545 match &active_edit_prediction.completion {
7546 EditPrediction::MoveWithin { target, .. } => {
7547 let target = *target;
7548 self.change_selections(
7549 SelectionEffects::scroll(Autoscroll::newest()),
7550 window,
7551 cx,
7552 |selections| {
7553 selections.select_anchor_ranges([target..target]);
7554 },
7555 );
7556 }
7557 EditPrediction::MoveOutside { snapshot, target } => {
7558 if let Some(workspace) = self.workspace() {
7559 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7560 .detach_and_log_err(cx);
7561 }
7562 }
7563 EditPrediction::Edit { edits, .. } => {
7564 self.report_edit_prediction_event(
7565 active_edit_prediction.completion_id.clone(),
7566 true,
7567 cx,
7568 );
7569
7570 // Find an insertion that starts at the cursor position.
7571 let snapshot = self.buffer.read(cx).snapshot(cx);
7572 let cursor_offset = self
7573 .selections
7574 .newest::<usize>(&self.display_snapshot(cx))
7575 .head();
7576 let insertion = edits.iter().find_map(|(range, text)| {
7577 let range = range.to_offset(&snapshot);
7578 if range.is_empty() && range.start == cursor_offset {
7579 Some(text)
7580 } else {
7581 None
7582 }
7583 });
7584
7585 if let Some(text) = insertion {
7586 let mut partial_completion = text
7587 .chars()
7588 .by_ref()
7589 .take_while(|c| c.is_alphabetic())
7590 .collect::<String>();
7591 if partial_completion.is_empty() {
7592 partial_completion = text
7593 .chars()
7594 .by_ref()
7595 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7596 .collect::<String>();
7597 }
7598
7599 cx.emit(EditorEvent::InputHandled {
7600 utf16_range_to_replace: None,
7601 text: partial_completion.clone().into(),
7602 });
7603
7604 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7605
7606 self.refresh_edit_prediction(true, true, window, cx);
7607 cx.notify();
7608 } else {
7609 self.accept_edit_prediction(&Default::default(), window, cx);
7610 }
7611 }
7612 }
7613 }
7614
7615 fn discard_edit_prediction(
7616 &mut self,
7617 should_report_edit_prediction_event: bool,
7618 cx: &mut Context<Self>,
7619 ) -> bool {
7620 if should_report_edit_prediction_event {
7621 let completion_id = self
7622 .active_edit_prediction
7623 .as_ref()
7624 .and_then(|active_completion| active_completion.completion_id.clone());
7625
7626 self.report_edit_prediction_event(completion_id, false, cx);
7627 }
7628
7629 if let Some(provider) = self.edit_prediction_provider() {
7630 provider.discard(cx);
7631 }
7632
7633 self.take_active_edit_prediction(cx)
7634 }
7635
7636 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7637 let Some(provider) = self.edit_prediction_provider() else {
7638 return;
7639 };
7640
7641 let Some((_, buffer, _)) = self
7642 .buffer
7643 .read(cx)
7644 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7645 else {
7646 return;
7647 };
7648
7649 let extension = buffer
7650 .read(cx)
7651 .file()
7652 .and_then(|file| Some(file.path().extension()?.to_string()));
7653
7654 let event_type = match accepted {
7655 true => "Edit Prediction Accepted",
7656 false => "Edit Prediction Discarded",
7657 };
7658 telemetry::event!(
7659 event_type,
7660 provider = provider.name(),
7661 prediction_id = id,
7662 suggestion_accepted = accepted,
7663 file_extension = extension,
7664 );
7665 }
7666
7667 fn open_editor_at_anchor(
7668 snapshot: &language::BufferSnapshot,
7669 target: language::Anchor,
7670 workspace: &Entity<Workspace>,
7671 window: &mut Window,
7672 cx: &mut App,
7673 ) -> Task<Result<()>> {
7674 workspace.update(cx, |workspace, cx| {
7675 let path = snapshot.file().map(|file| file.full_path(cx));
7676 let Some(path) =
7677 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7678 else {
7679 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7680 };
7681 let target = text::ToPoint::to_point(&target, snapshot);
7682 let item = workspace.open_path(path, None, true, window, cx);
7683 window.spawn(cx, async move |cx| {
7684 let Some(editor) = item.await?.downcast::<Editor>() else {
7685 return Ok(());
7686 };
7687 editor
7688 .update_in(cx, |editor, window, cx| {
7689 editor.go_to_singleton_buffer_point(target, window, cx);
7690 })
7691 .ok();
7692 anyhow::Ok(())
7693 })
7694 })
7695 }
7696
7697 pub fn has_active_edit_prediction(&self) -> bool {
7698 self.active_edit_prediction.is_some()
7699 }
7700
7701 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7702 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7703 return false;
7704 };
7705
7706 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7707 self.clear_highlights::<EditPredictionHighlight>(cx);
7708 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7709 true
7710 }
7711
7712 /// Returns true when we're displaying the edit prediction popover below the cursor
7713 /// like we are not previewing and the LSP autocomplete menu is visible
7714 /// or we are in `when_holding_modifier` mode.
7715 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7716 if self.edit_prediction_preview_is_active()
7717 || !self.show_edit_predictions_in_menu()
7718 || !self.edit_predictions_enabled()
7719 {
7720 return false;
7721 }
7722
7723 if self.has_visible_completions_menu() {
7724 return true;
7725 }
7726
7727 has_completion && self.edit_prediction_requires_modifier()
7728 }
7729
7730 fn handle_modifiers_changed(
7731 &mut self,
7732 modifiers: Modifiers,
7733 position_map: &PositionMap,
7734 window: &mut Window,
7735 cx: &mut Context<Self>,
7736 ) {
7737 // Ensure that the edit prediction preview is updated, even when not
7738 // enabled, if there's an active edit prediction preview.
7739 if self.show_edit_predictions_in_menu()
7740 || matches!(
7741 self.edit_prediction_preview,
7742 EditPredictionPreview::Active { .. }
7743 )
7744 {
7745 self.update_edit_prediction_preview(&modifiers, window, cx);
7746 }
7747
7748 self.update_selection_mode(&modifiers, position_map, window, cx);
7749
7750 let mouse_position = window.mouse_position();
7751 if !position_map.text_hitbox.is_hovered(window) {
7752 return;
7753 }
7754
7755 self.update_hovered_link(
7756 position_map.point_for_position(mouse_position),
7757 &position_map.snapshot,
7758 modifiers,
7759 window,
7760 cx,
7761 )
7762 }
7763
7764 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7765 match EditorSettings::get_global(cx).multi_cursor_modifier {
7766 MultiCursorModifier::Alt => modifiers.secondary(),
7767 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7768 }
7769 }
7770
7771 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7772 match EditorSettings::get_global(cx).multi_cursor_modifier {
7773 MultiCursorModifier::Alt => modifiers.alt,
7774 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7775 }
7776 }
7777
7778 fn columnar_selection_mode(
7779 modifiers: &Modifiers,
7780 cx: &mut Context<Self>,
7781 ) -> Option<ColumnarMode> {
7782 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7783 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7784 Some(ColumnarMode::FromMouse)
7785 } else if Self::is_alt_pressed(modifiers, cx) {
7786 Some(ColumnarMode::FromSelection)
7787 } else {
7788 None
7789 }
7790 } else {
7791 None
7792 }
7793 }
7794
7795 fn update_selection_mode(
7796 &mut self,
7797 modifiers: &Modifiers,
7798 position_map: &PositionMap,
7799 window: &mut Window,
7800 cx: &mut Context<Self>,
7801 ) {
7802 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7803 return;
7804 };
7805 if self.selections.pending_anchor().is_none() {
7806 return;
7807 }
7808
7809 let mouse_position = window.mouse_position();
7810 let point_for_position = position_map.point_for_position(mouse_position);
7811 let position = point_for_position.previous_valid;
7812
7813 self.select(
7814 SelectPhase::BeginColumnar {
7815 position,
7816 reset: false,
7817 mode,
7818 goal_column: point_for_position.exact_unclipped.column(),
7819 },
7820 window,
7821 cx,
7822 );
7823 }
7824
7825 fn update_edit_prediction_preview(
7826 &mut self,
7827 modifiers: &Modifiers,
7828 window: &mut Window,
7829 cx: &mut Context<Self>,
7830 ) {
7831 let mut modifiers_held = false;
7832 if let Some(accept_keystroke) = self
7833 .accept_edit_prediction_keybind(false, window, cx)
7834 .keystroke()
7835 {
7836 modifiers_held = modifiers_held
7837 || (accept_keystroke.modifiers() == modifiers
7838 && accept_keystroke.modifiers().modified());
7839 };
7840 if let Some(accept_partial_keystroke) = self
7841 .accept_edit_prediction_keybind(true, window, cx)
7842 .keystroke()
7843 {
7844 modifiers_held = modifiers_held
7845 || (accept_partial_keystroke.modifiers() == modifiers
7846 && accept_partial_keystroke.modifiers().modified());
7847 }
7848
7849 if modifiers_held {
7850 if matches!(
7851 self.edit_prediction_preview,
7852 EditPredictionPreview::Inactive { .. }
7853 ) {
7854 self.edit_prediction_preview = EditPredictionPreview::Active {
7855 previous_scroll_position: None,
7856 since: Instant::now(),
7857 };
7858
7859 self.update_visible_edit_prediction(window, cx);
7860 cx.notify();
7861 }
7862 } else if let EditPredictionPreview::Active {
7863 previous_scroll_position,
7864 since,
7865 } = self.edit_prediction_preview
7866 {
7867 if let (Some(previous_scroll_position), Some(position_map)) =
7868 (previous_scroll_position, self.last_position_map.as_ref())
7869 {
7870 self.set_scroll_position(
7871 previous_scroll_position
7872 .scroll_position(&position_map.snapshot.display_snapshot),
7873 window,
7874 cx,
7875 );
7876 }
7877
7878 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7879 released_too_fast: since.elapsed() < Duration::from_millis(200),
7880 };
7881 self.clear_row_highlights::<EditPredictionPreview>();
7882 self.update_visible_edit_prediction(window, cx);
7883 cx.notify();
7884 }
7885 }
7886
7887 fn update_visible_edit_prediction(
7888 &mut self,
7889 _window: &mut Window,
7890 cx: &mut Context<Self>,
7891 ) -> Option<()> {
7892 if DisableAiSettings::get_global(cx).disable_ai {
7893 return None;
7894 }
7895
7896 if self.ime_transaction.is_some() {
7897 self.discard_edit_prediction(false, cx);
7898 return None;
7899 }
7900
7901 let selection = self.selections.newest_anchor();
7902 let cursor = selection.head();
7903 let multibuffer = self.buffer.read(cx).snapshot(cx);
7904 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7905 let excerpt_id = cursor.excerpt_id;
7906
7907 let show_in_menu = self.show_edit_predictions_in_menu();
7908 let completions_menu_has_precedence = !show_in_menu
7909 && (self.context_menu.borrow().is_some()
7910 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7911
7912 if completions_menu_has_precedence
7913 || !offset_selection.is_empty()
7914 || self
7915 .active_edit_prediction
7916 .as_ref()
7917 .is_some_and(|completion| {
7918 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7919 return false;
7920 };
7921 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7922 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7923 !invalidation_range.contains(&offset_selection.head())
7924 })
7925 {
7926 self.discard_edit_prediction(false, cx);
7927 return None;
7928 }
7929
7930 self.take_active_edit_prediction(cx);
7931 let Some(provider) = self.edit_prediction_provider() else {
7932 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7933 return None;
7934 };
7935
7936 let (buffer, cursor_buffer_position) =
7937 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7938
7939 self.edit_prediction_settings =
7940 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7941
7942 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7943
7944 if self.edit_prediction_indent_conflict {
7945 let cursor_point = cursor.to_point(&multibuffer);
7946
7947 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7948
7949 if let Some((_, indent)) = indents.iter().next()
7950 && indent.len == cursor_point.column
7951 {
7952 self.edit_prediction_indent_conflict = false;
7953 }
7954 }
7955
7956 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7957
7958 let (completion_id, edits, edit_preview) = match edit_prediction {
7959 edit_prediction::EditPrediction::Local {
7960 id,
7961 edits,
7962 edit_preview,
7963 } => (id, edits, edit_preview),
7964 edit_prediction::EditPrediction::Jump {
7965 id,
7966 snapshot,
7967 target,
7968 } => {
7969 self.stale_edit_prediction_in_menu = None;
7970 self.active_edit_prediction = Some(EditPredictionState {
7971 inlay_ids: vec![],
7972 completion: EditPrediction::MoveOutside { snapshot, target },
7973 completion_id: id,
7974 invalidation_range: None,
7975 });
7976 cx.notify();
7977 return Some(());
7978 }
7979 };
7980
7981 let edits = edits
7982 .into_iter()
7983 .flat_map(|(range, new_text)| {
7984 Some((
7985 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7986 new_text,
7987 ))
7988 })
7989 .collect::<Vec<_>>();
7990 if edits.is_empty() {
7991 return None;
7992 }
7993
7994 let first_edit_start = edits.first().unwrap().0.start;
7995 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7996 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7997
7998 let last_edit_end = edits.last().unwrap().0.end;
7999 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8000 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8001
8002 let cursor_row = cursor.to_point(&multibuffer).row;
8003
8004 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8005
8006 let mut inlay_ids = Vec::new();
8007 let invalidation_row_range;
8008 let move_invalidation_row_range = if cursor_row < edit_start_row {
8009 Some(cursor_row..edit_end_row)
8010 } else if cursor_row > edit_end_row {
8011 Some(edit_start_row..cursor_row)
8012 } else {
8013 None
8014 };
8015 let supports_jump = self
8016 .edit_prediction_provider
8017 .as_ref()
8018 .map(|provider| provider.provider.supports_jump_to_edit())
8019 .unwrap_or(true);
8020
8021 let is_move = supports_jump
8022 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8023 let completion = if is_move {
8024 invalidation_row_range =
8025 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8026 let target = first_edit_start;
8027 EditPrediction::MoveWithin { target, snapshot }
8028 } else {
8029 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8030 && !self.edit_predictions_hidden_for_vim_mode;
8031
8032 if show_completions_in_buffer {
8033 if edits
8034 .iter()
8035 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8036 {
8037 let mut inlays = Vec::new();
8038 for (range, new_text) in &edits {
8039 let inlay = Inlay::edit_prediction(
8040 post_inc(&mut self.next_inlay_id),
8041 range.start,
8042 new_text.as_ref(),
8043 );
8044 inlay_ids.push(inlay.id);
8045 inlays.push(inlay);
8046 }
8047
8048 self.splice_inlays(&[], inlays, cx);
8049 } else {
8050 let background_color = cx.theme().status().deleted_background;
8051 self.highlight_text::<EditPredictionHighlight>(
8052 edits.iter().map(|(range, _)| range.clone()).collect(),
8053 HighlightStyle {
8054 background_color: Some(background_color),
8055 ..Default::default()
8056 },
8057 cx,
8058 );
8059 }
8060 }
8061
8062 invalidation_row_range = edit_start_row..edit_end_row;
8063
8064 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8065 if provider.show_tab_accept_marker() {
8066 EditDisplayMode::TabAccept
8067 } else {
8068 EditDisplayMode::Inline
8069 }
8070 } else {
8071 EditDisplayMode::DiffPopover
8072 };
8073
8074 EditPrediction::Edit {
8075 edits,
8076 edit_preview,
8077 display_mode,
8078 snapshot,
8079 }
8080 };
8081
8082 let invalidation_range = multibuffer
8083 .anchor_before(Point::new(invalidation_row_range.start, 0))
8084 ..multibuffer.anchor_after(Point::new(
8085 invalidation_row_range.end,
8086 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8087 ));
8088
8089 self.stale_edit_prediction_in_menu = None;
8090 self.active_edit_prediction = Some(EditPredictionState {
8091 inlay_ids,
8092 completion,
8093 completion_id,
8094 invalidation_range: Some(invalidation_range),
8095 });
8096
8097 cx.notify();
8098
8099 Some(())
8100 }
8101
8102 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8103 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8104 }
8105
8106 fn clear_tasks(&mut self) {
8107 self.tasks.clear()
8108 }
8109
8110 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8111 if self.tasks.insert(key, value).is_some() {
8112 // This case should hopefully be rare, but just in case...
8113 log::error!(
8114 "multiple different run targets found on a single line, only the last target will be rendered"
8115 )
8116 }
8117 }
8118
8119 /// Get all display points of breakpoints that will be rendered within editor
8120 ///
8121 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8122 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8123 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8124 fn active_breakpoints(
8125 &self,
8126 range: Range<DisplayRow>,
8127 window: &mut Window,
8128 cx: &mut Context<Self>,
8129 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8130 let mut breakpoint_display_points = HashMap::default();
8131
8132 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8133 return breakpoint_display_points;
8134 };
8135
8136 let snapshot = self.snapshot(window, cx);
8137
8138 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8139 let Some(project) = self.project() else {
8140 return breakpoint_display_points;
8141 };
8142
8143 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8144 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8145
8146 for (buffer_snapshot, range, excerpt_id) in
8147 multi_buffer_snapshot.range_to_buffer_ranges(range)
8148 {
8149 let Some(buffer) = project
8150 .read(cx)
8151 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8152 else {
8153 continue;
8154 };
8155 let breakpoints = breakpoint_store.read(cx).breakpoints(
8156 &buffer,
8157 Some(
8158 buffer_snapshot.anchor_before(range.start)
8159 ..buffer_snapshot.anchor_after(range.end),
8160 ),
8161 buffer_snapshot,
8162 cx,
8163 );
8164 for (breakpoint, state) in breakpoints {
8165 let multi_buffer_anchor =
8166 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8167 let position = multi_buffer_anchor
8168 .to_point(&multi_buffer_snapshot)
8169 .to_display_point(&snapshot);
8170
8171 breakpoint_display_points.insert(
8172 position.row(),
8173 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8174 );
8175 }
8176 }
8177
8178 breakpoint_display_points
8179 }
8180
8181 fn breakpoint_context_menu(
8182 &self,
8183 anchor: Anchor,
8184 window: &mut Window,
8185 cx: &mut Context<Self>,
8186 ) -> Entity<ui::ContextMenu> {
8187 let weak_editor = cx.weak_entity();
8188 let focus_handle = self.focus_handle(cx);
8189
8190 let row = self
8191 .buffer
8192 .read(cx)
8193 .snapshot(cx)
8194 .summary_for_anchor::<Point>(&anchor)
8195 .row;
8196
8197 let breakpoint = self
8198 .breakpoint_at_row(row, window, cx)
8199 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8200
8201 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8202 "Edit Log Breakpoint"
8203 } else {
8204 "Set Log Breakpoint"
8205 };
8206
8207 let condition_breakpoint_msg = if breakpoint
8208 .as_ref()
8209 .is_some_and(|bp| bp.1.condition.is_some())
8210 {
8211 "Edit Condition Breakpoint"
8212 } else {
8213 "Set Condition Breakpoint"
8214 };
8215
8216 let hit_condition_breakpoint_msg = if breakpoint
8217 .as_ref()
8218 .is_some_and(|bp| bp.1.hit_condition.is_some())
8219 {
8220 "Edit Hit Condition Breakpoint"
8221 } else {
8222 "Set Hit Condition Breakpoint"
8223 };
8224
8225 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8226 "Unset Breakpoint"
8227 } else {
8228 "Set Breakpoint"
8229 };
8230
8231 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8232
8233 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8234 BreakpointState::Enabled => Some("Disable"),
8235 BreakpointState::Disabled => Some("Enable"),
8236 });
8237
8238 let (anchor, breakpoint) =
8239 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8240
8241 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8242 menu.on_blur_subscription(Subscription::new(|| {}))
8243 .context(focus_handle)
8244 .when(run_to_cursor, |this| {
8245 let weak_editor = weak_editor.clone();
8246 this.entry("Run to cursor", None, move |window, cx| {
8247 weak_editor
8248 .update(cx, |editor, cx| {
8249 editor.change_selections(
8250 SelectionEffects::no_scroll(),
8251 window,
8252 cx,
8253 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8254 );
8255 })
8256 .ok();
8257
8258 window.dispatch_action(Box::new(RunToCursor), cx);
8259 })
8260 .separator()
8261 })
8262 .when_some(toggle_state_msg, |this, msg| {
8263 this.entry(msg, None, {
8264 let weak_editor = weak_editor.clone();
8265 let breakpoint = breakpoint.clone();
8266 move |_window, cx| {
8267 weak_editor
8268 .update(cx, |this, cx| {
8269 this.edit_breakpoint_at_anchor(
8270 anchor,
8271 breakpoint.as_ref().clone(),
8272 BreakpointEditAction::InvertState,
8273 cx,
8274 );
8275 })
8276 .log_err();
8277 }
8278 })
8279 })
8280 .entry(set_breakpoint_msg, None, {
8281 let weak_editor = weak_editor.clone();
8282 let breakpoint = breakpoint.clone();
8283 move |_window, cx| {
8284 weak_editor
8285 .update(cx, |this, cx| {
8286 this.edit_breakpoint_at_anchor(
8287 anchor,
8288 breakpoint.as_ref().clone(),
8289 BreakpointEditAction::Toggle,
8290 cx,
8291 );
8292 })
8293 .log_err();
8294 }
8295 })
8296 .entry(log_breakpoint_msg, None, {
8297 let breakpoint = breakpoint.clone();
8298 let weak_editor = weak_editor.clone();
8299 move |window, cx| {
8300 weak_editor
8301 .update(cx, |this, cx| {
8302 this.add_edit_breakpoint_block(
8303 anchor,
8304 breakpoint.as_ref(),
8305 BreakpointPromptEditAction::Log,
8306 window,
8307 cx,
8308 );
8309 })
8310 .log_err();
8311 }
8312 })
8313 .entry(condition_breakpoint_msg, None, {
8314 let breakpoint = breakpoint.clone();
8315 let weak_editor = weak_editor.clone();
8316 move |window, cx| {
8317 weak_editor
8318 .update(cx, |this, cx| {
8319 this.add_edit_breakpoint_block(
8320 anchor,
8321 breakpoint.as_ref(),
8322 BreakpointPromptEditAction::Condition,
8323 window,
8324 cx,
8325 );
8326 })
8327 .log_err();
8328 }
8329 })
8330 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8331 weak_editor
8332 .update(cx, |this, cx| {
8333 this.add_edit_breakpoint_block(
8334 anchor,
8335 breakpoint.as_ref(),
8336 BreakpointPromptEditAction::HitCondition,
8337 window,
8338 cx,
8339 );
8340 })
8341 .log_err();
8342 })
8343 })
8344 }
8345
8346 fn render_breakpoint(
8347 &self,
8348 position: Anchor,
8349 row: DisplayRow,
8350 breakpoint: &Breakpoint,
8351 state: Option<BreakpointSessionState>,
8352 cx: &mut Context<Self>,
8353 ) -> IconButton {
8354 let is_rejected = state.is_some_and(|s| !s.verified);
8355 // Is it a breakpoint that shows up when hovering over gutter?
8356 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8357 (false, false),
8358 |PhantomBreakpointIndicator {
8359 is_active,
8360 display_row,
8361 collides_with_existing_breakpoint,
8362 }| {
8363 (
8364 is_active && display_row == row,
8365 collides_with_existing_breakpoint,
8366 )
8367 },
8368 );
8369
8370 let (color, icon) = {
8371 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8372 (false, false) => ui::IconName::DebugBreakpoint,
8373 (true, false) => ui::IconName::DebugLogBreakpoint,
8374 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8375 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8376 };
8377
8378 let color = if is_phantom {
8379 Color::Hint
8380 } else if is_rejected {
8381 Color::Disabled
8382 } else {
8383 Color::Debugger
8384 };
8385
8386 (color, icon)
8387 };
8388
8389 let breakpoint = Arc::from(breakpoint.clone());
8390
8391 let alt_as_text = gpui::Keystroke {
8392 modifiers: Modifiers::secondary_key(),
8393 ..Default::default()
8394 };
8395 let primary_action_text = if breakpoint.is_disabled() {
8396 "Enable breakpoint"
8397 } else if is_phantom && !collides_with_existing {
8398 "Set breakpoint"
8399 } else {
8400 "Unset breakpoint"
8401 };
8402 let focus_handle = self.focus_handle.clone();
8403
8404 let meta = if is_rejected {
8405 SharedString::from("No executable code is associated with this line.")
8406 } else if collides_with_existing && !breakpoint.is_disabled() {
8407 SharedString::from(format!(
8408 "{alt_as_text}-click to disable,\nright-click for more options."
8409 ))
8410 } else {
8411 SharedString::from("Right-click for more options.")
8412 };
8413 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8414 .icon_size(IconSize::XSmall)
8415 .size(ui::ButtonSize::None)
8416 .when(is_rejected, |this| {
8417 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8418 })
8419 .icon_color(color)
8420 .style(ButtonStyle::Transparent)
8421 .on_click(cx.listener({
8422 move |editor, event: &ClickEvent, window, cx| {
8423 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8424 BreakpointEditAction::InvertState
8425 } else {
8426 BreakpointEditAction::Toggle
8427 };
8428
8429 window.focus(&editor.focus_handle(cx));
8430 editor.edit_breakpoint_at_anchor(
8431 position,
8432 breakpoint.as_ref().clone(),
8433 edit_action,
8434 cx,
8435 );
8436 }
8437 }))
8438 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8439 editor.set_breakpoint_context_menu(
8440 row,
8441 Some(position),
8442 event.position(),
8443 window,
8444 cx,
8445 );
8446 }))
8447 .tooltip(move |_window, cx| {
8448 Tooltip::with_meta_in(
8449 primary_action_text,
8450 Some(&ToggleBreakpoint),
8451 meta.clone(),
8452 &focus_handle,
8453 cx,
8454 )
8455 })
8456 }
8457
8458 fn build_tasks_context(
8459 project: &Entity<Project>,
8460 buffer: &Entity<Buffer>,
8461 buffer_row: u32,
8462 tasks: &Arc<RunnableTasks>,
8463 cx: &mut Context<Self>,
8464 ) -> Task<Option<task::TaskContext>> {
8465 let position = Point::new(buffer_row, tasks.column);
8466 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8467 let location = Location {
8468 buffer: buffer.clone(),
8469 range: range_start..range_start,
8470 };
8471 // Fill in the environmental variables from the tree-sitter captures
8472 let mut captured_task_variables = TaskVariables::default();
8473 for (capture_name, value) in tasks.extra_variables.clone() {
8474 captured_task_variables.insert(
8475 task::VariableName::Custom(capture_name.into()),
8476 value.clone(),
8477 );
8478 }
8479 project.update(cx, |project, cx| {
8480 project.task_store().update(cx, |task_store, cx| {
8481 task_store.task_context_for_location(captured_task_variables, location, cx)
8482 })
8483 })
8484 }
8485
8486 pub fn spawn_nearest_task(
8487 &mut self,
8488 action: &SpawnNearestTask,
8489 window: &mut Window,
8490 cx: &mut Context<Self>,
8491 ) {
8492 let Some((workspace, _)) = self.workspace.clone() else {
8493 return;
8494 };
8495 let Some(project) = self.project.clone() else {
8496 return;
8497 };
8498
8499 // Try to find a closest, enclosing node using tree-sitter that has a task
8500 let Some((buffer, buffer_row, tasks)) = self
8501 .find_enclosing_node_task(cx)
8502 // Or find the task that's closest in row-distance.
8503 .or_else(|| self.find_closest_task(cx))
8504 else {
8505 return;
8506 };
8507
8508 let reveal_strategy = action.reveal;
8509 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8510 cx.spawn_in(window, async move |_, cx| {
8511 let context = task_context.await?;
8512 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8513
8514 let resolved = &mut resolved_task.resolved;
8515 resolved.reveal = reveal_strategy;
8516
8517 workspace
8518 .update_in(cx, |workspace, window, cx| {
8519 workspace.schedule_resolved_task(
8520 task_source_kind,
8521 resolved_task,
8522 false,
8523 window,
8524 cx,
8525 );
8526 })
8527 .ok()
8528 })
8529 .detach();
8530 }
8531
8532 fn find_closest_task(
8533 &mut self,
8534 cx: &mut Context<Self>,
8535 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8536 let cursor_row = self
8537 .selections
8538 .newest_adjusted(&self.display_snapshot(cx))
8539 .head()
8540 .row;
8541
8542 let ((buffer_id, row), tasks) = self
8543 .tasks
8544 .iter()
8545 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8546
8547 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8548 let tasks = Arc::new(tasks.to_owned());
8549 Some((buffer, *row, tasks))
8550 }
8551
8552 fn find_enclosing_node_task(
8553 &mut self,
8554 cx: &mut Context<Self>,
8555 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8556 let snapshot = self.buffer.read(cx).snapshot(cx);
8557 let offset = self
8558 .selections
8559 .newest::<usize>(&self.display_snapshot(cx))
8560 .head();
8561 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8562 let buffer_id = excerpt.buffer().remote_id();
8563
8564 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8565 let mut cursor = layer.node().walk();
8566
8567 while cursor.goto_first_child_for_byte(offset).is_some() {
8568 if cursor.node().end_byte() == offset {
8569 cursor.goto_next_sibling();
8570 }
8571 }
8572
8573 // Ascend to the smallest ancestor that contains the range and has a task.
8574 loop {
8575 let node = cursor.node();
8576 let node_range = node.byte_range();
8577 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8578
8579 // Check if this node contains our offset
8580 if node_range.start <= offset && node_range.end >= offset {
8581 // If it contains offset, check for task
8582 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8583 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8584 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8585 }
8586 }
8587
8588 if !cursor.goto_parent() {
8589 break;
8590 }
8591 }
8592 None
8593 }
8594
8595 fn render_run_indicator(
8596 &self,
8597 _style: &EditorStyle,
8598 is_active: bool,
8599 row: DisplayRow,
8600 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8601 cx: &mut Context<Self>,
8602 ) -> IconButton {
8603 let color = Color::Muted;
8604 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8605
8606 IconButton::new(
8607 ("run_indicator", row.0 as usize),
8608 ui::IconName::PlayOutlined,
8609 )
8610 .shape(ui::IconButtonShape::Square)
8611 .icon_size(IconSize::XSmall)
8612 .icon_color(color)
8613 .toggle_state(is_active)
8614 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8615 let quick_launch = match e {
8616 ClickEvent::Keyboard(_) => true,
8617 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8618 };
8619
8620 window.focus(&editor.focus_handle(cx));
8621 editor.toggle_code_actions(
8622 &ToggleCodeActions {
8623 deployed_from: Some(CodeActionSource::RunMenu(row)),
8624 quick_launch,
8625 },
8626 window,
8627 cx,
8628 );
8629 }))
8630 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8631 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8632 }))
8633 }
8634
8635 pub fn context_menu_visible(&self) -> bool {
8636 !self.edit_prediction_preview_is_active()
8637 && self
8638 .context_menu
8639 .borrow()
8640 .as_ref()
8641 .is_some_and(|menu| menu.visible())
8642 }
8643
8644 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8645 self.context_menu
8646 .borrow()
8647 .as_ref()
8648 .map(|menu| menu.origin())
8649 }
8650
8651 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8652 self.context_menu_options = Some(options);
8653 }
8654
8655 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8656 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8657
8658 fn render_edit_prediction_popover(
8659 &mut self,
8660 text_bounds: &Bounds<Pixels>,
8661 content_origin: gpui::Point<Pixels>,
8662 right_margin: Pixels,
8663 editor_snapshot: &EditorSnapshot,
8664 visible_row_range: Range<DisplayRow>,
8665 scroll_top: ScrollOffset,
8666 scroll_bottom: ScrollOffset,
8667 line_layouts: &[LineWithInvisibles],
8668 line_height: Pixels,
8669 scroll_position: gpui::Point<ScrollOffset>,
8670 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8671 newest_selection_head: Option<DisplayPoint>,
8672 editor_width: Pixels,
8673 style: &EditorStyle,
8674 window: &mut Window,
8675 cx: &mut App,
8676 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8677 if self.mode().is_minimap() {
8678 return None;
8679 }
8680 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8681
8682 if self.edit_prediction_visible_in_cursor_popover(true) {
8683 return None;
8684 }
8685
8686 match &active_edit_prediction.completion {
8687 EditPrediction::MoveWithin { target, .. } => {
8688 let target_display_point = target.to_display_point(editor_snapshot);
8689
8690 if self.edit_prediction_requires_modifier() {
8691 if !self.edit_prediction_preview_is_active() {
8692 return None;
8693 }
8694
8695 self.render_edit_prediction_modifier_jump_popover(
8696 text_bounds,
8697 content_origin,
8698 visible_row_range,
8699 line_layouts,
8700 line_height,
8701 scroll_pixel_position,
8702 newest_selection_head,
8703 target_display_point,
8704 window,
8705 cx,
8706 )
8707 } else {
8708 self.render_edit_prediction_eager_jump_popover(
8709 text_bounds,
8710 content_origin,
8711 editor_snapshot,
8712 visible_row_range,
8713 scroll_top,
8714 scroll_bottom,
8715 line_height,
8716 scroll_pixel_position,
8717 target_display_point,
8718 editor_width,
8719 window,
8720 cx,
8721 )
8722 }
8723 }
8724 EditPrediction::Edit {
8725 display_mode: EditDisplayMode::Inline,
8726 ..
8727 } => None,
8728 EditPrediction::Edit {
8729 display_mode: EditDisplayMode::TabAccept,
8730 edits,
8731 ..
8732 } => {
8733 let range = &edits.first()?.0;
8734 let target_display_point = range.end.to_display_point(editor_snapshot);
8735
8736 self.render_edit_prediction_end_of_line_popover(
8737 "Accept",
8738 editor_snapshot,
8739 visible_row_range,
8740 target_display_point,
8741 line_height,
8742 scroll_pixel_position,
8743 content_origin,
8744 editor_width,
8745 window,
8746 cx,
8747 )
8748 }
8749 EditPrediction::Edit {
8750 edits,
8751 edit_preview,
8752 display_mode: EditDisplayMode::DiffPopover,
8753 snapshot,
8754 } => self.render_edit_prediction_diff_popover(
8755 text_bounds,
8756 content_origin,
8757 right_margin,
8758 editor_snapshot,
8759 visible_row_range,
8760 line_layouts,
8761 line_height,
8762 scroll_position,
8763 scroll_pixel_position,
8764 newest_selection_head,
8765 editor_width,
8766 style,
8767 edits,
8768 edit_preview,
8769 snapshot,
8770 window,
8771 cx,
8772 ),
8773 EditPrediction::MoveOutside { snapshot, .. } => {
8774 let file_name = snapshot
8775 .file()
8776 .map(|file| file.file_name(cx))
8777 .unwrap_or("untitled");
8778 let mut element = self
8779 .render_edit_prediction_line_popover(
8780 format!("Jump to {file_name}"),
8781 Some(IconName::ZedPredict),
8782 window,
8783 cx,
8784 )
8785 .into_any();
8786
8787 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8788 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8789 let origin_y = text_bounds.size.height - size.height - px(30.);
8790 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8791 element.prepaint_at(origin, window, cx);
8792
8793 Some((element, origin))
8794 }
8795 }
8796 }
8797
8798 fn render_edit_prediction_modifier_jump_popover(
8799 &mut self,
8800 text_bounds: &Bounds<Pixels>,
8801 content_origin: gpui::Point<Pixels>,
8802 visible_row_range: Range<DisplayRow>,
8803 line_layouts: &[LineWithInvisibles],
8804 line_height: Pixels,
8805 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8806 newest_selection_head: Option<DisplayPoint>,
8807 target_display_point: DisplayPoint,
8808 window: &mut Window,
8809 cx: &mut App,
8810 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8811 let scrolled_content_origin =
8812 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8813
8814 const SCROLL_PADDING_Y: Pixels = px(12.);
8815
8816 if target_display_point.row() < visible_row_range.start {
8817 return self.render_edit_prediction_scroll_popover(
8818 |_| SCROLL_PADDING_Y,
8819 IconName::ArrowUp,
8820 visible_row_range,
8821 line_layouts,
8822 newest_selection_head,
8823 scrolled_content_origin,
8824 window,
8825 cx,
8826 );
8827 } else if target_display_point.row() >= visible_row_range.end {
8828 return self.render_edit_prediction_scroll_popover(
8829 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8830 IconName::ArrowDown,
8831 visible_row_range,
8832 line_layouts,
8833 newest_selection_head,
8834 scrolled_content_origin,
8835 window,
8836 cx,
8837 );
8838 }
8839
8840 const POLE_WIDTH: Pixels = px(2.);
8841
8842 let line_layout =
8843 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8844 let target_column = target_display_point.column() as usize;
8845
8846 let target_x = line_layout.x_for_index(target_column);
8847 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8848 - scroll_pixel_position.y;
8849
8850 let flag_on_right = target_x < text_bounds.size.width / 2.;
8851
8852 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8853 border_color.l += 0.001;
8854
8855 let mut element = v_flex()
8856 .items_end()
8857 .when(flag_on_right, |el| el.items_start())
8858 .child(if flag_on_right {
8859 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8860 .rounded_bl(px(0.))
8861 .rounded_tl(px(0.))
8862 .border_l_2()
8863 .border_color(border_color)
8864 } else {
8865 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8866 .rounded_br(px(0.))
8867 .rounded_tr(px(0.))
8868 .border_r_2()
8869 .border_color(border_color)
8870 })
8871 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8872 .into_any();
8873
8874 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8875
8876 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8877 - point(
8878 if flag_on_right {
8879 POLE_WIDTH
8880 } else {
8881 size.width - POLE_WIDTH
8882 },
8883 size.height - line_height,
8884 );
8885
8886 origin.x = origin.x.max(content_origin.x);
8887
8888 element.prepaint_at(origin, window, cx);
8889
8890 Some((element, origin))
8891 }
8892
8893 fn render_edit_prediction_scroll_popover(
8894 &mut self,
8895 to_y: impl Fn(Size<Pixels>) -> Pixels,
8896 scroll_icon: IconName,
8897 visible_row_range: Range<DisplayRow>,
8898 line_layouts: &[LineWithInvisibles],
8899 newest_selection_head: Option<DisplayPoint>,
8900 scrolled_content_origin: gpui::Point<Pixels>,
8901 window: &mut Window,
8902 cx: &mut App,
8903 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8904 let mut element = self
8905 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8906 .into_any();
8907
8908 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8909
8910 let cursor = newest_selection_head?;
8911 let cursor_row_layout =
8912 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8913 let cursor_column = cursor.column() as usize;
8914
8915 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8916
8917 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8918
8919 element.prepaint_at(origin, window, cx);
8920 Some((element, origin))
8921 }
8922
8923 fn render_edit_prediction_eager_jump_popover(
8924 &mut self,
8925 text_bounds: &Bounds<Pixels>,
8926 content_origin: gpui::Point<Pixels>,
8927 editor_snapshot: &EditorSnapshot,
8928 visible_row_range: Range<DisplayRow>,
8929 scroll_top: ScrollOffset,
8930 scroll_bottom: ScrollOffset,
8931 line_height: Pixels,
8932 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8933 target_display_point: DisplayPoint,
8934 editor_width: Pixels,
8935 window: &mut Window,
8936 cx: &mut App,
8937 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8938 if target_display_point.row().as_f64() < scroll_top {
8939 let mut element = self
8940 .render_edit_prediction_line_popover(
8941 "Jump to Edit",
8942 Some(IconName::ArrowUp),
8943 window,
8944 cx,
8945 )
8946 .into_any();
8947
8948 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8949 let offset = point(
8950 (text_bounds.size.width - size.width) / 2.,
8951 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8952 );
8953
8954 let origin = text_bounds.origin + offset;
8955 element.prepaint_at(origin, window, cx);
8956 Some((element, origin))
8957 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8958 let mut element = self
8959 .render_edit_prediction_line_popover(
8960 "Jump to Edit",
8961 Some(IconName::ArrowDown),
8962 window,
8963 cx,
8964 )
8965 .into_any();
8966
8967 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8968 let offset = point(
8969 (text_bounds.size.width - size.width) / 2.,
8970 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8971 );
8972
8973 let origin = text_bounds.origin + offset;
8974 element.prepaint_at(origin, window, cx);
8975 Some((element, origin))
8976 } else {
8977 self.render_edit_prediction_end_of_line_popover(
8978 "Jump to Edit",
8979 editor_snapshot,
8980 visible_row_range,
8981 target_display_point,
8982 line_height,
8983 scroll_pixel_position,
8984 content_origin,
8985 editor_width,
8986 window,
8987 cx,
8988 )
8989 }
8990 }
8991
8992 fn render_edit_prediction_end_of_line_popover(
8993 self: &mut Editor,
8994 label: &'static str,
8995 editor_snapshot: &EditorSnapshot,
8996 visible_row_range: Range<DisplayRow>,
8997 target_display_point: DisplayPoint,
8998 line_height: Pixels,
8999 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9000 content_origin: gpui::Point<Pixels>,
9001 editor_width: Pixels,
9002 window: &mut Window,
9003 cx: &mut App,
9004 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9005 let target_line_end = DisplayPoint::new(
9006 target_display_point.row(),
9007 editor_snapshot.line_len(target_display_point.row()),
9008 );
9009
9010 let mut element = self
9011 .render_edit_prediction_line_popover(label, None, window, cx)
9012 .into_any();
9013
9014 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9015
9016 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9017
9018 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9019 let mut origin = start_point
9020 + line_origin
9021 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9022 origin.x = origin.x.max(content_origin.x);
9023
9024 let max_x = content_origin.x + editor_width - size.width;
9025
9026 if origin.x > max_x {
9027 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9028
9029 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9030 origin.y += offset;
9031 IconName::ArrowUp
9032 } else {
9033 origin.y -= offset;
9034 IconName::ArrowDown
9035 };
9036
9037 element = self
9038 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9039 .into_any();
9040
9041 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9042
9043 origin.x = content_origin.x + editor_width - size.width - px(2.);
9044 }
9045
9046 element.prepaint_at(origin, window, cx);
9047 Some((element, origin))
9048 }
9049
9050 fn render_edit_prediction_diff_popover(
9051 self: &Editor,
9052 text_bounds: &Bounds<Pixels>,
9053 content_origin: gpui::Point<Pixels>,
9054 right_margin: Pixels,
9055 editor_snapshot: &EditorSnapshot,
9056 visible_row_range: Range<DisplayRow>,
9057 line_layouts: &[LineWithInvisibles],
9058 line_height: Pixels,
9059 scroll_position: gpui::Point<ScrollOffset>,
9060 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9061 newest_selection_head: Option<DisplayPoint>,
9062 editor_width: Pixels,
9063 style: &EditorStyle,
9064 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9065 edit_preview: &Option<language::EditPreview>,
9066 snapshot: &language::BufferSnapshot,
9067 window: &mut Window,
9068 cx: &mut App,
9069 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9070 let edit_start = edits
9071 .first()
9072 .unwrap()
9073 .0
9074 .start
9075 .to_display_point(editor_snapshot);
9076 let edit_end = edits
9077 .last()
9078 .unwrap()
9079 .0
9080 .end
9081 .to_display_point(editor_snapshot);
9082
9083 let is_visible = visible_row_range.contains(&edit_start.row())
9084 || visible_row_range.contains(&edit_end.row());
9085 if !is_visible {
9086 return None;
9087 }
9088
9089 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9090 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9091 } else {
9092 // Fallback for providers without edit_preview
9093 crate::edit_prediction_fallback_text(edits, cx)
9094 };
9095
9096 let styled_text = highlighted_edits.to_styled_text(&style.text);
9097 let line_count = highlighted_edits.text.lines().count();
9098
9099 const BORDER_WIDTH: Pixels = px(1.);
9100
9101 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9102 let has_keybind = keybind.is_some();
9103
9104 let mut element = h_flex()
9105 .items_start()
9106 .child(
9107 h_flex()
9108 .bg(cx.theme().colors().editor_background)
9109 .border(BORDER_WIDTH)
9110 .shadow_xs()
9111 .border_color(cx.theme().colors().border)
9112 .rounded_l_lg()
9113 .when(line_count > 1, |el| el.rounded_br_lg())
9114 .pr_1()
9115 .child(styled_text),
9116 )
9117 .child(
9118 h_flex()
9119 .h(line_height + BORDER_WIDTH * 2.)
9120 .px_1p5()
9121 .gap_1()
9122 // Workaround: For some reason, there's a gap if we don't do this
9123 .ml(-BORDER_WIDTH)
9124 .shadow(vec![gpui::BoxShadow {
9125 color: gpui::black().opacity(0.05),
9126 offset: point(px(1.), px(1.)),
9127 blur_radius: px(2.),
9128 spread_radius: px(0.),
9129 }])
9130 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9131 .border(BORDER_WIDTH)
9132 .border_color(cx.theme().colors().border)
9133 .rounded_r_lg()
9134 .id("edit_prediction_diff_popover_keybind")
9135 .when(!has_keybind, |el| {
9136 let status_colors = cx.theme().status();
9137
9138 el.bg(status_colors.error_background)
9139 .border_color(status_colors.error.opacity(0.6))
9140 .child(Icon::new(IconName::Info).color(Color::Error))
9141 .cursor_default()
9142 .hoverable_tooltip(move |_window, cx| {
9143 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9144 })
9145 })
9146 .children(keybind),
9147 )
9148 .into_any();
9149
9150 let longest_row =
9151 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9152 let longest_line_width = if visible_row_range.contains(&longest_row) {
9153 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9154 } else {
9155 layout_line(
9156 longest_row,
9157 editor_snapshot,
9158 style,
9159 editor_width,
9160 |_| false,
9161 window,
9162 cx,
9163 )
9164 .width
9165 };
9166
9167 let viewport_bounds =
9168 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9169 right: -right_margin,
9170 ..Default::default()
9171 });
9172
9173 let x_after_longest = Pixels::from(
9174 ScrollPixelOffset::from(
9175 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9176 ) - scroll_pixel_position.x,
9177 );
9178
9179 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9180
9181 // Fully visible if it can be displayed within the window (allow overlapping other
9182 // panes). However, this is only allowed if the popover starts within text_bounds.
9183 let can_position_to_the_right = x_after_longest < text_bounds.right()
9184 && x_after_longest + element_bounds.width < viewport_bounds.right();
9185
9186 let mut origin = if can_position_to_the_right {
9187 point(
9188 x_after_longest,
9189 text_bounds.origin.y
9190 + Pixels::from(
9191 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9192 - scroll_pixel_position.y,
9193 ),
9194 )
9195 } else {
9196 let cursor_row = newest_selection_head.map(|head| head.row());
9197 let above_edit = edit_start
9198 .row()
9199 .0
9200 .checked_sub(line_count as u32)
9201 .map(DisplayRow);
9202 let below_edit = Some(edit_end.row() + 1);
9203 let above_cursor =
9204 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9205 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9206
9207 // Place the edit popover adjacent to the edit if there is a location
9208 // available that is onscreen and does not obscure the cursor. Otherwise,
9209 // place it adjacent to the cursor.
9210 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9211 .into_iter()
9212 .flatten()
9213 .find(|&start_row| {
9214 let end_row = start_row + line_count as u32;
9215 visible_row_range.contains(&start_row)
9216 && visible_row_range.contains(&end_row)
9217 && cursor_row
9218 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9219 })?;
9220
9221 content_origin
9222 + point(
9223 Pixels::from(-scroll_pixel_position.x),
9224 Pixels::from(
9225 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9226 ),
9227 )
9228 };
9229
9230 origin.x -= BORDER_WIDTH;
9231
9232 window.defer_draw(element, origin, 1);
9233
9234 // Do not return an element, since it will already be drawn due to defer_draw.
9235 None
9236 }
9237
9238 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9239 px(30.)
9240 }
9241
9242 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9243 if self.read_only(cx) {
9244 cx.theme().players().read_only()
9245 } else {
9246 self.style.as_ref().unwrap().local_player
9247 }
9248 }
9249
9250 fn render_edit_prediction_accept_keybind(
9251 &self,
9252 window: &mut Window,
9253 cx: &mut App,
9254 ) -> Option<AnyElement> {
9255 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9256 let accept_keystroke = accept_binding.keystroke()?;
9257
9258 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9259
9260 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9261 Color::Accent
9262 } else {
9263 Color::Muted
9264 };
9265
9266 h_flex()
9267 .px_0p5()
9268 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9269 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9270 .text_size(TextSize::XSmall.rems(cx))
9271 .child(h_flex().children(ui::render_modifiers(
9272 accept_keystroke.modifiers(),
9273 PlatformStyle::platform(),
9274 Some(modifiers_color),
9275 Some(IconSize::XSmall.rems().into()),
9276 true,
9277 )))
9278 .when(is_platform_style_mac, |parent| {
9279 parent.child(accept_keystroke.key().to_string())
9280 })
9281 .when(!is_platform_style_mac, |parent| {
9282 parent.child(
9283 Key::new(
9284 util::capitalize(accept_keystroke.key()),
9285 Some(Color::Default),
9286 )
9287 .size(Some(IconSize::XSmall.rems().into())),
9288 )
9289 })
9290 .into_any()
9291 .into()
9292 }
9293
9294 fn render_edit_prediction_line_popover(
9295 &self,
9296 label: impl Into<SharedString>,
9297 icon: Option<IconName>,
9298 window: &mut Window,
9299 cx: &mut App,
9300 ) -> Stateful<Div> {
9301 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9302
9303 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9304 let has_keybind = keybind.is_some();
9305
9306 h_flex()
9307 .id("ep-line-popover")
9308 .py_0p5()
9309 .pl_1()
9310 .pr(padding_right)
9311 .gap_1()
9312 .rounded_md()
9313 .border_1()
9314 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9315 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9316 .shadow_xs()
9317 .when(!has_keybind, |el| {
9318 let status_colors = cx.theme().status();
9319
9320 el.bg(status_colors.error_background)
9321 .border_color(status_colors.error.opacity(0.6))
9322 .pl_2()
9323 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9324 .cursor_default()
9325 .hoverable_tooltip(move |_window, cx| {
9326 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9327 })
9328 })
9329 .children(keybind)
9330 .child(
9331 Label::new(label)
9332 .size(LabelSize::Small)
9333 .when(!has_keybind, |el| {
9334 el.color(cx.theme().status().error.into()).strikethrough()
9335 }),
9336 )
9337 .when(!has_keybind, |el| {
9338 el.child(
9339 h_flex().ml_1().child(
9340 Icon::new(IconName::Info)
9341 .size(IconSize::Small)
9342 .color(cx.theme().status().error.into()),
9343 ),
9344 )
9345 })
9346 .when_some(icon, |element, icon| {
9347 element.child(
9348 div()
9349 .mt(px(1.5))
9350 .child(Icon::new(icon).size(IconSize::Small)),
9351 )
9352 })
9353 }
9354
9355 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9356 let accent_color = cx.theme().colors().text_accent;
9357 let editor_bg_color = cx.theme().colors().editor_background;
9358 editor_bg_color.blend(accent_color.opacity(0.1))
9359 }
9360
9361 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9362 let accent_color = cx.theme().colors().text_accent;
9363 let editor_bg_color = cx.theme().colors().editor_background;
9364 editor_bg_color.blend(accent_color.opacity(0.6))
9365 }
9366 fn get_prediction_provider_icon_name(
9367 provider: &Option<RegisteredEditPredictionProvider>,
9368 ) -> IconName {
9369 match provider {
9370 Some(provider) => match provider.provider.name() {
9371 "copilot" => IconName::Copilot,
9372 "supermaven" => IconName::Supermaven,
9373 _ => IconName::ZedPredict,
9374 },
9375 None => IconName::ZedPredict,
9376 }
9377 }
9378
9379 fn render_edit_prediction_cursor_popover(
9380 &self,
9381 min_width: Pixels,
9382 max_width: Pixels,
9383 cursor_point: Point,
9384 style: &EditorStyle,
9385 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9386 _window: &Window,
9387 cx: &mut Context<Editor>,
9388 ) -> Option<AnyElement> {
9389 let provider = self.edit_prediction_provider.as_ref()?;
9390 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9391
9392 let is_refreshing = provider.provider.is_refreshing(cx);
9393
9394 fn pending_completion_container(icon: IconName) -> Div {
9395 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9396 }
9397
9398 let completion = match &self.active_edit_prediction {
9399 Some(prediction) => {
9400 if !self.has_visible_completions_menu() {
9401 const RADIUS: Pixels = px(6.);
9402 const BORDER_WIDTH: Pixels = px(1.);
9403
9404 return Some(
9405 h_flex()
9406 .elevation_2(cx)
9407 .border(BORDER_WIDTH)
9408 .border_color(cx.theme().colors().border)
9409 .when(accept_keystroke.is_none(), |el| {
9410 el.border_color(cx.theme().status().error)
9411 })
9412 .rounded(RADIUS)
9413 .rounded_tl(px(0.))
9414 .overflow_hidden()
9415 .child(div().px_1p5().child(match &prediction.completion {
9416 EditPrediction::MoveWithin { target, snapshot } => {
9417 use text::ToPoint as _;
9418 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9419 {
9420 Icon::new(IconName::ZedPredictDown)
9421 } else {
9422 Icon::new(IconName::ZedPredictUp)
9423 }
9424 }
9425 EditPrediction::MoveOutside { .. } => {
9426 // TODO [zeta2] custom icon for external jump?
9427 Icon::new(provider_icon)
9428 }
9429 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9430 }))
9431 .child(
9432 h_flex()
9433 .gap_1()
9434 .py_1()
9435 .px_2()
9436 .rounded_r(RADIUS - BORDER_WIDTH)
9437 .border_l_1()
9438 .border_color(cx.theme().colors().border)
9439 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9440 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9441 el.child(
9442 Label::new("Hold")
9443 .size(LabelSize::Small)
9444 .when(accept_keystroke.is_none(), |el| {
9445 el.strikethrough()
9446 })
9447 .line_height_style(LineHeightStyle::UiLabel),
9448 )
9449 })
9450 .id("edit_prediction_cursor_popover_keybind")
9451 .when(accept_keystroke.is_none(), |el| {
9452 let status_colors = cx.theme().status();
9453
9454 el.bg(status_colors.error_background)
9455 .border_color(status_colors.error.opacity(0.6))
9456 .child(Icon::new(IconName::Info).color(Color::Error))
9457 .cursor_default()
9458 .hoverable_tooltip(move |_window, cx| {
9459 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9460 .into()
9461 })
9462 })
9463 .when_some(
9464 accept_keystroke.as_ref(),
9465 |el, accept_keystroke| {
9466 el.child(h_flex().children(ui::render_modifiers(
9467 accept_keystroke.modifiers(),
9468 PlatformStyle::platform(),
9469 Some(Color::Default),
9470 Some(IconSize::XSmall.rems().into()),
9471 false,
9472 )))
9473 },
9474 ),
9475 )
9476 .into_any(),
9477 );
9478 }
9479
9480 self.render_edit_prediction_cursor_popover_preview(
9481 prediction,
9482 cursor_point,
9483 style,
9484 cx,
9485 )?
9486 }
9487
9488 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9489 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9490 stale_completion,
9491 cursor_point,
9492 style,
9493 cx,
9494 )?,
9495
9496 None => pending_completion_container(provider_icon)
9497 .child(Label::new("...").size(LabelSize::Small)),
9498 },
9499
9500 None => pending_completion_container(provider_icon)
9501 .child(Label::new("...").size(LabelSize::Small)),
9502 };
9503
9504 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9505 completion
9506 .with_animation(
9507 "loading-completion",
9508 Animation::new(Duration::from_secs(2))
9509 .repeat()
9510 .with_easing(pulsating_between(0.4, 0.8)),
9511 |label, delta| label.opacity(delta),
9512 )
9513 .into_any_element()
9514 } else {
9515 completion.into_any_element()
9516 };
9517
9518 let has_completion = self.active_edit_prediction.is_some();
9519
9520 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9521 Some(
9522 h_flex()
9523 .min_w(min_width)
9524 .max_w(max_width)
9525 .flex_1()
9526 .elevation_2(cx)
9527 .border_color(cx.theme().colors().border)
9528 .child(
9529 div()
9530 .flex_1()
9531 .py_1()
9532 .px_2()
9533 .overflow_hidden()
9534 .child(completion),
9535 )
9536 .when_some(accept_keystroke, |el, accept_keystroke| {
9537 if !accept_keystroke.modifiers().modified() {
9538 return el;
9539 }
9540
9541 el.child(
9542 h_flex()
9543 .h_full()
9544 .border_l_1()
9545 .rounded_r_lg()
9546 .border_color(cx.theme().colors().border)
9547 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9548 .gap_1()
9549 .py_1()
9550 .px_2()
9551 .child(
9552 h_flex()
9553 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9554 .when(is_platform_style_mac, |parent| parent.gap_1())
9555 .child(h_flex().children(ui::render_modifiers(
9556 accept_keystroke.modifiers(),
9557 PlatformStyle::platform(),
9558 Some(if !has_completion {
9559 Color::Muted
9560 } else {
9561 Color::Default
9562 }),
9563 None,
9564 false,
9565 ))),
9566 )
9567 .child(Label::new("Preview").into_any_element())
9568 .opacity(if has_completion { 1.0 } else { 0.4 }),
9569 )
9570 })
9571 .into_any(),
9572 )
9573 }
9574
9575 fn render_edit_prediction_cursor_popover_preview(
9576 &self,
9577 completion: &EditPredictionState,
9578 cursor_point: Point,
9579 style: &EditorStyle,
9580 cx: &mut Context<Editor>,
9581 ) -> Option<Div> {
9582 use text::ToPoint as _;
9583
9584 fn render_relative_row_jump(
9585 prefix: impl Into<String>,
9586 current_row: u32,
9587 target_row: u32,
9588 ) -> Div {
9589 let (row_diff, arrow) = if target_row < current_row {
9590 (current_row - target_row, IconName::ArrowUp)
9591 } else {
9592 (target_row - current_row, IconName::ArrowDown)
9593 };
9594
9595 h_flex()
9596 .child(
9597 Label::new(format!("{}{}", prefix.into(), row_diff))
9598 .color(Color::Muted)
9599 .size(LabelSize::Small),
9600 )
9601 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9602 }
9603
9604 let supports_jump = self
9605 .edit_prediction_provider
9606 .as_ref()
9607 .map(|provider| provider.provider.supports_jump_to_edit())
9608 .unwrap_or(true);
9609
9610 match &completion.completion {
9611 EditPrediction::MoveWithin {
9612 target, snapshot, ..
9613 } => {
9614 if !supports_jump {
9615 return None;
9616 }
9617
9618 Some(
9619 h_flex()
9620 .px_2()
9621 .gap_2()
9622 .flex_1()
9623 .child(
9624 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9625 Icon::new(IconName::ZedPredictDown)
9626 } else {
9627 Icon::new(IconName::ZedPredictUp)
9628 },
9629 )
9630 .child(Label::new("Jump to Edit")),
9631 )
9632 }
9633 EditPrediction::MoveOutside { snapshot, .. } => {
9634 let file_name = snapshot
9635 .file()
9636 .map(|file| file.file_name(cx))
9637 .unwrap_or("untitled");
9638 Some(
9639 h_flex()
9640 .px_2()
9641 .gap_2()
9642 .flex_1()
9643 .child(Icon::new(IconName::ZedPredict))
9644 .child(Label::new(format!("Jump to {file_name}"))),
9645 )
9646 }
9647 EditPrediction::Edit {
9648 edits,
9649 edit_preview,
9650 snapshot,
9651 display_mode: _,
9652 } => {
9653 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9654
9655 let (highlighted_edits, has_more_lines) =
9656 if let Some(edit_preview) = edit_preview.as_ref() {
9657 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9658 .first_line_preview()
9659 } else {
9660 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9661 };
9662
9663 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9664 .with_default_highlights(&style.text, highlighted_edits.highlights);
9665
9666 let preview = h_flex()
9667 .gap_1()
9668 .min_w_16()
9669 .child(styled_text)
9670 .when(has_more_lines, |parent| parent.child("…"));
9671
9672 let left = if supports_jump && first_edit_row != cursor_point.row {
9673 render_relative_row_jump("", cursor_point.row, first_edit_row)
9674 .into_any_element()
9675 } else {
9676 let icon_name =
9677 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9678 Icon::new(icon_name).into_any_element()
9679 };
9680
9681 Some(
9682 h_flex()
9683 .h_full()
9684 .flex_1()
9685 .gap_2()
9686 .pr_1()
9687 .overflow_x_hidden()
9688 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9689 .child(left)
9690 .child(preview),
9691 )
9692 }
9693 }
9694 }
9695
9696 pub fn render_context_menu(
9697 &self,
9698 style: &EditorStyle,
9699 max_height_in_lines: u32,
9700 window: &mut Window,
9701 cx: &mut Context<Editor>,
9702 ) -> Option<AnyElement> {
9703 let menu = self.context_menu.borrow();
9704 let menu = menu.as_ref()?;
9705 if !menu.visible() {
9706 return None;
9707 };
9708 Some(menu.render(style, max_height_in_lines, window, cx))
9709 }
9710
9711 fn render_context_menu_aside(
9712 &mut self,
9713 max_size: Size<Pixels>,
9714 window: &mut Window,
9715 cx: &mut Context<Editor>,
9716 ) -> Option<AnyElement> {
9717 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9718 if menu.visible() {
9719 menu.render_aside(max_size, window, cx)
9720 } else {
9721 None
9722 }
9723 })
9724 }
9725
9726 fn hide_context_menu(
9727 &mut self,
9728 window: &mut Window,
9729 cx: &mut Context<Self>,
9730 ) -> Option<CodeContextMenu> {
9731 cx.notify();
9732 self.completion_tasks.clear();
9733 let context_menu = self.context_menu.borrow_mut().take();
9734 self.stale_edit_prediction_in_menu.take();
9735 self.update_visible_edit_prediction(window, cx);
9736 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9737 && let Some(completion_provider) = &self.completion_provider
9738 {
9739 completion_provider.selection_changed(None, window, cx);
9740 }
9741 context_menu
9742 }
9743
9744 fn show_snippet_choices(
9745 &mut self,
9746 choices: &Vec<String>,
9747 selection: Range<Anchor>,
9748 cx: &mut Context<Self>,
9749 ) {
9750 let Some((_, buffer, _)) = self
9751 .buffer()
9752 .read(cx)
9753 .excerpt_containing(selection.start, cx)
9754 else {
9755 return;
9756 };
9757 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9758 else {
9759 return;
9760 };
9761 if buffer != end_buffer {
9762 log::error!("expected anchor range to have matching buffer IDs");
9763 return;
9764 }
9765
9766 let id = post_inc(&mut self.next_completion_id);
9767 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9768 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9769 CompletionsMenu::new_snippet_choices(
9770 id,
9771 true,
9772 choices,
9773 selection,
9774 buffer,
9775 snippet_sort_order,
9776 ),
9777 ));
9778 }
9779
9780 pub fn insert_snippet(
9781 &mut self,
9782 insertion_ranges: &[Range<usize>],
9783 snippet: Snippet,
9784 window: &mut Window,
9785 cx: &mut Context<Self>,
9786 ) -> Result<()> {
9787 struct Tabstop<T> {
9788 is_end_tabstop: bool,
9789 ranges: Vec<Range<T>>,
9790 choices: Option<Vec<String>>,
9791 }
9792
9793 let tabstops = self.buffer.update(cx, |buffer, cx| {
9794 let snippet_text: Arc<str> = snippet.text.clone().into();
9795 let edits = insertion_ranges
9796 .iter()
9797 .cloned()
9798 .map(|range| (range, snippet_text.clone()));
9799 let autoindent_mode = AutoindentMode::Block {
9800 original_indent_columns: Vec::new(),
9801 };
9802 buffer.edit(edits, Some(autoindent_mode), cx);
9803
9804 let snapshot = &*buffer.read(cx);
9805 let snippet = &snippet;
9806 snippet
9807 .tabstops
9808 .iter()
9809 .map(|tabstop| {
9810 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9811 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9812 });
9813 let mut tabstop_ranges = tabstop
9814 .ranges
9815 .iter()
9816 .flat_map(|tabstop_range| {
9817 let mut delta = 0_isize;
9818 insertion_ranges.iter().map(move |insertion_range| {
9819 let insertion_start = insertion_range.start as isize + delta;
9820 delta +=
9821 snippet.text.len() as isize - insertion_range.len() as isize;
9822
9823 let start = ((insertion_start + tabstop_range.start) as usize)
9824 .min(snapshot.len());
9825 let end = ((insertion_start + tabstop_range.end) as usize)
9826 .min(snapshot.len());
9827 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9828 })
9829 })
9830 .collect::<Vec<_>>();
9831 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9832
9833 Tabstop {
9834 is_end_tabstop,
9835 ranges: tabstop_ranges,
9836 choices: tabstop.choices.clone(),
9837 }
9838 })
9839 .collect::<Vec<_>>()
9840 });
9841 if let Some(tabstop) = tabstops.first() {
9842 self.change_selections(Default::default(), window, cx, |s| {
9843 // Reverse order so that the first range is the newest created selection.
9844 // Completions will use it and autoscroll will prioritize it.
9845 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9846 });
9847
9848 if let Some(choices) = &tabstop.choices
9849 && let Some(selection) = tabstop.ranges.first()
9850 {
9851 self.show_snippet_choices(choices, selection.clone(), cx)
9852 }
9853
9854 // If we're already at the last tabstop and it's at the end of the snippet,
9855 // we're done, we don't need to keep the state around.
9856 if !tabstop.is_end_tabstop {
9857 let choices = tabstops
9858 .iter()
9859 .map(|tabstop| tabstop.choices.clone())
9860 .collect();
9861
9862 let ranges = tabstops
9863 .into_iter()
9864 .map(|tabstop| tabstop.ranges)
9865 .collect::<Vec<_>>();
9866
9867 self.snippet_stack.push(SnippetState {
9868 active_index: 0,
9869 ranges,
9870 choices,
9871 });
9872 }
9873
9874 // Check whether the just-entered snippet ends with an auto-closable bracket.
9875 if self.autoclose_regions.is_empty() {
9876 let snapshot = self.buffer.read(cx).snapshot(cx);
9877 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9878 let selection_head = selection.head();
9879 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9880 continue;
9881 };
9882
9883 let mut bracket_pair = None;
9884 let max_lookup_length = scope
9885 .brackets()
9886 .map(|(pair, _)| {
9887 pair.start
9888 .as_str()
9889 .chars()
9890 .count()
9891 .max(pair.end.as_str().chars().count())
9892 })
9893 .max();
9894 if let Some(max_lookup_length) = max_lookup_length {
9895 let next_text = snapshot
9896 .chars_at(selection_head)
9897 .take(max_lookup_length)
9898 .collect::<String>();
9899 let prev_text = snapshot
9900 .reversed_chars_at(selection_head)
9901 .take(max_lookup_length)
9902 .collect::<String>();
9903
9904 for (pair, enabled) in scope.brackets() {
9905 if enabled
9906 && pair.close
9907 && prev_text.starts_with(pair.start.as_str())
9908 && next_text.starts_with(pair.end.as_str())
9909 {
9910 bracket_pair = Some(pair.clone());
9911 break;
9912 }
9913 }
9914 }
9915
9916 if let Some(pair) = bracket_pair {
9917 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9918 let autoclose_enabled =
9919 self.use_autoclose && snapshot_settings.use_autoclose;
9920 if autoclose_enabled {
9921 let start = snapshot.anchor_after(selection_head);
9922 let end = snapshot.anchor_after(selection_head);
9923 self.autoclose_regions.push(AutocloseRegion {
9924 selection_id: selection.id,
9925 range: start..end,
9926 pair,
9927 });
9928 }
9929 }
9930 }
9931 }
9932 }
9933 Ok(())
9934 }
9935
9936 pub fn move_to_next_snippet_tabstop(
9937 &mut self,
9938 window: &mut Window,
9939 cx: &mut Context<Self>,
9940 ) -> bool {
9941 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9942 }
9943
9944 pub fn move_to_prev_snippet_tabstop(
9945 &mut self,
9946 window: &mut Window,
9947 cx: &mut Context<Self>,
9948 ) -> bool {
9949 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9950 }
9951
9952 pub fn move_to_snippet_tabstop(
9953 &mut self,
9954 bias: Bias,
9955 window: &mut Window,
9956 cx: &mut Context<Self>,
9957 ) -> bool {
9958 if let Some(mut snippet) = self.snippet_stack.pop() {
9959 match bias {
9960 Bias::Left => {
9961 if snippet.active_index > 0 {
9962 snippet.active_index -= 1;
9963 } else {
9964 self.snippet_stack.push(snippet);
9965 return false;
9966 }
9967 }
9968 Bias::Right => {
9969 if snippet.active_index + 1 < snippet.ranges.len() {
9970 snippet.active_index += 1;
9971 } else {
9972 self.snippet_stack.push(snippet);
9973 return false;
9974 }
9975 }
9976 }
9977 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9978 self.change_selections(Default::default(), window, cx, |s| {
9979 // Reverse order so that the first range is the newest created selection.
9980 // Completions will use it and autoscroll will prioritize it.
9981 s.select_ranges(current_ranges.iter().rev().cloned())
9982 });
9983
9984 if let Some(choices) = &snippet.choices[snippet.active_index]
9985 && let Some(selection) = current_ranges.first()
9986 {
9987 self.show_snippet_choices(choices, selection.clone(), cx);
9988 }
9989
9990 // If snippet state is not at the last tabstop, push it back on the stack
9991 if snippet.active_index + 1 < snippet.ranges.len() {
9992 self.snippet_stack.push(snippet);
9993 }
9994 return true;
9995 }
9996 }
9997
9998 false
9999 }
10000
10001 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10002 self.transact(window, cx, |this, window, cx| {
10003 this.select_all(&SelectAll, window, cx);
10004 this.insert("", window, cx);
10005 });
10006 }
10007
10008 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10009 if self.read_only(cx) {
10010 return;
10011 }
10012 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10013 self.transact(window, cx, |this, window, cx| {
10014 this.select_autoclose_pair(window, cx);
10015
10016 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10017
10018 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10019 if !this.linked_edit_ranges.is_empty() {
10020 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10021 let snapshot = this.buffer.read(cx).snapshot(cx);
10022
10023 for selection in selections.iter() {
10024 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10025 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10026 if selection_start.buffer_id != selection_end.buffer_id {
10027 continue;
10028 }
10029 if let Some(ranges) =
10030 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10031 {
10032 for (buffer, entries) in ranges {
10033 linked_ranges.entry(buffer).or_default().extend(entries);
10034 }
10035 }
10036 }
10037 }
10038
10039 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10040 for selection in &mut selections {
10041 if selection.is_empty() {
10042 let old_head = selection.head();
10043 let mut new_head =
10044 movement::left(&display_map, old_head.to_display_point(&display_map))
10045 .to_point(&display_map);
10046 if let Some((buffer, line_buffer_range)) = display_map
10047 .buffer_snapshot()
10048 .buffer_line_for_row(MultiBufferRow(old_head.row))
10049 {
10050 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10051 let indent_len = match indent_size.kind {
10052 IndentKind::Space => {
10053 buffer.settings_at(line_buffer_range.start, cx).tab_size
10054 }
10055 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10056 };
10057 if old_head.column <= indent_size.len && old_head.column > 0 {
10058 let indent_len = indent_len.get();
10059 new_head = cmp::min(
10060 new_head,
10061 MultiBufferPoint::new(
10062 old_head.row,
10063 ((old_head.column - 1) / indent_len) * indent_len,
10064 ),
10065 );
10066 }
10067 }
10068
10069 selection.set_head(new_head, SelectionGoal::None);
10070 }
10071 }
10072
10073 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10074 this.insert("", window, cx);
10075 let empty_str: Arc<str> = Arc::from("");
10076 for (buffer, edits) in linked_ranges {
10077 let snapshot = buffer.read(cx).snapshot();
10078 use text::ToPoint as TP;
10079
10080 let edits = edits
10081 .into_iter()
10082 .map(|range| {
10083 let end_point = TP::to_point(&range.end, &snapshot);
10084 let mut start_point = TP::to_point(&range.start, &snapshot);
10085
10086 if end_point == start_point {
10087 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10088 .saturating_sub(1);
10089 start_point =
10090 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10091 };
10092
10093 (start_point..end_point, empty_str.clone())
10094 })
10095 .sorted_by_key(|(range, _)| range.start)
10096 .collect::<Vec<_>>();
10097 buffer.update(cx, |this, cx| {
10098 this.edit(edits, None, cx);
10099 })
10100 }
10101 this.refresh_edit_prediction(true, false, window, cx);
10102 refresh_linked_ranges(this, window, cx);
10103 });
10104 }
10105
10106 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10107 if self.read_only(cx) {
10108 return;
10109 }
10110 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10111 self.transact(window, cx, |this, window, cx| {
10112 this.change_selections(Default::default(), window, cx, |s| {
10113 s.move_with(|map, selection| {
10114 if selection.is_empty() {
10115 let cursor = movement::right(map, selection.head());
10116 selection.end = cursor;
10117 selection.reversed = true;
10118 selection.goal = SelectionGoal::None;
10119 }
10120 })
10121 });
10122 this.insert("", window, cx);
10123 this.refresh_edit_prediction(true, false, window, cx);
10124 });
10125 }
10126
10127 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10128 if self.mode.is_single_line() {
10129 cx.propagate();
10130 return;
10131 }
10132
10133 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10134 if self.move_to_prev_snippet_tabstop(window, cx) {
10135 return;
10136 }
10137 self.outdent(&Outdent, window, cx);
10138 }
10139
10140 pub fn next_snippet_tabstop(
10141 &mut self,
10142 _: &NextSnippetTabstop,
10143 window: &mut Window,
10144 cx: &mut Context<Self>,
10145 ) {
10146 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10147 cx.propagate();
10148 return;
10149 }
10150
10151 if self.move_to_next_snippet_tabstop(window, cx) {
10152 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10153 return;
10154 }
10155 cx.propagate();
10156 }
10157
10158 pub fn previous_snippet_tabstop(
10159 &mut self,
10160 _: &PreviousSnippetTabstop,
10161 window: &mut Window,
10162 cx: &mut Context<Self>,
10163 ) {
10164 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10165 cx.propagate();
10166 return;
10167 }
10168
10169 if self.move_to_prev_snippet_tabstop(window, cx) {
10170 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10171 return;
10172 }
10173 cx.propagate();
10174 }
10175
10176 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10177 if self.mode.is_single_line() {
10178 cx.propagate();
10179 return;
10180 }
10181
10182 if self.move_to_next_snippet_tabstop(window, cx) {
10183 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10184 return;
10185 }
10186 if self.read_only(cx) {
10187 return;
10188 }
10189 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10190 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10191 let buffer = self.buffer.read(cx);
10192 let snapshot = buffer.snapshot(cx);
10193 let rows_iter = selections.iter().map(|s| s.head().row);
10194 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10195
10196 let has_some_cursor_in_whitespace = selections
10197 .iter()
10198 .filter(|selection| selection.is_empty())
10199 .any(|selection| {
10200 let cursor = selection.head();
10201 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10202 cursor.column < current_indent.len
10203 });
10204
10205 let mut edits = Vec::new();
10206 let mut prev_edited_row = 0;
10207 let mut row_delta = 0;
10208 for selection in &mut selections {
10209 if selection.start.row != prev_edited_row {
10210 row_delta = 0;
10211 }
10212 prev_edited_row = selection.end.row;
10213
10214 // If the selection is non-empty, then increase the indentation of the selected lines.
10215 if !selection.is_empty() {
10216 row_delta =
10217 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10218 continue;
10219 }
10220
10221 let cursor = selection.head();
10222 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10223 if let Some(suggested_indent) =
10224 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10225 {
10226 // Don't do anything if already at suggested indent
10227 // and there is any other cursor which is not
10228 if has_some_cursor_in_whitespace
10229 && cursor.column == current_indent.len
10230 && current_indent.len == suggested_indent.len
10231 {
10232 continue;
10233 }
10234
10235 // Adjust line and move cursor to suggested indent
10236 // if cursor is not at suggested indent
10237 if cursor.column < suggested_indent.len
10238 && cursor.column <= current_indent.len
10239 && current_indent.len <= suggested_indent.len
10240 {
10241 selection.start = Point::new(cursor.row, suggested_indent.len);
10242 selection.end = selection.start;
10243 if row_delta == 0 {
10244 edits.extend(Buffer::edit_for_indent_size_adjustment(
10245 cursor.row,
10246 current_indent,
10247 suggested_indent,
10248 ));
10249 row_delta = suggested_indent.len - current_indent.len;
10250 }
10251 continue;
10252 }
10253
10254 // If current indent is more than suggested indent
10255 // only move cursor to current indent and skip indent
10256 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10257 selection.start = Point::new(cursor.row, current_indent.len);
10258 selection.end = selection.start;
10259 continue;
10260 }
10261 }
10262
10263 // Otherwise, insert a hard or soft tab.
10264 let settings = buffer.language_settings_at(cursor, cx);
10265 let tab_size = if settings.hard_tabs {
10266 IndentSize::tab()
10267 } else {
10268 let tab_size = settings.tab_size.get();
10269 let indent_remainder = snapshot
10270 .text_for_range(Point::new(cursor.row, 0)..cursor)
10271 .flat_map(str::chars)
10272 .fold(row_delta % tab_size, |counter: u32, c| {
10273 if c == '\t' {
10274 0
10275 } else {
10276 (counter + 1) % tab_size
10277 }
10278 });
10279
10280 let chars_to_next_tab_stop = tab_size - indent_remainder;
10281 IndentSize::spaces(chars_to_next_tab_stop)
10282 };
10283 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10284 selection.end = selection.start;
10285 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10286 row_delta += tab_size.len;
10287 }
10288
10289 self.transact(window, cx, |this, window, cx| {
10290 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10291 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10292 this.refresh_edit_prediction(true, false, window, cx);
10293 });
10294 }
10295
10296 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10297 if self.read_only(cx) {
10298 return;
10299 }
10300 if self.mode.is_single_line() {
10301 cx.propagate();
10302 return;
10303 }
10304
10305 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10306 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10307 let mut prev_edited_row = 0;
10308 let mut row_delta = 0;
10309 let mut edits = Vec::new();
10310 let buffer = self.buffer.read(cx);
10311 let snapshot = buffer.snapshot(cx);
10312 for selection in &mut selections {
10313 if selection.start.row != prev_edited_row {
10314 row_delta = 0;
10315 }
10316 prev_edited_row = selection.end.row;
10317
10318 row_delta =
10319 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10320 }
10321
10322 self.transact(window, cx, |this, window, cx| {
10323 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10324 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10325 });
10326 }
10327
10328 fn indent_selection(
10329 buffer: &MultiBuffer,
10330 snapshot: &MultiBufferSnapshot,
10331 selection: &mut Selection<Point>,
10332 edits: &mut Vec<(Range<Point>, String)>,
10333 delta_for_start_row: u32,
10334 cx: &App,
10335 ) -> u32 {
10336 let settings = buffer.language_settings_at(selection.start, cx);
10337 let tab_size = settings.tab_size.get();
10338 let indent_kind = if settings.hard_tabs {
10339 IndentKind::Tab
10340 } else {
10341 IndentKind::Space
10342 };
10343 let mut start_row = selection.start.row;
10344 let mut end_row = selection.end.row + 1;
10345
10346 // If a selection ends at the beginning of a line, don't indent
10347 // that last line.
10348 if selection.end.column == 0 && selection.end.row > selection.start.row {
10349 end_row -= 1;
10350 }
10351
10352 // Avoid re-indenting a row that has already been indented by a
10353 // previous selection, but still update this selection's column
10354 // to reflect that indentation.
10355 if delta_for_start_row > 0 {
10356 start_row += 1;
10357 selection.start.column += delta_for_start_row;
10358 if selection.end.row == selection.start.row {
10359 selection.end.column += delta_for_start_row;
10360 }
10361 }
10362
10363 let mut delta_for_end_row = 0;
10364 let has_multiple_rows = start_row + 1 != end_row;
10365 for row in start_row..end_row {
10366 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10367 let indent_delta = match (current_indent.kind, indent_kind) {
10368 (IndentKind::Space, IndentKind::Space) => {
10369 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10370 IndentSize::spaces(columns_to_next_tab_stop)
10371 }
10372 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10373 (_, IndentKind::Tab) => IndentSize::tab(),
10374 };
10375
10376 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10377 0
10378 } else {
10379 selection.start.column
10380 };
10381 let row_start = Point::new(row, start);
10382 edits.push((
10383 row_start..row_start,
10384 indent_delta.chars().collect::<String>(),
10385 ));
10386
10387 // Update this selection's endpoints to reflect the indentation.
10388 if row == selection.start.row {
10389 selection.start.column += indent_delta.len;
10390 }
10391 if row == selection.end.row {
10392 selection.end.column += indent_delta.len;
10393 delta_for_end_row = indent_delta.len;
10394 }
10395 }
10396
10397 if selection.start.row == selection.end.row {
10398 delta_for_start_row + delta_for_end_row
10399 } else {
10400 delta_for_end_row
10401 }
10402 }
10403
10404 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10405 if self.read_only(cx) {
10406 return;
10407 }
10408 if self.mode.is_single_line() {
10409 cx.propagate();
10410 return;
10411 }
10412
10413 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10414 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10415 let selections = self.selections.all::<Point>(&display_map);
10416 let mut deletion_ranges = Vec::new();
10417 let mut last_outdent = None;
10418 {
10419 let buffer = self.buffer.read(cx);
10420 let snapshot = buffer.snapshot(cx);
10421 for selection in &selections {
10422 let settings = buffer.language_settings_at(selection.start, cx);
10423 let tab_size = settings.tab_size.get();
10424 let mut rows = selection.spanned_rows(false, &display_map);
10425
10426 // Avoid re-outdenting a row that has already been outdented by a
10427 // previous selection.
10428 if let Some(last_row) = last_outdent
10429 && last_row == rows.start
10430 {
10431 rows.start = rows.start.next_row();
10432 }
10433 let has_multiple_rows = rows.len() > 1;
10434 for row in rows.iter_rows() {
10435 let indent_size = snapshot.indent_size_for_line(row);
10436 if indent_size.len > 0 {
10437 let deletion_len = match indent_size.kind {
10438 IndentKind::Space => {
10439 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10440 if columns_to_prev_tab_stop == 0 {
10441 tab_size
10442 } else {
10443 columns_to_prev_tab_stop
10444 }
10445 }
10446 IndentKind::Tab => 1,
10447 };
10448 let start = if has_multiple_rows
10449 || deletion_len > selection.start.column
10450 || indent_size.len < selection.start.column
10451 {
10452 0
10453 } else {
10454 selection.start.column - deletion_len
10455 };
10456 deletion_ranges.push(
10457 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10458 );
10459 last_outdent = Some(row);
10460 }
10461 }
10462 }
10463 }
10464
10465 self.transact(window, cx, |this, window, cx| {
10466 this.buffer.update(cx, |buffer, cx| {
10467 let empty_str: Arc<str> = Arc::default();
10468 buffer.edit(
10469 deletion_ranges
10470 .into_iter()
10471 .map(|range| (range, empty_str.clone())),
10472 None,
10473 cx,
10474 );
10475 });
10476 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10477 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10478 });
10479 }
10480
10481 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10482 if self.read_only(cx) {
10483 return;
10484 }
10485 if self.mode.is_single_line() {
10486 cx.propagate();
10487 return;
10488 }
10489
10490 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10491 let selections = self
10492 .selections
10493 .all::<usize>(&self.display_snapshot(cx))
10494 .into_iter()
10495 .map(|s| s.range());
10496
10497 self.transact(window, cx, |this, window, cx| {
10498 this.buffer.update(cx, |buffer, cx| {
10499 buffer.autoindent_ranges(selections, cx);
10500 });
10501 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10502 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10503 });
10504 }
10505
10506 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10507 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10508 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10509 let selections = self.selections.all::<Point>(&display_map);
10510
10511 let mut new_cursors = Vec::new();
10512 let mut edit_ranges = Vec::new();
10513 let mut selections = selections.iter().peekable();
10514 while let Some(selection) = selections.next() {
10515 let mut rows = selection.spanned_rows(false, &display_map);
10516
10517 // Accumulate contiguous regions of rows that we want to delete.
10518 while let Some(next_selection) = selections.peek() {
10519 let next_rows = next_selection.spanned_rows(false, &display_map);
10520 if next_rows.start <= rows.end {
10521 rows.end = next_rows.end;
10522 selections.next().unwrap();
10523 } else {
10524 break;
10525 }
10526 }
10527
10528 let buffer = display_map.buffer_snapshot();
10529 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10530 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10531 // If there's a line after the range, delete the \n from the end of the row range
10532 (
10533 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10534 rows.end,
10535 )
10536 } else {
10537 // If there isn't a line after the range, delete the \n from the line before the
10538 // start of the row range
10539 edit_start = edit_start.saturating_sub(1);
10540 (buffer.len(), rows.start.previous_row())
10541 };
10542
10543 let text_layout_details = self.text_layout_details(window);
10544 let x = display_map.x_for_display_point(
10545 selection.head().to_display_point(&display_map),
10546 &text_layout_details,
10547 );
10548 let row = Point::new(target_row.0, 0)
10549 .to_display_point(&display_map)
10550 .row();
10551 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10552
10553 new_cursors.push((
10554 selection.id,
10555 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10556 SelectionGoal::None,
10557 ));
10558 edit_ranges.push(edit_start..edit_end);
10559 }
10560
10561 self.transact(window, cx, |this, window, cx| {
10562 let buffer = this.buffer.update(cx, |buffer, cx| {
10563 let empty_str: Arc<str> = Arc::default();
10564 buffer.edit(
10565 edit_ranges
10566 .into_iter()
10567 .map(|range| (range, empty_str.clone())),
10568 None,
10569 cx,
10570 );
10571 buffer.snapshot(cx)
10572 });
10573 let new_selections = new_cursors
10574 .into_iter()
10575 .map(|(id, cursor, goal)| {
10576 let cursor = cursor.to_point(&buffer);
10577 Selection {
10578 id,
10579 start: cursor,
10580 end: cursor,
10581 reversed: false,
10582 goal,
10583 }
10584 })
10585 .collect();
10586
10587 this.change_selections(Default::default(), window, cx, |s| {
10588 s.select(new_selections);
10589 });
10590 });
10591 }
10592
10593 pub fn join_lines_impl(
10594 &mut self,
10595 insert_whitespace: bool,
10596 window: &mut Window,
10597 cx: &mut Context<Self>,
10598 ) {
10599 if self.read_only(cx) {
10600 return;
10601 }
10602 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10603 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10604 let start = MultiBufferRow(selection.start.row);
10605 // Treat single line selections as if they include the next line. Otherwise this action
10606 // would do nothing for single line selections individual cursors.
10607 let end = if selection.start.row == selection.end.row {
10608 MultiBufferRow(selection.start.row + 1)
10609 } else {
10610 MultiBufferRow(selection.end.row)
10611 };
10612
10613 if let Some(last_row_range) = row_ranges.last_mut()
10614 && start <= last_row_range.end
10615 {
10616 last_row_range.end = end;
10617 continue;
10618 }
10619 row_ranges.push(start..end);
10620 }
10621
10622 let snapshot = self.buffer.read(cx).snapshot(cx);
10623 let mut cursor_positions = Vec::new();
10624 for row_range in &row_ranges {
10625 let anchor = snapshot.anchor_before(Point::new(
10626 row_range.end.previous_row().0,
10627 snapshot.line_len(row_range.end.previous_row()),
10628 ));
10629 cursor_positions.push(anchor..anchor);
10630 }
10631
10632 self.transact(window, cx, |this, window, cx| {
10633 for row_range in row_ranges.into_iter().rev() {
10634 for row in row_range.iter_rows().rev() {
10635 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10636 let next_line_row = row.next_row();
10637 let indent = snapshot.indent_size_for_line(next_line_row);
10638 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10639
10640 let replace =
10641 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10642 " "
10643 } else {
10644 ""
10645 };
10646
10647 this.buffer.update(cx, |buffer, cx| {
10648 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10649 });
10650 }
10651 }
10652
10653 this.change_selections(Default::default(), window, cx, |s| {
10654 s.select_anchor_ranges(cursor_positions)
10655 });
10656 });
10657 }
10658
10659 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10660 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10661 self.join_lines_impl(true, window, cx);
10662 }
10663
10664 pub fn sort_lines_case_sensitive(
10665 &mut self,
10666 _: &SortLinesCaseSensitive,
10667 window: &mut Window,
10668 cx: &mut Context<Self>,
10669 ) {
10670 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10671 }
10672
10673 pub fn sort_lines_by_length(
10674 &mut self,
10675 _: &SortLinesByLength,
10676 window: &mut Window,
10677 cx: &mut Context<Self>,
10678 ) {
10679 self.manipulate_immutable_lines(window, cx, |lines| {
10680 lines.sort_by_key(|&line| line.chars().count())
10681 })
10682 }
10683
10684 pub fn sort_lines_case_insensitive(
10685 &mut self,
10686 _: &SortLinesCaseInsensitive,
10687 window: &mut Window,
10688 cx: &mut Context<Self>,
10689 ) {
10690 self.manipulate_immutable_lines(window, cx, |lines| {
10691 lines.sort_by_key(|line| line.to_lowercase())
10692 })
10693 }
10694
10695 pub fn unique_lines_case_insensitive(
10696 &mut self,
10697 _: &UniqueLinesCaseInsensitive,
10698 window: &mut Window,
10699 cx: &mut Context<Self>,
10700 ) {
10701 self.manipulate_immutable_lines(window, cx, |lines| {
10702 let mut seen = HashSet::default();
10703 lines.retain(|line| seen.insert(line.to_lowercase()));
10704 })
10705 }
10706
10707 pub fn unique_lines_case_sensitive(
10708 &mut self,
10709 _: &UniqueLinesCaseSensitive,
10710 window: &mut Window,
10711 cx: &mut Context<Self>,
10712 ) {
10713 self.manipulate_immutable_lines(window, cx, |lines| {
10714 let mut seen = HashSet::default();
10715 lines.retain(|line| seen.insert(*line));
10716 })
10717 }
10718
10719 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10720 let snapshot = self.buffer.read(cx).snapshot(cx);
10721 for selection in self.selections.disjoint_anchors_arc().iter() {
10722 if snapshot
10723 .language_at(selection.start)
10724 .and_then(|lang| lang.config().wrap_characters.as_ref())
10725 .is_some()
10726 {
10727 return true;
10728 }
10729 }
10730 false
10731 }
10732
10733 fn wrap_selections_in_tag(
10734 &mut self,
10735 _: &WrapSelectionsInTag,
10736 window: &mut Window,
10737 cx: &mut Context<Self>,
10738 ) {
10739 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10740
10741 let snapshot = self.buffer.read(cx).snapshot(cx);
10742
10743 let mut edits = Vec::new();
10744 let mut boundaries = Vec::new();
10745
10746 for selection in self
10747 .selections
10748 .all_adjusted(&self.display_snapshot(cx))
10749 .iter()
10750 {
10751 let Some(wrap_config) = snapshot
10752 .language_at(selection.start)
10753 .and_then(|lang| lang.config().wrap_characters.clone())
10754 else {
10755 continue;
10756 };
10757
10758 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10759 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10760
10761 let start_before = snapshot.anchor_before(selection.start);
10762 let end_after = snapshot.anchor_after(selection.end);
10763
10764 edits.push((start_before..start_before, open_tag));
10765 edits.push((end_after..end_after, close_tag));
10766
10767 boundaries.push((
10768 start_before,
10769 end_after,
10770 wrap_config.start_prefix.len(),
10771 wrap_config.end_suffix.len(),
10772 ));
10773 }
10774
10775 if edits.is_empty() {
10776 return;
10777 }
10778
10779 self.transact(window, cx, |this, window, cx| {
10780 let buffer = this.buffer.update(cx, |buffer, cx| {
10781 buffer.edit(edits, None, cx);
10782 buffer.snapshot(cx)
10783 });
10784
10785 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10786 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10787 boundaries.into_iter()
10788 {
10789 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10790 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10791 new_selections.push(open_offset..open_offset);
10792 new_selections.push(close_offset..close_offset);
10793 }
10794
10795 this.change_selections(Default::default(), window, cx, |s| {
10796 s.select_ranges(new_selections);
10797 });
10798
10799 this.request_autoscroll(Autoscroll::fit(), cx);
10800 });
10801 }
10802
10803 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10804 let Some(project) = self.project.clone() else {
10805 return;
10806 };
10807 self.reload(project, window, cx)
10808 .detach_and_notify_err(window, cx);
10809 }
10810
10811 pub fn restore_file(
10812 &mut self,
10813 _: &::git::RestoreFile,
10814 window: &mut Window,
10815 cx: &mut Context<Self>,
10816 ) {
10817 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10818 let mut buffer_ids = HashSet::default();
10819 let snapshot = self.buffer().read(cx).snapshot(cx);
10820 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10821 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10822 }
10823
10824 let buffer = self.buffer().read(cx);
10825 let ranges = buffer_ids
10826 .into_iter()
10827 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10828 .collect::<Vec<_>>();
10829
10830 self.restore_hunks_in_ranges(ranges, window, cx);
10831 }
10832
10833 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10834 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10835 let selections = self
10836 .selections
10837 .all(&self.display_snapshot(cx))
10838 .into_iter()
10839 .map(|s| s.range())
10840 .collect();
10841 self.restore_hunks_in_ranges(selections, window, cx);
10842 }
10843
10844 pub fn restore_hunks_in_ranges(
10845 &mut self,
10846 ranges: Vec<Range<Point>>,
10847 window: &mut Window,
10848 cx: &mut Context<Editor>,
10849 ) {
10850 let mut revert_changes = HashMap::default();
10851 let chunk_by = self
10852 .snapshot(window, cx)
10853 .hunks_for_ranges(ranges)
10854 .into_iter()
10855 .chunk_by(|hunk| hunk.buffer_id);
10856 for (buffer_id, hunks) in &chunk_by {
10857 let hunks = hunks.collect::<Vec<_>>();
10858 for hunk in &hunks {
10859 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10860 }
10861 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10862 }
10863 drop(chunk_by);
10864 if !revert_changes.is_empty() {
10865 self.transact(window, cx, |editor, window, cx| {
10866 editor.restore(revert_changes, window, cx);
10867 });
10868 }
10869 }
10870
10871 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10872 if let Some(status) = self
10873 .addons
10874 .iter()
10875 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10876 {
10877 return Some(status);
10878 }
10879 self.project
10880 .as_ref()?
10881 .read(cx)
10882 .status_for_buffer_id(buffer_id, cx)
10883 }
10884
10885 pub fn open_active_item_in_terminal(
10886 &mut self,
10887 _: &OpenInTerminal,
10888 window: &mut Window,
10889 cx: &mut Context<Self>,
10890 ) {
10891 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10892 let project_path = buffer.read(cx).project_path(cx)?;
10893 let project = self.project()?.read(cx);
10894 let entry = project.entry_for_path(&project_path, cx)?;
10895 let parent = match &entry.canonical_path {
10896 Some(canonical_path) => canonical_path.to_path_buf(),
10897 None => project.absolute_path(&project_path, cx)?,
10898 }
10899 .parent()?
10900 .to_path_buf();
10901 Some(parent)
10902 }) {
10903 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10904 }
10905 }
10906
10907 fn set_breakpoint_context_menu(
10908 &mut self,
10909 display_row: DisplayRow,
10910 position: Option<Anchor>,
10911 clicked_point: gpui::Point<Pixels>,
10912 window: &mut Window,
10913 cx: &mut Context<Self>,
10914 ) {
10915 let source = self
10916 .buffer
10917 .read(cx)
10918 .snapshot(cx)
10919 .anchor_before(Point::new(display_row.0, 0u32));
10920
10921 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10922
10923 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10924 self,
10925 source,
10926 clicked_point,
10927 context_menu,
10928 window,
10929 cx,
10930 );
10931 }
10932
10933 fn add_edit_breakpoint_block(
10934 &mut self,
10935 anchor: Anchor,
10936 breakpoint: &Breakpoint,
10937 edit_action: BreakpointPromptEditAction,
10938 window: &mut Window,
10939 cx: &mut Context<Self>,
10940 ) {
10941 let weak_editor = cx.weak_entity();
10942 let bp_prompt = cx.new(|cx| {
10943 BreakpointPromptEditor::new(
10944 weak_editor,
10945 anchor,
10946 breakpoint.clone(),
10947 edit_action,
10948 window,
10949 cx,
10950 )
10951 });
10952
10953 let height = bp_prompt.update(cx, |this, cx| {
10954 this.prompt
10955 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10956 });
10957 let cloned_prompt = bp_prompt.clone();
10958 let blocks = vec![BlockProperties {
10959 style: BlockStyle::Sticky,
10960 placement: BlockPlacement::Above(anchor),
10961 height: Some(height),
10962 render: Arc::new(move |cx| {
10963 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10964 cloned_prompt.clone().into_any_element()
10965 }),
10966 priority: 0,
10967 }];
10968
10969 let focus_handle = bp_prompt.focus_handle(cx);
10970 window.focus(&focus_handle);
10971
10972 let block_ids = self.insert_blocks(blocks, None, cx);
10973 bp_prompt.update(cx, |prompt, _| {
10974 prompt.add_block_ids(block_ids);
10975 });
10976 }
10977
10978 pub(crate) fn breakpoint_at_row(
10979 &self,
10980 row: u32,
10981 window: &mut Window,
10982 cx: &mut Context<Self>,
10983 ) -> Option<(Anchor, Breakpoint)> {
10984 let snapshot = self.snapshot(window, cx);
10985 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10986
10987 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10988 }
10989
10990 pub(crate) fn breakpoint_at_anchor(
10991 &self,
10992 breakpoint_position: Anchor,
10993 snapshot: &EditorSnapshot,
10994 cx: &mut Context<Self>,
10995 ) -> Option<(Anchor, Breakpoint)> {
10996 let buffer = self
10997 .buffer
10998 .read(cx)
10999 .buffer_for_anchor(breakpoint_position, cx)?;
11000
11001 let enclosing_excerpt = breakpoint_position.excerpt_id;
11002 let buffer_snapshot = buffer.read(cx).snapshot();
11003
11004 let row = buffer_snapshot
11005 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11006 .row;
11007
11008 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11009 let anchor_end = snapshot
11010 .buffer_snapshot()
11011 .anchor_after(Point::new(row, line_len));
11012
11013 self.breakpoint_store
11014 .as_ref()?
11015 .read_with(cx, |breakpoint_store, cx| {
11016 breakpoint_store
11017 .breakpoints(
11018 &buffer,
11019 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11020 &buffer_snapshot,
11021 cx,
11022 )
11023 .next()
11024 .and_then(|(bp, _)| {
11025 let breakpoint_row = buffer_snapshot
11026 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11027 .row;
11028
11029 if breakpoint_row == row {
11030 snapshot
11031 .buffer_snapshot()
11032 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11033 .map(|position| (position, bp.bp.clone()))
11034 } else {
11035 None
11036 }
11037 })
11038 })
11039 }
11040
11041 pub fn edit_log_breakpoint(
11042 &mut self,
11043 _: &EditLogBreakpoint,
11044 window: &mut Window,
11045 cx: &mut Context<Self>,
11046 ) {
11047 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11048 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11049 message: None,
11050 state: BreakpointState::Enabled,
11051 condition: None,
11052 hit_condition: None,
11053 });
11054
11055 self.add_edit_breakpoint_block(
11056 anchor,
11057 &breakpoint,
11058 BreakpointPromptEditAction::Log,
11059 window,
11060 cx,
11061 );
11062 }
11063 }
11064
11065 fn breakpoints_at_cursors(
11066 &self,
11067 window: &mut Window,
11068 cx: &mut Context<Self>,
11069 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11070 let snapshot = self.snapshot(window, cx);
11071 let cursors = self
11072 .selections
11073 .disjoint_anchors_arc()
11074 .iter()
11075 .map(|selection| {
11076 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11077
11078 let breakpoint_position = self
11079 .breakpoint_at_row(cursor_position.row, window, cx)
11080 .map(|bp| bp.0)
11081 .unwrap_or_else(|| {
11082 snapshot
11083 .display_snapshot
11084 .buffer_snapshot()
11085 .anchor_after(Point::new(cursor_position.row, 0))
11086 });
11087
11088 let breakpoint = self
11089 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11090 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11091
11092 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11093 })
11094 // 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.
11095 .collect::<HashMap<Anchor, _>>();
11096
11097 cursors.into_iter().collect()
11098 }
11099
11100 pub fn enable_breakpoint(
11101 &mut self,
11102 _: &crate::actions::EnableBreakpoint,
11103 window: &mut Window,
11104 cx: &mut Context<Self>,
11105 ) {
11106 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11107 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11108 continue;
11109 };
11110 self.edit_breakpoint_at_anchor(
11111 anchor,
11112 breakpoint,
11113 BreakpointEditAction::InvertState,
11114 cx,
11115 );
11116 }
11117 }
11118
11119 pub fn disable_breakpoint(
11120 &mut self,
11121 _: &crate::actions::DisableBreakpoint,
11122 window: &mut Window,
11123 cx: &mut Context<Self>,
11124 ) {
11125 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11126 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11127 continue;
11128 };
11129 self.edit_breakpoint_at_anchor(
11130 anchor,
11131 breakpoint,
11132 BreakpointEditAction::InvertState,
11133 cx,
11134 );
11135 }
11136 }
11137
11138 pub fn toggle_breakpoint(
11139 &mut self,
11140 _: &crate::actions::ToggleBreakpoint,
11141 window: &mut Window,
11142 cx: &mut Context<Self>,
11143 ) {
11144 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11145 if let Some(breakpoint) = breakpoint {
11146 self.edit_breakpoint_at_anchor(
11147 anchor,
11148 breakpoint,
11149 BreakpointEditAction::Toggle,
11150 cx,
11151 );
11152 } else {
11153 self.edit_breakpoint_at_anchor(
11154 anchor,
11155 Breakpoint::new_standard(),
11156 BreakpointEditAction::Toggle,
11157 cx,
11158 );
11159 }
11160 }
11161 }
11162
11163 pub fn edit_breakpoint_at_anchor(
11164 &mut self,
11165 breakpoint_position: Anchor,
11166 breakpoint: Breakpoint,
11167 edit_action: BreakpointEditAction,
11168 cx: &mut Context<Self>,
11169 ) {
11170 let Some(breakpoint_store) = &self.breakpoint_store else {
11171 return;
11172 };
11173
11174 let Some(buffer) = self
11175 .buffer
11176 .read(cx)
11177 .buffer_for_anchor(breakpoint_position, cx)
11178 else {
11179 return;
11180 };
11181
11182 breakpoint_store.update(cx, |breakpoint_store, cx| {
11183 breakpoint_store.toggle_breakpoint(
11184 buffer,
11185 BreakpointWithPosition {
11186 position: breakpoint_position.text_anchor,
11187 bp: breakpoint,
11188 },
11189 edit_action,
11190 cx,
11191 );
11192 });
11193
11194 cx.notify();
11195 }
11196
11197 #[cfg(any(test, feature = "test-support"))]
11198 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11199 self.breakpoint_store.clone()
11200 }
11201
11202 pub fn prepare_restore_change(
11203 &self,
11204 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11205 hunk: &MultiBufferDiffHunk,
11206 cx: &mut App,
11207 ) -> Option<()> {
11208 if hunk.is_created_file() {
11209 return None;
11210 }
11211 let buffer = self.buffer.read(cx);
11212 let diff = buffer.diff_for(hunk.buffer_id)?;
11213 let buffer = buffer.buffer(hunk.buffer_id)?;
11214 let buffer = buffer.read(cx);
11215 let original_text = diff
11216 .read(cx)
11217 .base_text()
11218 .as_rope()
11219 .slice(hunk.diff_base_byte_range.clone());
11220 let buffer_snapshot = buffer.snapshot();
11221 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11222 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11223 probe
11224 .0
11225 .start
11226 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11227 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11228 }) {
11229 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11230 Some(())
11231 } else {
11232 None
11233 }
11234 }
11235
11236 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11237 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11238 }
11239
11240 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11241 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11242 }
11243
11244 fn manipulate_lines<M>(
11245 &mut self,
11246 window: &mut Window,
11247 cx: &mut Context<Self>,
11248 mut manipulate: M,
11249 ) where
11250 M: FnMut(&str) -> LineManipulationResult,
11251 {
11252 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11253
11254 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11255 let buffer = self.buffer.read(cx).snapshot(cx);
11256
11257 let mut edits = Vec::new();
11258
11259 let selections = self.selections.all::<Point>(&display_map);
11260 let mut selections = selections.iter().peekable();
11261 let mut contiguous_row_selections = Vec::new();
11262 let mut new_selections = Vec::new();
11263 let mut added_lines = 0;
11264 let mut removed_lines = 0;
11265
11266 while let Some(selection) = selections.next() {
11267 let (start_row, end_row) = consume_contiguous_rows(
11268 &mut contiguous_row_selections,
11269 selection,
11270 &display_map,
11271 &mut selections,
11272 );
11273
11274 let start_point = Point::new(start_row.0, 0);
11275 let end_point = Point::new(
11276 end_row.previous_row().0,
11277 buffer.line_len(end_row.previous_row()),
11278 );
11279 let text = buffer
11280 .text_for_range(start_point..end_point)
11281 .collect::<String>();
11282
11283 let LineManipulationResult {
11284 new_text,
11285 line_count_before,
11286 line_count_after,
11287 } = manipulate(&text);
11288
11289 edits.push((start_point..end_point, new_text));
11290
11291 // Selections must change based on added and removed line count
11292 let start_row =
11293 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11294 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11295 new_selections.push(Selection {
11296 id: selection.id,
11297 start: start_row,
11298 end: end_row,
11299 goal: SelectionGoal::None,
11300 reversed: selection.reversed,
11301 });
11302
11303 if line_count_after > line_count_before {
11304 added_lines += line_count_after - line_count_before;
11305 } else if line_count_before > line_count_after {
11306 removed_lines += line_count_before - line_count_after;
11307 }
11308 }
11309
11310 self.transact(window, cx, |this, window, cx| {
11311 let buffer = this.buffer.update(cx, |buffer, cx| {
11312 buffer.edit(edits, None, cx);
11313 buffer.snapshot(cx)
11314 });
11315
11316 // Recalculate offsets on newly edited buffer
11317 let new_selections = new_selections
11318 .iter()
11319 .map(|s| {
11320 let start_point = Point::new(s.start.0, 0);
11321 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11322 Selection {
11323 id: s.id,
11324 start: buffer.point_to_offset(start_point),
11325 end: buffer.point_to_offset(end_point),
11326 goal: s.goal,
11327 reversed: s.reversed,
11328 }
11329 })
11330 .collect();
11331
11332 this.change_selections(Default::default(), window, cx, |s| {
11333 s.select(new_selections);
11334 });
11335
11336 this.request_autoscroll(Autoscroll::fit(), cx);
11337 });
11338 }
11339
11340 fn manipulate_immutable_lines<Fn>(
11341 &mut self,
11342 window: &mut Window,
11343 cx: &mut Context<Self>,
11344 mut callback: Fn,
11345 ) where
11346 Fn: FnMut(&mut Vec<&str>),
11347 {
11348 self.manipulate_lines(window, cx, |text| {
11349 let mut lines: Vec<&str> = text.split('\n').collect();
11350 let line_count_before = lines.len();
11351
11352 callback(&mut lines);
11353
11354 LineManipulationResult {
11355 new_text: lines.join("\n"),
11356 line_count_before,
11357 line_count_after: lines.len(),
11358 }
11359 });
11360 }
11361
11362 fn manipulate_mutable_lines<Fn>(
11363 &mut self,
11364 window: &mut Window,
11365 cx: &mut Context<Self>,
11366 mut callback: Fn,
11367 ) where
11368 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11369 {
11370 self.manipulate_lines(window, cx, |text| {
11371 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11372 let line_count_before = lines.len();
11373
11374 callback(&mut lines);
11375
11376 LineManipulationResult {
11377 new_text: lines.join("\n"),
11378 line_count_before,
11379 line_count_after: lines.len(),
11380 }
11381 });
11382 }
11383
11384 pub fn convert_indentation_to_spaces(
11385 &mut self,
11386 _: &ConvertIndentationToSpaces,
11387 window: &mut Window,
11388 cx: &mut Context<Self>,
11389 ) {
11390 let settings = self.buffer.read(cx).language_settings(cx);
11391 let tab_size = settings.tab_size.get() as usize;
11392
11393 self.manipulate_mutable_lines(window, cx, |lines| {
11394 // Allocates a reasonably sized scratch buffer once for the whole loop
11395 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11396 // Avoids recomputing spaces that could be inserted many times
11397 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11398 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11399 .collect();
11400
11401 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11402 let mut chars = line.as_ref().chars();
11403 let mut col = 0;
11404 let mut changed = false;
11405
11406 for ch in chars.by_ref() {
11407 match ch {
11408 ' ' => {
11409 reindented_line.push(' ');
11410 col += 1;
11411 }
11412 '\t' => {
11413 // \t are converted to spaces depending on the current column
11414 let spaces_len = tab_size - (col % tab_size);
11415 reindented_line.extend(&space_cache[spaces_len - 1]);
11416 col += spaces_len;
11417 changed = true;
11418 }
11419 _ => {
11420 // If we dont append before break, the character is consumed
11421 reindented_line.push(ch);
11422 break;
11423 }
11424 }
11425 }
11426
11427 if !changed {
11428 reindented_line.clear();
11429 continue;
11430 }
11431 // Append the rest of the line and replace old reference with new one
11432 reindented_line.extend(chars);
11433 *line = Cow::Owned(reindented_line.clone());
11434 reindented_line.clear();
11435 }
11436 });
11437 }
11438
11439 pub fn convert_indentation_to_tabs(
11440 &mut self,
11441 _: &ConvertIndentationToTabs,
11442 window: &mut Window,
11443 cx: &mut Context<Self>,
11444 ) {
11445 let settings = self.buffer.read(cx).language_settings(cx);
11446 let tab_size = settings.tab_size.get() as usize;
11447
11448 self.manipulate_mutable_lines(window, cx, |lines| {
11449 // Allocates a reasonably sized buffer once for the whole loop
11450 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11451 // Avoids recomputing spaces that could be inserted many times
11452 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11453 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11454 .collect();
11455
11456 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11457 let mut chars = line.chars();
11458 let mut spaces_count = 0;
11459 let mut first_non_indent_char = None;
11460 let mut changed = false;
11461
11462 for ch in chars.by_ref() {
11463 match ch {
11464 ' ' => {
11465 // Keep track of spaces. Append \t when we reach tab_size
11466 spaces_count += 1;
11467 changed = true;
11468 if spaces_count == tab_size {
11469 reindented_line.push('\t');
11470 spaces_count = 0;
11471 }
11472 }
11473 '\t' => {
11474 reindented_line.push('\t');
11475 spaces_count = 0;
11476 }
11477 _ => {
11478 // Dont append it yet, we might have remaining spaces
11479 first_non_indent_char = Some(ch);
11480 break;
11481 }
11482 }
11483 }
11484
11485 if !changed {
11486 reindented_line.clear();
11487 continue;
11488 }
11489 // Remaining spaces that didn't make a full tab stop
11490 if spaces_count > 0 {
11491 reindented_line.extend(&space_cache[spaces_count - 1]);
11492 }
11493 // If we consume an extra character that was not indentation, add it back
11494 if let Some(extra_char) = first_non_indent_char {
11495 reindented_line.push(extra_char);
11496 }
11497 // Append the rest of the line and replace old reference with new one
11498 reindented_line.extend(chars);
11499 *line = Cow::Owned(reindented_line.clone());
11500 reindented_line.clear();
11501 }
11502 });
11503 }
11504
11505 pub fn convert_to_upper_case(
11506 &mut self,
11507 _: &ConvertToUpperCase,
11508 window: &mut Window,
11509 cx: &mut Context<Self>,
11510 ) {
11511 self.manipulate_text(window, cx, |text| text.to_uppercase())
11512 }
11513
11514 pub fn convert_to_lower_case(
11515 &mut self,
11516 _: &ConvertToLowerCase,
11517 window: &mut Window,
11518 cx: &mut Context<Self>,
11519 ) {
11520 self.manipulate_text(window, cx, |text| text.to_lowercase())
11521 }
11522
11523 pub fn convert_to_title_case(
11524 &mut self,
11525 _: &ConvertToTitleCase,
11526 window: &mut Window,
11527 cx: &mut Context<Self>,
11528 ) {
11529 self.manipulate_text(window, cx, |text| {
11530 text.split('\n')
11531 .map(|line| line.to_case(Case::Title))
11532 .join("\n")
11533 })
11534 }
11535
11536 pub fn convert_to_snake_case(
11537 &mut self,
11538 _: &ConvertToSnakeCase,
11539 window: &mut Window,
11540 cx: &mut Context<Self>,
11541 ) {
11542 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11543 }
11544
11545 pub fn convert_to_kebab_case(
11546 &mut self,
11547 _: &ConvertToKebabCase,
11548 window: &mut Window,
11549 cx: &mut Context<Self>,
11550 ) {
11551 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11552 }
11553
11554 pub fn convert_to_upper_camel_case(
11555 &mut self,
11556 _: &ConvertToUpperCamelCase,
11557 window: &mut Window,
11558 cx: &mut Context<Self>,
11559 ) {
11560 self.manipulate_text(window, cx, |text| {
11561 text.split('\n')
11562 .map(|line| line.to_case(Case::UpperCamel))
11563 .join("\n")
11564 })
11565 }
11566
11567 pub fn convert_to_lower_camel_case(
11568 &mut self,
11569 _: &ConvertToLowerCamelCase,
11570 window: &mut Window,
11571 cx: &mut Context<Self>,
11572 ) {
11573 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11574 }
11575
11576 pub fn convert_to_opposite_case(
11577 &mut self,
11578 _: &ConvertToOppositeCase,
11579 window: &mut Window,
11580 cx: &mut Context<Self>,
11581 ) {
11582 self.manipulate_text(window, cx, |text| {
11583 text.chars()
11584 .fold(String::with_capacity(text.len()), |mut t, c| {
11585 if c.is_uppercase() {
11586 t.extend(c.to_lowercase());
11587 } else {
11588 t.extend(c.to_uppercase());
11589 }
11590 t
11591 })
11592 })
11593 }
11594
11595 pub fn convert_to_sentence_case(
11596 &mut self,
11597 _: &ConvertToSentenceCase,
11598 window: &mut Window,
11599 cx: &mut Context<Self>,
11600 ) {
11601 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11602 }
11603
11604 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11605 self.manipulate_text(window, cx, |text| {
11606 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11607 if has_upper_case_characters {
11608 text.to_lowercase()
11609 } else {
11610 text.to_uppercase()
11611 }
11612 })
11613 }
11614
11615 pub fn convert_to_rot13(
11616 &mut self,
11617 _: &ConvertToRot13,
11618 window: &mut Window,
11619 cx: &mut Context<Self>,
11620 ) {
11621 self.manipulate_text(window, cx, |text| {
11622 text.chars()
11623 .map(|c| match c {
11624 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11625 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11626 _ => c,
11627 })
11628 .collect()
11629 })
11630 }
11631
11632 pub fn convert_to_rot47(
11633 &mut self,
11634 _: &ConvertToRot47,
11635 window: &mut Window,
11636 cx: &mut Context<Self>,
11637 ) {
11638 self.manipulate_text(window, cx, |text| {
11639 text.chars()
11640 .map(|c| {
11641 let code_point = c as u32;
11642 if code_point >= 33 && code_point <= 126 {
11643 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11644 }
11645 c
11646 })
11647 .collect()
11648 })
11649 }
11650
11651 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11652 where
11653 Fn: FnMut(&str) -> String,
11654 {
11655 let buffer = self.buffer.read(cx).snapshot(cx);
11656
11657 let mut new_selections = Vec::new();
11658 let mut edits = Vec::new();
11659 let mut selection_adjustment = 0i32;
11660
11661 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11662 let selection_is_empty = selection.is_empty();
11663
11664 let (start, end) = if selection_is_empty {
11665 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11666 (word_range.start, word_range.end)
11667 } else {
11668 (
11669 buffer.point_to_offset(selection.start),
11670 buffer.point_to_offset(selection.end),
11671 )
11672 };
11673
11674 let text = buffer.text_for_range(start..end).collect::<String>();
11675 let old_length = text.len() as i32;
11676 let text = callback(&text);
11677
11678 new_selections.push(Selection {
11679 start: (start as i32 - selection_adjustment) as usize,
11680 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11681 goal: SelectionGoal::None,
11682 id: selection.id,
11683 reversed: selection.reversed,
11684 });
11685
11686 selection_adjustment += old_length - text.len() as i32;
11687
11688 edits.push((start..end, text));
11689 }
11690
11691 self.transact(window, cx, |this, window, cx| {
11692 this.buffer.update(cx, |buffer, cx| {
11693 buffer.edit(edits, None, cx);
11694 });
11695
11696 this.change_selections(Default::default(), window, cx, |s| {
11697 s.select(new_selections);
11698 });
11699
11700 this.request_autoscroll(Autoscroll::fit(), cx);
11701 });
11702 }
11703
11704 pub fn move_selection_on_drop(
11705 &mut self,
11706 selection: &Selection<Anchor>,
11707 target: DisplayPoint,
11708 is_cut: bool,
11709 window: &mut Window,
11710 cx: &mut Context<Self>,
11711 ) {
11712 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11713 let buffer = display_map.buffer_snapshot();
11714 let mut edits = Vec::new();
11715 let insert_point = display_map
11716 .clip_point(target, Bias::Left)
11717 .to_point(&display_map);
11718 let text = buffer
11719 .text_for_range(selection.start..selection.end)
11720 .collect::<String>();
11721 if is_cut {
11722 edits.push(((selection.start..selection.end), String::new()));
11723 }
11724 let insert_anchor = buffer.anchor_before(insert_point);
11725 edits.push(((insert_anchor..insert_anchor), text));
11726 let last_edit_start = insert_anchor.bias_left(buffer);
11727 let last_edit_end = insert_anchor.bias_right(buffer);
11728 self.transact(window, cx, |this, window, cx| {
11729 this.buffer.update(cx, |buffer, cx| {
11730 buffer.edit(edits, None, cx);
11731 });
11732 this.change_selections(Default::default(), window, cx, |s| {
11733 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11734 });
11735 });
11736 }
11737
11738 pub fn clear_selection_drag_state(&mut self) {
11739 self.selection_drag_state = SelectionDragState::None;
11740 }
11741
11742 pub fn duplicate(
11743 &mut self,
11744 upwards: bool,
11745 whole_lines: bool,
11746 window: &mut Window,
11747 cx: &mut Context<Self>,
11748 ) {
11749 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11750
11751 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11752 let buffer = display_map.buffer_snapshot();
11753 let selections = self.selections.all::<Point>(&display_map);
11754
11755 let mut edits = Vec::new();
11756 let mut selections_iter = selections.iter().peekable();
11757 while let Some(selection) = selections_iter.next() {
11758 let mut rows = selection.spanned_rows(false, &display_map);
11759 // duplicate line-wise
11760 if whole_lines || selection.start == selection.end {
11761 // Avoid duplicating the same lines twice.
11762 while let Some(next_selection) = selections_iter.peek() {
11763 let next_rows = next_selection.spanned_rows(false, &display_map);
11764 if next_rows.start < rows.end {
11765 rows.end = next_rows.end;
11766 selections_iter.next().unwrap();
11767 } else {
11768 break;
11769 }
11770 }
11771
11772 // Copy the text from the selected row region and splice it either at the start
11773 // or end of the region.
11774 let start = Point::new(rows.start.0, 0);
11775 let end = Point::new(
11776 rows.end.previous_row().0,
11777 buffer.line_len(rows.end.previous_row()),
11778 );
11779
11780 let mut text = buffer.text_for_range(start..end).collect::<String>();
11781
11782 let insert_location = if upwards {
11783 // When duplicating upward, we need to insert before the current line.
11784 // If we're on the last line and it doesn't end with a newline,
11785 // we need to add a newline before the duplicated content.
11786 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11787 && buffer.max_point().column > 0
11788 && !text.ends_with('\n');
11789
11790 if needs_leading_newline {
11791 text.insert(0, '\n');
11792 end
11793 } else {
11794 text.push('\n');
11795 Point::new(rows.start.0, 0)
11796 }
11797 } else {
11798 text.push('\n');
11799 start
11800 };
11801 edits.push((insert_location..insert_location, text));
11802 } else {
11803 // duplicate character-wise
11804 let start = selection.start;
11805 let end = selection.end;
11806 let text = buffer.text_for_range(start..end).collect::<String>();
11807 edits.push((selection.end..selection.end, text));
11808 }
11809 }
11810
11811 self.transact(window, cx, |this, window, cx| {
11812 this.buffer.update(cx, |buffer, cx| {
11813 buffer.edit(edits, None, cx);
11814 });
11815
11816 // When duplicating upward with whole lines, move the cursor to the duplicated line
11817 if upwards && whole_lines {
11818 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11819
11820 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11821 let mut new_ranges = Vec::new();
11822 let selections = s.all::<Point>(&display_map);
11823 let mut selections_iter = selections.iter().peekable();
11824
11825 while let Some(first_selection) = selections_iter.next() {
11826 // Group contiguous selections together to find the total row span
11827 let mut group_selections = vec![first_selection];
11828 let mut rows = first_selection.spanned_rows(false, &display_map);
11829
11830 while let Some(next_selection) = selections_iter.peek() {
11831 let next_rows = next_selection.spanned_rows(false, &display_map);
11832 if next_rows.start < rows.end {
11833 rows.end = next_rows.end;
11834 group_selections.push(selections_iter.next().unwrap());
11835 } else {
11836 break;
11837 }
11838 }
11839
11840 let row_count = rows.end.0 - rows.start.0;
11841
11842 // Move all selections in this group up by the total number of duplicated rows
11843 for selection in group_selections {
11844 let new_start = Point::new(
11845 selection.start.row.saturating_sub(row_count),
11846 selection.start.column,
11847 );
11848
11849 let new_end = Point::new(
11850 selection.end.row.saturating_sub(row_count),
11851 selection.end.column,
11852 );
11853
11854 new_ranges.push(new_start..new_end);
11855 }
11856 }
11857
11858 s.select_ranges(new_ranges);
11859 });
11860 }
11861
11862 this.request_autoscroll(Autoscroll::fit(), cx);
11863 });
11864 }
11865
11866 pub fn duplicate_line_up(
11867 &mut self,
11868 _: &DuplicateLineUp,
11869 window: &mut Window,
11870 cx: &mut Context<Self>,
11871 ) {
11872 self.duplicate(true, true, window, cx);
11873 }
11874
11875 pub fn duplicate_line_down(
11876 &mut self,
11877 _: &DuplicateLineDown,
11878 window: &mut Window,
11879 cx: &mut Context<Self>,
11880 ) {
11881 self.duplicate(false, true, window, cx);
11882 }
11883
11884 pub fn duplicate_selection(
11885 &mut self,
11886 _: &DuplicateSelection,
11887 window: &mut Window,
11888 cx: &mut Context<Self>,
11889 ) {
11890 self.duplicate(false, false, window, cx);
11891 }
11892
11893 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11894 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11895 if self.mode.is_single_line() {
11896 cx.propagate();
11897 return;
11898 }
11899
11900 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11901 let buffer = self.buffer.read(cx).snapshot(cx);
11902
11903 let mut edits = Vec::new();
11904 let mut unfold_ranges = Vec::new();
11905 let mut refold_creases = Vec::new();
11906
11907 let selections = self.selections.all::<Point>(&display_map);
11908 let mut selections = selections.iter().peekable();
11909 let mut contiguous_row_selections = Vec::new();
11910 let mut new_selections = Vec::new();
11911
11912 while let Some(selection) = selections.next() {
11913 // Find all the selections that span a contiguous row range
11914 let (start_row, end_row) = consume_contiguous_rows(
11915 &mut contiguous_row_selections,
11916 selection,
11917 &display_map,
11918 &mut selections,
11919 );
11920
11921 // Move the text spanned by the row range to be before the line preceding the row range
11922 if start_row.0 > 0 {
11923 let range_to_move = Point::new(
11924 start_row.previous_row().0,
11925 buffer.line_len(start_row.previous_row()),
11926 )
11927 ..Point::new(
11928 end_row.previous_row().0,
11929 buffer.line_len(end_row.previous_row()),
11930 );
11931 let insertion_point = display_map
11932 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11933 .0;
11934
11935 // Don't move lines across excerpts
11936 if buffer
11937 .excerpt_containing(insertion_point..range_to_move.end)
11938 .is_some()
11939 {
11940 let text = buffer
11941 .text_for_range(range_to_move.clone())
11942 .flat_map(|s| s.chars())
11943 .skip(1)
11944 .chain(['\n'])
11945 .collect::<String>();
11946
11947 edits.push((
11948 buffer.anchor_after(range_to_move.start)
11949 ..buffer.anchor_before(range_to_move.end),
11950 String::new(),
11951 ));
11952 let insertion_anchor = buffer.anchor_after(insertion_point);
11953 edits.push((insertion_anchor..insertion_anchor, text));
11954
11955 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11956
11957 // Move selections up
11958 new_selections.extend(contiguous_row_selections.drain(..).map(
11959 |mut selection| {
11960 selection.start.row -= row_delta;
11961 selection.end.row -= row_delta;
11962 selection
11963 },
11964 ));
11965
11966 // Move folds up
11967 unfold_ranges.push(range_to_move.clone());
11968 for fold in display_map.folds_in_range(
11969 buffer.anchor_before(range_to_move.start)
11970 ..buffer.anchor_after(range_to_move.end),
11971 ) {
11972 let mut start = fold.range.start.to_point(&buffer);
11973 let mut end = fold.range.end.to_point(&buffer);
11974 start.row -= row_delta;
11975 end.row -= row_delta;
11976 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11977 }
11978 }
11979 }
11980
11981 // If we didn't move line(s), preserve the existing selections
11982 new_selections.append(&mut contiguous_row_selections);
11983 }
11984
11985 self.transact(window, cx, |this, window, cx| {
11986 this.unfold_ranges(&unfold_ranges, true, true, cx);
11987 this.buffer.update(cx, |buffer, cx| {
11988 for (range, text) in edits {
11989 buffer.edit([(range, text)], None, cx);
11990 }
11991 });
11992 this.fold_creases(refold_creases, true, window, cx);
11993 this.change_selections(Default::default(), window, cx, |s| {
11994 s.select(new_selections);
11995 })
11996 });
11997 }
11998
11999 pub fn move_line_down(
12000 &mut self,
12001 _: &MoveLineDown,
12002 window: &mut Window,
12003 cx: &mut Context<Self>,
12004 ) {
12005 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12006 if self.mode.is_single_line() {
12007 cx.propagate();
12008 return;
12009 }
12010
12011 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12012 let buffer = self.buffer.read(cx).snapshot(cx);
12013
12014 let mut edits = Vec::new();
12015 let mut unfold_ranges = Vec::new();
12016 let mut refold_creases = Vec::new();
12017
12018 let selections = self.selections.all::<Point>(&display_map);
12019 let mut selections = selections.iter().peekable();
12020 let mut contiguous_row_selections = Vec::new();
12021 let mut new_selections = Vec::new();
12022
12023 while let Some(selection) = selections.next() {
12024 // Find all the selections that span a contiguous row range
12025 let (start_row, end_row) = consume_contiguous_rows(
12026 &mut contiguous_row_selections,
12027 selection,
12028 &display_map,
12029 &mut selections,
12030 );
12031
12032 // Move the text spanned by the row range to be after the last line of the row range
12033 if end_row.0 <= buffer.max_point().row {
12034 let range_to_move =
12035 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12036 let insertion_point = display_map
12037 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12038 .0;
12039
12040 // Don't move lines across excerpt boundaries
12041 if buffer
12042 .excerpt_containing(range_to_move.start..insertion_point)
12043 .is_some()
12044 {
12045 let mut text = String::from("\n");
12046 text.extend(buffer.text_for_range(range_to_move.clone()));
12047 text.pop(); // Drop trailing newline
12048 edits.push((
12049 buffer.anchor_after(range_to_move.start)
12050 ..buffer.anchor_before(range_to_move.end),
12051 String::new(),
12052 ));
12053 let insertion_anchor = buffer.anchor_after(insertion_point);
12054 edits.push((insertion_anchor..insertion_anchor, text));
12055
12056 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12057
12058 // Move selections down
12059 new_selections.extend(contiguous_row_selections.drain(..).map(
12060 |mut selection| {
12061 selection.start.row += row_delta;
12062 selection.end.row += row_delta;
12063 selection
12064 },
12065 ));
12066
12067 // Move folds down
12068 unfold_ranges.push(range_to_move.clone());
12069 for fold in display_map.folds_in_range(
12070 buffer.anchor_before(range_to_move.start)
12071 ..buffer.anchor_after(range_to_move.end),
12072 ) {
12073 let mut start = fold.range.start.to_point(&buffer);
12074 let mut end = fold.range.end.to_point(&buffer);
12075 start.row += row_delta;
12076 end.row += row_delta;
12077 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12078 }
12079 }
12080 }
12081
12082 // If we didn't move line(s), preserve the existing selections
12083 new_selections.append(&mut contiguous_row_selections);
12084 }
12085
12086 self.transact(window, cx, |this, window, cx| {
12087 this.unfold_ranges(&unfold_ranges, true, true, cx);
12088 this.buffer.update(cx, |buffer, cx| {
12089 for (range, text) in edits {
12090 buffer.edit([(range, text)], None, cx);
12091 }
12092 });
12093 this.fold_creases(refold_creases, true, window, cx);
12094 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12095 });
12096 }
12097
12098 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12099 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12100 let text_layout_details = &self.text_layout_details(window);
12101 self.transact(window, cx, |this, window, cx| {
12102 let edits = this.change_selections(Default::default(), window, cx, |s| {
12103 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12104 s.move_with(|display_map, selection| {
12105 if !selection.is_empty() {
12106 return;
12107 }
12108
12109 let mut head = selection.head();
12110 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12111 if head.column() == display_map.line_len(head.row()) {
12112 transpose_offset = display_map
12113 .buffer_snapshot()
12114 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12115 }
12116
12117 if transpose_offset == 0 {
12118 return;
12119 }
12120
12121 *head.column_mut() += 1;
12122 head = display_map.clip_point(head, Bias::Right);
12123 let goal = SelectionGoal::HorizontalPosition(
12124 display_map
12125 .x_for_display_point(head, text_layout_details)
12126 .into(),
12127 );
12128 selection.collapse_to(head, goal);
12129
12130 let transpose_start = display_map
12131 .buffer_snapshot()
12132 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12133 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12134 let transpose_end = display_map
12135 .buffer_snapshot()
12136 .clip_offset(transpose_offset + 1, Bias::Right);
12137 if let Some(ch) = display_map
12138 .buffer_snapshot()
12139 .chars_at(transpose_start)
12140 .next()
12141 {
12142 edits.push((transpose_start..transpose_offset, String::new()));
12143 edits.push((transpose_end..transpose_end, ch.to_string()));
12144 }
12145 }
12146 });
12147 edits
12148 });
12149 this.buffer
12150 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12151 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12152 this.change_selections(Default::default(), window, cx, |s| {
12153 s.select(selections);
12154 });
12155 });
12156 }
12157
12158 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12159 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12160 if self.mode.is_single_line() {
12161 cx.propagate();
12162 return;
12163 }
12164
12165 self.rewrap_impl(RewrapOptions::default(), cx)
12166 }
12167
12168 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12169 let buffer = self.buffer.read(cx).snapshot(cx);
12170 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12171
12172 #[derive(Clone, Debug, PartialEq)]
12173 enum CommentFormat {
12174 /// single line comment, with prefix for line
12175 Line(String),
12176 /// single line within a block comment, with prefix for line
12177 BlockLine(String),
12178 /// a single line of a block comment that includes the initial delimiter
12179 BlockCommentWithStart(BlockCommentConfig),
12180 /// a single line of a block comment that includes the ending delimiter
12181 BlockCommentWithEnd(BlockCommentConfig),
12182 }
12183
12184 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12185 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12186 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12187 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12188 .peekable();
12189
12190 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12191 row
12192 } else {
12193 return Vec::new();
12194 };
12195
12196 let language_settings = buffer.language_settings_at(selection.head(), cx);
12197 let language_scope = buffer.language_scope_at(selection.head());
12198
12199 let indent_and_prefix_for_row =
12200 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12201 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12202 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12203 &language_scope
12204 {
12205 let indent_end = Point::new(row, indent.len);
12206 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12207 let line_text_after_indent = buffer
12208 .text_for_range(indent_end..line_end)
12209 .collect::<String>();
12210
12211 let is_within_comment_override = buffer
12212 .language_scope_at(indent_end)
12213 .is_some_and(|scope| scope.override_name() == Some("comment"));
12214 let comment_delimiters = if is_within_comment_override {
12215 // we are within a comment syntax node, but we don't
12216 // yet know what kind of comment: block, doc or line
12217 match (
12218 language_scope.documentation_comment(),
12219 language_scope.block_comment(),
12220 ) {
12221 (Some(config), _) | (_, Some(config))
12222 if buffer.contains_str_at(indent_end, &config.start) =>
12223 {
12224 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12225 }
12226 (Some(config), _) | (_, Some(config))
12227 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12228 {
12229 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12230 }
12231 (Some(config), _) | (_, Some(config))
12232 if buffer.contains_str_at(indent_end, &config.prefix) =>
12233 {
12234 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12235 }
12236 (_, _) => language_scope
12237 .line_comment_prefixes()
12238 .iter()
12239 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12240 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12241 }
12242 } else {
12243 // we not in an overridden comment node, but we may
12244 // be within a non-overridden line comment node
12245 language_scope
12246 .line_comment_prefixes()
12247 .iter()
12248 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12249 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12250 };
12251
12252 let rewrap_prefix = language_scope
12253 .rewrap_prefixes()
12254 .iter()
12255 .find_map(|prefix_regex| {
12256 prefix_regex.find(&line_text_after_indent).map(|mat| {
12257 if mat.start() == 0 {
12258 Some(mat.as_str().to_string())
12259 } else {
12260 None
12261 }
12262 })
12263 })
12264 .flatten();
12265 (comment_delimiters, rewrap_prefix)
12266 } else {
12267 (None, None)
12268 };
12269 (indent, comment_prefix, rewrap_prefix)
12270 };
12271
12272 let mut ranges = Vec::new();
12273 let from_empty_selection = selection.is_empty();
12274
12275 let mut current_range_start = first_row;
12276 let mut prev_row = first_row;
12277 let (
12278 mut current_range_indent,
12279 mut current_range_comment_delimiters,
12280 mut current_range_rewrap_prefix,
12281 ) = indent_and_prefix_for_row(first_row);
12282
12283 for row in non_blank_rows_iter.skip(1) {
12284 let has_paragraph_break = row > prev_row + 1;
12285
12286 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12287 indent_and_prefix_for_row(row);
12288
12289 let has_indent_change = row_indent != current_range_indent;
12290 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12291
12292 let has_boundary_change = has_comment_change
12293 || row_rewrap_prefix.is_some()
12294 || (has_indent_change && current_range_comment_delimiters.is_some());
12295
12296 if has_paragraph_break || has_boundary_change {
12297 ranges.push((
12298 language_settings.clone(),
12299 Point::new(current_range_start, 0)
12300 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12301 current_range_indent,
12302 current_range_comment_delimiters.clone(),
12303 current_range_rewrap_prefix.clone(),
12304 from_empty_selection,
12305 ));
12306 current_range_start = row;
12307 current_range_indent = row_indent;
12308 current_range_comment_delimiters = row_comment_delimiters;
12309 current_range_rewrap_prefix = row_rewrap_prefix;
12310 }
12311 prev_row = row;
12312 }
12313
12314 ranges.push((
12315 language_settings.clone(),
12316 Point::new(current_range_start, 0)
12317 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12318 current_range_indent,
12319 current_range_comment_delimiters,
12320 current_range_rewrap_prefix,
12321 from_empty_selection,
12322 ));
12323
12324 ranges
12325 });
12326
12327 let mut edits = Vec::new();
12328 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12329
12330 for (
12331 language_settings,
12332 wrap_range,
12333 mut indent_size,
12334 comment_prefix,
12335 rewrap_prefix,
12336 from_empty_selection,
12337 ) in wrap_ranges
12338 {
12339 let mut start_row = wrap_range.start.row;
12340 let mut end_row = wrap_range.end.row;
12341
12342 // Skip selections that overlap with a range that has already been rewrapped.
12343 let selection_range = start_row..end_row;
12344 if rewrapped_row_ranges
12345 .iter()
12346 .any(|range| range.overlaps(&selection_range))
12347 {
12348 continue;
12349 }
12350
12351 let tab_size = language_settings.tab_size;
12352
12353 let (line_prefix, inside_comment) = match &comment_prefix {
12354 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12355 (Some(prefix.as_str()), true)
12356 }
12357 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12358 (Some(prefix.as_ref()), true)
12359 }
12360 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12361 start: _,
12362 end: _,
12363 prefix,
12364 tab_size,
12365 })) => {
12366 indent_size.len += tab_size;
12367 (Some(prefix.as_ref()), true)
12368 }
12369 None => (None, false),
12370 };
12371 let indent_prefix = indent_size.chars().collect::<String>();
12372 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12373
12374 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12375 RewrapBehavior::InComments => inside_comment,
12376 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12377 RewrapBehavior::Anywhere => true,
12378 };
12379
12380 let should_rewrap = options.override_language_settings
12381 || allow_rewrap_based_on_language
12382 || self.hard_wrap.is_some();
12383 if !should_rewrap {
12384 continue;
12385 }
12386
12387 if from_empty_selection {
12388 'expand_upwards: while start_row > 0 {
12389 let prev_row = start_row - 1;
12390 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12391 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12392 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12393 {
12394 start_row = prev_row;
12395 } else {
12396 break 'expand_upwards;
12397 }
12398 }
12399
12400 'expand_downwards: while end_row < buffer.max_point().row {
12401 let next_row = end_row + 1;
12402 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12403 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12404 && !buffer.is_line_blank(MultiBufferRow(next_row))
12405 {
12406 end_row = next_row;
12407 } else {
12408 break 'expand_downwards;
12409 }
12410 }
12411 }
12412
12413 let start = Point::new(start_row, 0);
12414 let start_offset = ToOffset::to_offset(&start, &buffer);
12415 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12416 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12417 let mut first_line_delimiter = None;
12418 let mut last_line_delimiter = None;
12419 let Some(lines_without_prefixes) = selection_text
12420 .lines()
12421 .enumerate()
12422 .map(|(ix, line)| {
12423 let line_trimmed = line.trim_start();
12424 if rewrap_prefix.is_some() && ix > 0 {
12425 Ok(line_trimmed)
12426 } else if let Some(
12427 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12428 start,
12429 prefix,
12430 end,
12431 tab_size,
12432 })
12433 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12434 start,
12435 prefix,
12436 end,
12437 tab_size,
12438 }),
12439 ) = &comment_prefix
12440 {
12441 let line_trimmed = line_trimmed
12442 .strip_prefix(start.as_ref())
12443 .map(|s| {
12444 let mut indent_size = indent_size;
12445 indent_size.len -= tab_size;
12446 let indent_prefix: String = indent_size.chars().collect();
12447 first_line_delimiter = Some((indent_prefix, start));
12448 s.trim_start()
12449 })
12450 .unwrap_or(line_trimmed);
12451 let line_trimmed = line_trimmed
12452 .strip_suffix(end.as_ref())
12453 .map(|s| {
12454 last_line_delimiter = Some(end);
12455 s.trim_end()
12456 })
12457 .unwrap_or(line_trimmed);
12458 let line_trimmed = line_trimmed
12459 .strip_prefix(prefix.as_ref())
12460 .unwrap_or(line_trimmed);
12461 Ok(line_trimmed)
12462 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12463 line_trimmed.strip_prefix(prefix).with_context(|| {
12464 format!("line did not start with prefix {prefix:?}: {line:?}")
12465 })
12466 } else {
12467 line_trimmed
12468 .strip_prefix(&line_prefix.trim_start())
12469 .with_context(|| {
12470 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12471 })
12472 }
12473 })
12474 .collect::<Result<Vec<_>, _>>()
12475 .log_err()
12476 else {
12477 continue;
12478 };
12479
12480 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12481 buffer
12482 .language_settings_at(Point::new(start_row, 0), cx)
12483 .preferred_line_length as usize
12484 });
12485
12486 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12487 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12488 } else {
12489 line_prefix.clone()
12490 };
12491
12492 let wrapped_text = {
12493 let mut wrapped_text = wrap_with_prefix(
12494 line_prefix,
12495 subsequent_lines_prefix,
12496 lines_without_prefixes.join("\n"),
12497 wrap_column,
12498 tab_size,
12499 options.preserve_existing_whitespace,
12500 );
12501
12502 if let Some((indent, delimiter)) = first_line_delimiter {
12503 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12504 }
12505 if let Some(last_line) = last_line_delimiter {
12506 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12507 }
12508
12509 wrapped_text
12510 };
12511
12512 // TODO: should always use char-based diff while still supporting cursor behavior that
12513 // matches vim.
12514 let mut diff_options = DiffOptions::default();
12515 if options.override_language_settings {
12516 diff_options.max_word_diff_len = 0;
12517 diff_options.max_word_diff_line_count = 0;
12518 } else {
12519 diff_options.max_word_diff_len = usize::MAX;
12520 diff_options.max_word_diff_line_count = usize::MAX;
12521 }
12522
12523 for (old_range, new_text) in
12524 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12525 {
12526 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12527 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12528 edits.push((edit_start..edit_end, new_text));
12529 }
12530
12531 rewrapped_row_ranges.push(start_row..=end_row);
12532 }
12533
12534 self.buffer
12535 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12536 }
12537
12538 pub fn cut_common(
12539 &mut self,
12540 cut_no_selection_line: bool,
12541 window: &mut Window,
12542 cx: &mut Context<Self>,
12543 ) -> ClipboardItem {
12544 let mut text = String::new();
12545 let buffer = self.buffer.read(cx).snapshot(cx);
12546 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12547 let mut clipboard_selections = Vec::with_capacity(selections.len());
12548 {
12549 let max_point = buffer.max_point();
12550 let mut is_first = true;
12551 for selection in &mut selections {
12552 let is_entire_line =
12553 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12554 if is_entire_line {
12555 selection.start = Point::new(selection.start.row, 0);
12556 if !selection.is_empty() && selection.end.column == 0 {
12557 selection.end = cmp::min(max_point, selection.end);
12558 } else {
12559 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12560 }
12561 selection.goal = SelectionGoal::None;
12562 }
12563 if is_first {
12564 is_first = false;
12565 } else {
12566 text += "\n";
12567 }
12568 let mut len = 0;
12569 for chunk in buffer.text_for_range(selection.start..selection.end) {
12570 text.push_str(chunk);
12571 len += chunk.len();
12572 }
12573 clipboard_selections.push(ClipboardSelection {
12574 len,
12575 is_entire_line,
12576 first_line_indent: buffer
12577 .indent_size_for_line(MultiBufferRow(selection.start.row))
12578 .len,
12579 });
12580 }
12581 }
12582
12583 self.transact(window, cx, |this, window, cx| {
12584 this.change_selections(Default::default(), window, cx, |s| {
12585 s.select(selections);
12586 });
12587 this.insert("", window, cx);
12588 });
12589 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12590 }
12591
12592 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12593 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12594 let item = self.cut_common(true, window, cx);
12595 cx.write_to_clipboard(item);
12596 }
12597
12598 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12599 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12600 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12601 s.move_with(|snapshot, sel| {
12602 if sel.is_empty() {
12603 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12604 }
12605 if sel.is_empty() {
12606 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12607 }
12608 });
12609 });
12610 let item = self.cut_common(false, window, cx);
12611 cx.set_global(KillRing(item))
12612 }
12613
12614 pub fn kill_ring_yank(
12615 &mut self,
12616 _: &KillRingYank,
12617 window: &mut Window,
12618 cx: &mut Context<Self>,
12619 ) {
12620 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12621 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12622 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12623 (kill_ring.text().to_string(), kill_ring.metadata_json())
12624 } else {
12625 return;
12626 }
12627 } else {
12628 return;
12629 };
12630 self.do_paste(&text, metadata, false, window, cx);
12631 }
12632
12633 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12634 self.do_copy(true, cx);
12635 }
12636
12637 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12638 self.do_copy(false, cx);
12639 }
12640
12641 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12642 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12643 let buffer = self.buffer.read(cx).read(cx);
12644 let mut text = String::new();
12645
12646 let mut clipboard_selections = Vec::with_capacity(selections.len());
12647 {
12648 let max_point = buffer.max_point();
12649 let mut is_first = true;
12650 for selection in &selections {
12651 let mut start = selection.start;
12652 let mut end = selection.end;
12653 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12654 let mut add_trailing_newline = false;
12655 if is_entire_line {
12656 start = Point::new(start.row, 0);
12657 let next_line_start = Point::new(end.row + 1, 0);
12658 if next_line_start <= max_point {
12659 end = next_line_start;
12660 } else {
12661 // We're on the last line without a trailing newline.
12662 // Copy to the end of the line and add a newline afterwards.
12663 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12664 add_trailing_newline = true;
12665 }
12666 }
12667
12668 let mut trimmed_selections = Vec::new();
12669 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12670 let row = MultiBufferRow(start.row);
12671 let first_indent = buffer.indent_size_for_line(row);
12672 if first_indent.len == 0 || start.column > first_indent.len {
12673 trimmed_selections.push(start..end);
12674 } else {
12675 trimmed_selections.push(
12676 Point::new(row.0, first_indent.len)
12677 ..Point::new(row.0, buffer.line_len(row)),
12678 );
12679 for row in start.row + 1..=end.row {
12680 let mut line_len = buffer.line_len(MultiBufferRow(row));
12681 if row == end.row {
12682 line_len = end.column;
12683 }
12684 if line_len == 0 {
12685 trimmed_selections
12686 .push(Point::new(row, 0)..Point::new(row, line_len));
12687 continue;
12688 }
12689 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12690 if row_indent_size.len >= first_indent.len {
12691 trimmed_selections.push(
12692 Point::new(row, first_indent.len)..Point::new(row, line_len),
12693 );
12694 } else {
12695 trimmed_selections.clear();
12696 trimmed_selections.push(start..end);
12697 break;
12698 }
12699 }
12700 }
12701 } else {
12702 trimmed_selections.push(start..end);
12703 }
12704
12705 for trimmed_range in trimmed_selections {
12706 if is_first {
12707 is_first = false;
12708 } else {
12709 text += "\n";
12710 }
12711 let mut len = 0;
12712 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12713 text.push_str(chunk);
12714 len += chunk.len();
12715 }
12716 if add_trailing_newline {
12717 text.push('\n');
12718 len += 1;
12719 }
12720 clipboard_selections.push(ClipboardSelection {
12721 len,
12722 is_entire_line,
12723 first_line_indent: buffer
12724 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12725 .len,
12726 });
12727 }
12728 }
12729 }
12730
12731 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12732 text,
12733 clipboard_selections,
12734 ));
12735 }
12736
12737 pub fn do_paste(
12738 &mut self,
12739 text: &String,
12740 clipboard_selections: Option<Vec<ClipboardSelection>>,
12741 handle_entire_lines: bool,
12742 window: &mut Window,
12743 cx: &mut Context<Self>,
12744 ) {
12745 if self.read_only(cx) {
12746 return;
12747 }
12748
12749 let clipboard_text = Cow::Borrowed(text.as_str());
12750
12751 self.transact(window, cx, |this, window, cx| {
12752 let had_active_edit_prediction = this.has_active_edit_prediction();
12753 let display_map = this.display_snapshot(cx);
12754 let old_selections = this.selections.all::<usize>(&display_map);
12755 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12756
12757 if let Some(mut clipboard_selections) = clipboard_selections {
12758 let all_selections_were_entire_line =
12759 clipboard_selections.iter().all(|s| s.is_entire_line);
12760 let first_selection_indent_column =
12761 clipboard_selections.first().map(|s| s.first_line_indent);
12762 if clipboard_selections.len() != old_selections.len() {
12763 clipboard_selections.drain(..);
12764 }
12765 let mut auto_indent_on_paste = true;
12766
12767 this.buffer.update(cx, |buffer, cx| {
12768 let snapshot = buffer.read(cx);
12769 auto_indent_on_paste = snapshot
12770 .language_settings_at(cursor_offset, cx)
12771 .auto_indent_on_paste;
12772
12773 let mut start_offset = 0;
12774 let mut edits = Vec::new();
12775 let mut original_indent_columns = Vec::new();
12776 for (ix, selection) in old_selections.iter().enumerate() {
12777 let to_insert;
12778 let entire_line;
12779 let original_indent_column;
12780 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12781 let end_offset = start_offset + clipboard_selection.len;
12782 to_insert = &clipboard_text[start_offset..end_offset];
12783 entire_line = clipboard_selection.is_entire_line;
12784 start_offset = end_offset + 1;
12785 original_indent_column = Some(clipboard_selection.first_line_indent);
12786 } else {
12787 to_insert = &*clipboard_text;
12788 entire_line = all_selections_were_entire_line;
12789 original_indent_column = first_selection_indent_column
12790 }
12791
12792 let (range, to_insert) =
12793 if selection.is_empty() && handle_entire_lines && entire_line {
12794 // If the corresponding selection was empty when this slice of the
12795 // clipboard text was written, then the entire line containing the
12796 // selection was copied. If this selection is also currently empty,
12797 // then paste the line before the current line of the buffer.
12798 let column = selection.start.to_point(&snapshot).column as usize;
12799 let line_start = selection.start - column;
12800 (line_start..line_start, Cow::Borrowed(to_insert))
12801 } else {
12802 let language = snapshot.language_at(selection.head());
12803 let range = selection.range();
12804 if let Some(language) = language
12805 && language.name() == "Markdown".into()
12806 {
12807 edit_for_markdown_paste(
12808 &snapshot,
12809 range,
12810 to_insert,
12811 url::Url::parse(to_insert).ok(),
12812 )
12813 } else {
12814 (range, Cow::Borrowed(to_insert))
12815 }
12816 };
12817
12818 edits.push((range, to_insert));
12819 original_indent_columns.push(original_indent_column);
12820 }
12821 drop(snapshot);
12822
12823 buffer.edit(
12824 edits,
12825 if auto_indent_on_paste {
12826 Some(AutoindentMode::Block {
12827 original_indent_columns,
12828 })
12829 } else {
12830 None
12831 },
12832 cx,
12833 );
12834 });
12835
12836 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12837 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12838 } else {
12839 let url = url::Url::parse(&clipboard_text).ok();
12840
12841 let auto_indent_mode = if !clipboard_text.is_empty() {
12842 Some(AutoindentMode::Block {
12843 original_indent_columns: Vec::new(),
12844 })
12845 } else {
12846 None
12847 };
12848
12849 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12850 let snapshot = buffer.snapshot(cx);
12851
12852 let anchors = old_selections
12853 .iter()
12854 .map(|s| {
12855 let anchor = snapshot.anchor_after(s.head());
12856 s.map(|_| anchor)
12857 })
12858 .collect::<Vec<_>>();
12859
12860 let mut edits = Vec::new();
12861
12862 for selection in old_selections.iter() {
12863 let language = snapshot.language_at(selection.head());
12864 let range = selection.range();
12865
12866 let (edit_range, edit_text) = if let Some(language) = language
12867 && language.name() == "Markdown".into()
12868 {
12869 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12870 } else {
12871 (range, clipboard_text.clone())
12872 };
12873
12874 edits.push((edit_range, edit_text));
12875 }
12876
12877 drop(snapshot);
12878 buffer.edit(edits, auto_indent_mode, cx);
12879
12880 anchors
12881 });
12882
12883 this.change_selections(Default::default(), window, cx, |s| {
12884 s.select_anchors(selection_anchors);
12885 });
12886 }
12887
12888 // 🤔 | .. | show_in_menu |
12889 // | .. | true true
12890 // | had_edit_prediction | false true
12891
12892 let trigger_in_words =
12893 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12894
12895 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12896 });
12897 }
12898
12899 pub fn diff_clipboard_with_selection(
12900 &mut self,
12901 _: &DiffClipboardWithSelection,
12902 window: &mut Window,
12903 cx: &mut Context<Self>,
12904 ) {
12905 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12906
12907 if selections.is_empty() {
12908 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12909 return;
12910 };
12911
12912 let clipboard_text = match cx.read_from_clipboard() {
12913 Some(item) => match item.entries().first() {
12914 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12915 _ => None,
12916 },
12917 None => None,
12918 };
12919
12920 let Some(clipboard_text) = clipboard_text else {
12921 log::warn!("Clipboard doesn't contain text.");
12922 return;
12923 };
12924
12925 window.dispatch_action(
12926 Box::new(DiffClipboardWithSelectionData {
12927 clipboard_text,
12928 editor: cx.entity(),
12929 }),
12930 cx,
12931 );
12932 }
12933
12934 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12935 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12936 if let Some(item) = cx.read_from_clipboard() {
12937 let entries = item.entries();
12938
12939 match entries.first() {
12940 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12941 // of all the pasted entries.
12942 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12943 .do_paste(
12944 clipboard_string.text(),
12945 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12946 true,
12947 window,
12948 cx,
12949 ),
12950 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12951 }
12952 }
12953 }
12954
12955 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12956 if self.read_only(cx) {
12957 return;
12958 }
12959
12960 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12961
12962 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12963 if let Some((selections, _)) =
12964 self.selection_history.transaction(transaction_id).cloned()
12965 {
12966 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12967 s.select_anchors(selections.to_vec());
12968 });
12969 } else {
12970 log::error!(
12971 "No entry in selection_history found for undo. \
12972 This may correspond to a bug where undo does not update the selection. \
12973 If this is occurring, please add details to \
12974 https://github.com/zed-industries/zed/issues/22692"
12975 );
12976 }
12977 self.request_autoscroll(Autoscroll::fit(), cx);
12978 self.unmark_text(window, cx);
12979 self.refresh_edit_prediction(true, false, window, cx);
12980 cx.emit(EditorEvent::Edited { transaction_id });
12981 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12982 }
12983 }
12984
12985 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12986 if self.read_only(cx) {
12987 return;
12988 }
12989
12990 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12991
12992 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12993 if let Some((_, Some(selections))) =
12994 self.selection_history.transaction(transaction_id).cloned()
12995 {
12996 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12997 s.select_anchors(selections.to_vec());
12998 });
12999 } else {
13000 log::error!(
13001 "No entry in selection_history found for redo. \
13002 This may correspond to a bug where undo does not update the selection. \
13003 If this is occurring, please add details to \
13004 https://github.com/zed-industries/zed/issues/22692"
13005 );
13006 }
13007 self.request_autoscroll(Autoscroll::fit(), cx);
13008 self.unmark_text(window, cx);
13009 self.refresh_edit_prediction(true, false, window, cx);
13010 cx.emit(EditorEvent::Edited { transaction_id });
13011 }
13012 }
13013
13014 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13015 self.buffer
13016 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13017 }
13018
13019 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13020 self.buffer
13021 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13022 }
13023
13024 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13025 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13026 self.change_selections(Default::default(), window, cx, |s| {
13027 s.move_with(|map, selection| {
13028 let cursor = if selection.is_empty() {
13029 movement::left(map, selection.start)
13030 } else {
13031 selection.start
13032 };
13033 selection.collapse_to(cursor, SelectionGoal::None);
13034 });
13035 })
13036 }
13037
13038 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13039 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13040 self.change_selections(Default::default(), window, cx, |s| {
13041 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13042 })
13043 }
13044
13045 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13046 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13047 self.change_selections(Default::default(), window, cx, |s| {
13048 s.move_with(|map, selection| {
13049 let cursor = if selection.is_empty() {
13050 movement::right(map, selection.end)
13051 } else {
13052 selection.end
13053 };
13054 selection.collapse_to(cursor, SelectionGoal::None)
13055 });
13056 })
13057 }
13058
13059 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13060 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13061 self.change_selections(Default::default(), window, cx, |s| {
13062 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13063 });
13064 }
13065
13066 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13067 if self.take_rename(true, window, cx).is_some() {
13068 return;
13069 }
13070
13071 if self.mode.is_single_line() {
13072 cx.propagate();
13073 return;
13074 }
13075
13076 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13077
13078 let text_layout_details = &self.text_layout_details(window);
13079 let selection_count = self.selections.count();
13080 let first_selection = self.selections.first_anchor();
13081
13082 self.change_selections(Default::default(), window, cx, |s| {
13083 s.move_with(|map, selection| {
13084 if !selection.is_empty() {
13085 selection.goal = SelectionGoal::None;
13086 }
13087 let (cursor, goal) = movement::up(
13088 map,
13089 selection.start,
13090 selection.goal,
13091 false,
13092 text_layout_details,
13093 );
13094 selection.collapse_to(cursor, goal);
13095 });
13096 });
13097
13098 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13099 {
13100 cx.propagate();
13101 }
13102 }
13103
13104 pub fn move_up_by_lines(
13105 &mut self,
13106 action: &MoveUpByLines,
13107 window: &mut Window,
13108 cx: &mut Context<Self>,
13109 ) {
13110 if self.take_rename(true, window, cx).is_some() {
13111 return;
13112 }
13113
13114 if self.mode.is_single_line() {
13115 cx.propagate();
13116 return;
13117 }
13118
13119 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13120
13121 let text_layout_details = &self.text_layout_details(window);
13122
13123 self.change_selections(Default::default(), window, cx, |s| {
13124 s.move_with(|map, selection| {
13125 if !selection.is_empty() {
13126 selection.goal = SelectionGoal::None;
13127 }
13128 let (cursor, goal) = movement::up_by_rows(
13129 map,
13130 selection.start,
13131 action.lines,
13132 selection.goal,
13133 false,
13134 text_layout_details,
13135 );
13136 selection.collapse_to(cursor, goal);
13137 });
13138 })
13139 }
13140
13141 pub fn move_down_by_lines(
13142 &mut self,
13143 action: &MoveDownByLines,
13144 window: &mut Window,
13145 cx: &mut Context<Self>,
13146 ) {
13147 if self.take_rename(true, window, cx).is_some() {
13148 return;
13149 }
13150
13151 if self.mode.is_single_line() {
13152 cx.propagate();
13153 return;
13154 }
13155
13156 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13157
13158 let text_layout_details = &self.text_layout_details(window);
13159
13160 self.change_selections(Default::default(), window, cx, |s| {
13161 s.move_with(|map, selection| {
13162 if !selection.is_empty() {
13163 selection.goal = SelectionGoal::None;
13164 }
13165 let (cursor, goal) = movement::down_by_rows(
13166 map,
13167 selection.start,
13168 action.lines,
13169 selection.goal,
13170 false,
13171 text_layout_details,
13172 );
13173 selection.collapse_to(cursor, goal);
13174 });
13175 })
13176 }
13177
13178 pub fn select_down_by_lines(
13179 &mut self,
13180 action: &SelectDownByLines,
13181 window: &mut Window,
13182 cx: &mut Context<Self>,
13183 ) {
13184 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13185 let text_layout_details = &self.text_layout_details(window);
13186 self.change_selections(Default::default(), window, cx, |s| {
13187 s.move_heads_with(|map, head, goal| {
13188 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13189 })
13190 })
13191 }
13192
13193 pub fn select_up_by_lines(
13194 &mut self,
13195 action: &SelectUpByLines,
13196 window: &mut Window,
13197 cx: &mut Context<Self>,
13198 ) {
13199 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13200 let text_layout_details = &self.text_layout_details(window);
13201 self.change_selections(Default::default(), window, cx, |s| {
13202 s.move_heads_with(|map, head, goal| {
13203 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13204 })
13205 })
13206 }
13207
13208 pub fn select_page_up(
13209 &mut self,
13210 _: &SelectPageUp,
13211 window: &mut Window,
13212 cx: &mut Context<Self>,
13213 ) {
13214 let Some(row_count) = self.visible_row_count() else {
13215 return;
13216 };
13217
13218 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13219
13220 let text_layout_details = &self.text_layout_details(window);
13221
13222 self.change_selections(Default::default(), window, cx, |s| {
13223 s.move_heads_with(|map, head, goal| {
13224 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13225 })
13226 })
13227 }
13228
13229 pub fn move_page_up(
13230 &mut self,
13231 action: &MovePageUp,
13232 window: &mut Window,
13233 cx: &mut Context<Self>,
13234 ) {
13235 if self.take_rename(true, window, cx).is_some() {
13236 return;
13237 }
13238
13239 if self
13240 .context_menu
13241 .borrow_mut()
13242 .as_mut()
13243 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13244 .unwrap_or(false)
13245 {
13246 return;
13247 }
13248
13249 if matches!(self.mode, EditorMode::SingleLine) {
13250 cx.propagate();
13251 return;
13252 }
13253
13254 let Some(row_count) = self.visible_row_count() else {
13255 return;
13256 };
13257
13258 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13259
13260 let effects = if action.center_cursor {
13261 SelectionEffects::scroll(Autoscroll::center())
13262 } else {
13263 SelectionEffects::default()
13264 };
13265
13266 let text_layout_details = &self.text_layout_details(window);
13267
13268 self.change_selections(effects, window, cx, |s| {
13269 s.move_with(|map, selection| {
13270 if !selection.is_empty() {
13271 selection.goal = SelectionGoal::None;
13272 }
13273 let (cursor, goal) = movement::up_by_rows(
13274 map,
13275 selection.end,
13276 row_count,
13277 selection.goal,
13278 false,
13279 text_layout_details,
13280 );
13281 selection.collapse_to(cursor, goal);
13282 });
13283 });
13284 }
13285
13286 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13287 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13288 let text_layout_details = &self.text_layout_details(window);
13289 self.change_selections(Default::default(), window, cx, |s| {
13290 s.move_heads_with(|map, head, goal| {
13291 movement::up(map, head, goal, false, text_layout_details)
13292 })
13293 })
13294 }
13295
13296 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13297 self.take_rename(true, window, cx);
13298
13299 if self.mode.is_single_line() {
13300 cx.propagate();
13301 return;
13302 }
13303
13304 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13305
13306 let text_layout_details = &self.text_layout_details(window);
13307 let selection_count = self.selections.count();
13308 let first_selection = self.selections.first_anchor();
13309
13310 self.change_selections(Default::default(), window, cx, |s| {
13311 s.move_with(|map, selection| {
13312 if !selection.is_empty() {
13313 selection.goal = SelectionGoal::None;
13314 }
13315 let (cursor, goal) = movement::down(
13316 map,
13317 selection.end,
13318 selection.goal,
13319 false,
13320 text_layout_details,
13321 );
13322 selection.collapse_to(cursor, goal);
13323 });
13324 });
13325
13326 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13327 {
13328 cx.propagate();
13329 }
13330 }
13331
13332 pub fn select_page_down(
13333 &mut self,
13334 _: &SelectPageDown,
13335 window: &mut Window,
13336 cx: &mut Context<Self>,
13337 ) {
13338 let Some(row_count) = self.visible_row_count() else {
13339 return;
13340 };
13341
13342 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13343
13344 let text_layout_details = &self.text_layout_details(window);
13345
13346 self.change_selections(Default::default(), window, cx, |s| {
13347 s.move_heads_with(|map, head, goal| {
13348 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13349 })
13350 })
13351 }
13352
13353 pub fn move_page_down(
13354 &mut self,
13355 action: &MovePageDown,
13356 window: &mut Window,
13357 cx: &mut Context<Self>,
13358 ) {
13359 if self.take_rename(true, window, cx).is_some() {
13360 return;
13361 }
13362
13363 if self
13364 .context_menu
13365 .borrow_mut()
13366 .as_mut()
13367 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13368 .unwrap_or(false)
13369 {
13370 return;
13371 }
13372
13373 if matches!(self.mode, EditorMode::SingleLine) {
13374 cx.propagate();
13375 return;
13376 }
13377
13378 let Some(row_count) = self.visible_row_count() else {
13379 return;
13380 };
13381
13382 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13383
13384 let effects = if action.center_cursor {
13385 SelectionEffects::scroll(Autoscroll::center())
13386 } else {
13387 SelectionEffects::default()
13388 };
13389
13390 let text_layout_details = &self.text_layout_details(window);
13391 self.change_selections(effects, window, cx, |s| {
13392 s.move_with(|map, selection| {
13393 if !selection.is_empty() {
13394 selection.goal = SelectionGoal::None;
13395 }
13396 let (cursor, goal) = movement::down_by_rows(
13397 map,
13398 selection.end,
13399 row_count,
13400 selection.goal,
13401 false,
13402 text_layout_details,
13403 );
13404 selection.collapse_to(cursor, goal);
13405 });
13406 });
13407 }
13408
13409 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13410 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13411 let text_layout_details = &self.text_layout_details(window);
13412 self.change_selections(Default::default(), window, cx, |s| {
13413 s.move_heads_with(|map, head, goal| {
13414 movement::down(map, head, goal, false, text_layout_details)
13415 })
13416 });
13417 }
13418
13419 pub fn context_menu_first(
13420 &mut self,
13421 _: &ContextMenuFirst,
13422 window: &mut Window,
13423 cx: &mut Context<Self>,
13424 ) {
13425 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13426 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13427 }
13428 }
13429
13430 pub fn context_menu_prev(
13431 &mut self,
13432 _: &ContextMenuPrevious,
13433 window: &mut Window,
13434 cx: &mut Context<Self>,
13435 ) {
13436 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13437 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13438 }
13439 }
13440
13441 pub fn context_menu_next(
13442 &mut self,
13443 _: &ContextMenuNext,
13444 window: &mut Window,
13445 cx: &mut Context<Self>,
13446 ) {
13447 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13448 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13449 }
13450 }
13451
13452 pub fn context_menu_last(
13453 &mut self,
13454 _: &ContextMenuLast,
13455 window: &mut Window,
13456 cx: &mut Context<Self>,
13457 ) {
13458 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13459 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13460 }
13461 }
13462
13463 pub fn signature_help_prev(
13464 &mut self,
13465 _: &SignatureHelpPrevious,
13466 _: &mut Window,
13467 cx: &mut Context<Self>,
13468 ) {
13469 if let Some(popover) = self.signature_help_state.popover_mut() {
13470 if popover.current_signature == 0 {
13471 popover.current_signature = popover.signatures.len() - 1;
13472 } else {
13473 popover.current_signature -= 1;
13474 }
13475 cx.notify();
13476 }
13477 }
13478
13479 pub fn signature_help_next(
13480 &mut self,
13481 _: &SignatureHelpNext,
13482 _: &mut Window,
13483 cx: &mut Context<Self>,
13484 ) {
13485 if let Some(popover) = self.signature_help_state.popover_mut() {
13486 if popover.current_signature + 1 == popover.signatures.len() {
13487 popover.current_signature = 0;
13488 } else {
13489 popover.current_signature += 1;
13490 }
13491 cx.notify();
13492 }
13493 }
13494
13495 pub fn move_to_previous_word_start(
13496 &mut self,
13497 _: &MoveToPreviousWordStart,
13498 window: &mut Window,
13499 cx: &mut Context<Self>,
13500 ) {
13501 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13502 self.change_selections(Default::default(), window, cx, |s| {
13503 s.move_cursors_with(|map, head, _| {
13504 (
13505 movement::previous_word_start(map, head),
13506 SelectionGoal::None,
13507 )
13508 });
13509 })
13510 }
13511
13512 pub fn move_to_previous_subword_start(
13513 &mut self,
13514 _: &MoveToPreviousSubwordStart,
13515 window: &mut Window,
13516 cx: &mut Context<Self>,
13517 ) {
13518 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13519 self.change_selections(Default::default(), window, cx, |s| {
13520 s.move_cursors_with(|map, head, _| {
13521 (
13522 movement::previous_subword_start(map, head),
13523 SelectionGoal::None,
13524 )
13525 });
13526 })
13527 }
13528
13529 pub fn select_to_previous_word_start(
13530 &mut self,
13531 _: &SelectToPreviousWordStart,
13532 window: &mut Window,
13533 cx: &mut Context<Self>,
13534 ) {
13535 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13536 self.change_selections(Default::default(), window, cx, |s| {
13537 s.move_heads_with(|map, head, _| {
13538 (
13539 movement::previous_word_start(map, head),
13540 SelectionGoal::None,
13541 )
13542 });
13543 })
13544 }
13545
13546 pub fn select_to_previous_subword_start(
13547 &mut self,
13548 _: &SelectToPreviousSubwordStart,
13549 window: &mut Window,
13550 cx: &mut Context<Self>,
13551 ) {
13552 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13553 self.change_selections(Default::default(), window, cx, |s| {
13554 s.move_heads_with(|map, head, _| {
13555 (
13556 movement::previous_subword_start(map, head),
13557 SelectionGoal::None,
13558 )
13559 });
13560 })
13561 }
13562
13563 pub fn delete_to_previous_word_start(
13564 &mut self,
13565 action: &DeleteToPreviousWordStart,
13566 window: &mut Window,
13567 cx: &mut Context<Self>,
13568 ) {
13569 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13570 self.transact(window, cx, |this, window, cx| {
13571 this.select_autoclose_pair(window, cx);
13572 this.change_selections(Default::default(), window, cx, |s| {
13573 s.move_with(|map, selection| {
13574 if selection.is_empty() {
13575 let mut cursor = if action.ignore_newlines {
13576 movement::previous_word_start(map, selection.head())
13577 } else {
13578 movement::previous_word_start_or_newline(map, selection.head())
13579 };
13580 cursor = movement::adjust_greedy_deletion(
13581 map,
13582 selection.head(),
13583 cursor,
13584 action.ignore_brackets,
13585 );
13586 selection.set_head(cursor, SelectionGoal::None);
13587 }
13588 });
13589 });
13590 this.insert("", window, cx);
13591 });
13592 }
13593
13594 pub fn delete_to_previous_subword_start(
13595 &mut self,
13596 _: &DeleteToPreviousSubwordStart,
13597 window: &mut Window,
13598 cx: &mut Context<Self>,
13599 ) {
13600 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13601 self.transact(window, cx, |this, window, cx| {
13602 this.select_autoclose_pair(window, cx);
13603 this.change_selections(Default::default(), window, cx, |s| {
13604 s.move_with(|map, selection| {
13605 if selection.is_empty() {
13606 let mut cursor = movement::previous_subword_start(map, selection.head());
13607 cursor =
13608 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13609 selection.set_head(cursor, SelectionGoal::None);
13610 }
13611 });
13612 });
13613 this.insert("", window, cx);
13614 });
13615 }
13616
13617 pub fn move_to_next_word_end(
13618 &mut self,
13619 _: &MoveToNextWordEnd,
13620 window: &mut Window,
13621 cx: &mut Context<Self>,
13622 ) {
13623 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13624 self.change_selections(Default::default(), window, cx, |s| {
13625 s.move_cursors_with(|map, head, _| {
13626 (movement::next_word_end(map, head), SelectionGoal::None)
13627 });
13628 })
13629 }
13630
13631 pub fn move_to_next_subword_end(
13632 &mut self,
13633 _: &MoveToNextSubwordEnd,
13634 window: &mut Window,
13635 cx: &mut Context<Self>,
13636 ) {
13637 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13638 self.change_selections(Default::default(), window, cx, |s| {
13639 s.move_cursors_with(|map, head, _| {
13640 (movement::next_subword_end(map, head), SelectionGoal::None)
13641 });
13642 })
13643 }
13644
13645 pub fn select_to_next_word_end(
13646 &mut self,
13647 _: &SelectToNextWordEnd,
13648 window: &mut Window,
13649 cx: &mut Context<Self>,
13650 ) {
13651 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13652 self.change_selections(Default::default(), window, cx, |s| {
13653 s.move_heads_with(|map, head, _| {
13654 (movement::next_word_end(map, head), SelectionGoal::None)
13655 });
13656 })
13657 }
13658
13659 pub fn select_to_next_subword_end(
13660 &mut self,
13661 _: &SelectToNextSubwordEnd,
13662 window: &mut Window,
13663 cx: &mut Context<Self>,
13664 ) {
13665 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13666 self.change_selections(Default::default(), window, cx, |s| {
13667 s.move_heads_with(|map, head, _| {
13668 (movement::next_subword_end(map, head), SelectionGoal::None)
13669 });
13670 })
13671 }
13672
13673 pub fn delete_to_next_word_end(
13674 &mut self,
13675 action: &DeleteToNextWordEnd,
13676 window: &mut Window,
13677 cx: &mut Context<Self>,
13678 ) {
13679 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13680 self.transact(window, cx, |this, window, cx| {
13681 this.change_selections(Default::default(), window, cx, |s| {
13682 s.move_with(|map, selection| {
13683 if selection.is_empty() {
13684 let mut cursor = if action.ignore_newlines {
13685 movement::next_word_end(map, selection.head())
13686 } else {
13687 movement::next_word_end_or_newline(map, selection.head())
13688 };
13689 cursor = movement::adjust_greedy_deletion(
13690 map,
13691 selection.head(),
13692 cursor,
13693 action.ignore_brackets,
13694 );
13695 selection.set_head(cursor, SelectionGoal::None);
13696 }
13697 });
13698 });
13699 this.insert("", window, cx);
13700 });
13701 }
13702
13703 pub fn delete_to_next_subword_end(
13704 &mut self,
13705 _: &DeleteToNextSubwordEnd,
13706 window: &mut Window,
13707 cx: &mut Context<Self>,
13708 ) {
13709 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13710 self.transact(window, cx, |this, window, cx| {
13711 this.change_selections(Default::default(), window, cx, |s| {
13712 s.move_with(|map, selection| {
13713 if selection.is_empty() {
13714 let mut cursor = movement::next_subword_end(map, selection.head());
13715 cursor =
13716 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13717 selection.set_head(cursor, SelectionGoal::None);
13718 }
13719 });
13720 });
13721 this.insert("", window, cx);
13722 });
13723 }
13724
13725 pub fn move_to_beginning_of_line(
13726 &mut self,
13727 action: &MoveToBeginningOfLine,
13728 window: &mut Window,
13729 cx: &mut Context<Self>,
13730 ) {
13731 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13732 self.change_selections(Default::default(), window, cx, |s| {
13733 s.move_cursors_with(|map, head, _| {
13734 (
13735 movement::indented_line_beginning(
13736 map,
13737 head,
13738 action.stop_at_soft_wraps,
13739 action.stop_at_indent,
13740 ),
13741 SelectionGoal::None,
13742 )
13743 });
13744 })
13745 }
13746
13747 pub fn select_to_beginning_of_line(
13748 &mut self,
13749 action: &SelectToBeginningOfLine,
13750 window: &mut Window,
13751 cx: &mut Context<Self>,
13752 ) {
13753 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13754 self.change_selections(Default::default(), window, cx, |s| {
13755 s.move_heads_with(|map, head, _| {
13756 (
13757 movement::indented_line_beginning(
13758 map,
13759 head,
13760 action.stop_at_soft_wraps,
13761 action.stop_at_indent,
13762 ),
13763 SelectionGoal::None,
13764 )
13765 });
13766 });
13767 }
13768
13769 pub fn delete_to_beginning_of_line(
13770 &mut self,
13771 action: &DeleteToBeginningOfLine,
13772 window: &mut Window,
13773 cx: &mut Context<Self>,
13774 ) {
13775 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13776 self.transact(window, cx, |this, window, cx| {
13777 this.change_selections(Default::default(), window, cx, |s| {
13778 s.move_with(|_, selection| {
13779 selection.reversed = true;
13780 });
13781 });
13782
13783 this.select_to_beginning_of_line(
13784 &SelectToBeginningOfLine {
13785 stop_at_soft_wraps: false,
13786 stop_at_indent: action.stop_at_indent,
13787 },
13788 window,
13789 cx,
13790 );
13791 this.backspace(&Backspace, window, cx);
13792 });
13793 }
13794
13795 pub fn move_to_end_of_line(
13796 &mut self,
13797 action: &MoveToEndOfLine,
13798 window: &mut Window,
13799 cx: &mut Context<Self>,
13800 ) {
13801 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13802 self.change_selections(Default::default(), window, cx, |s| {
13803 s.move_cursors_with(|map, head, _| {
13804 (
13805 movement::line_end(map, head, action.stop_at_soft_wraps),
13806 SelectionGoal::None,
13807 )
13808 });
13809 })
13810 }
13811
13812 pub fn select_to_end_of_line(
13813 &mut self,
13814 action: &SelectToEndOfLine,
13815 window: &mut Window,
13816 cx: &mut Context<Self>,
13817 ) {
13818 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13819 self.change_selections(Default::default(), window, cx, |s| {
13820 s.move_heads_with(|map, head, _| {
13821 (
13822 movement::line_end(map, head, action.stop_at_soft_wraps),
13823 SelectionGoal::None,
13824 )
13825 });
13826 })
13827 }
13828
13829 pub fn delete_to_end_of_line(
13830 &mut self,
13831 _: &DeleteToEndOfLine,
13832 window: &mut Window,
13833 cx: &mut Context<Self>,
13834 ) {
13835 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13836 self.transact(window, cx, |this, window, cx| {
13837 this.select_to_end_of_line(
13838 &SelectToEndOfLine {
13839 stop_at_soft_wraps: false,
13840 },
13841 window,
13842 cx,
13843 );
13844 this.delete(&Delete, window, cx);
13845 });
13846 }
13847
13848 pub fn cut_to_end_of_line(
13849 &mut self,
13850 action: &CutToEndOfLine,
13851 window: &mut Window,
13852 cx: &mut Context<Self>,
13853 ) {
13854 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13855 self.transact(window, cx, |this, window, cx| {
13856 this.select_to_end_of_line(
13857 &SelectToEndOfLine {
13858 stop_at_soft_wraps: false,
13859 },
13860 window,
13861 cx,
13862 );
13863 if !action.stop_at_newlines {
13864 this.change_selections(Default::default(), window, cx, |s| {
13865 s.move_with(|_, sel| {
13866 if sel.is_empty() {
13867 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13868 }
13869 });
13870 });
13871 }
13872 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13873 let item = this.cut_common(false, window, cx);
13874 cx.write_to_clipboard(item);
13875 });
13876 }
13877
13878 pub fn move_to_start_of_paragraph(
13879 &mut self,
13880 _: &MoveToStartOfParagraph,
13881 window: &mut Window,
13882 cx: &mut Context<Self>,
13883 ) {
13884 if matches!(self.mode, EditorMode::SingleLine) {
13885 cx.propagate();
13886 return;
13887 }
13888 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13889 self.change_selections(Default::default(), window, cx, |s| {
13890 s.move_with(|map, selection| {
13891 selection.collapse_to(
13892 movement::start_of_paragraph(map, selection.head(), 1),
13893 SelectionGoal::None,
13894 )
13895 });
13896 })
13897 }
13898
13899 pub fn move_to_end_of_paragraph(
13900 &mut self,
13901 _: &MoveToEndOfParagraph,
13902 window: &mut Window,
13903 cx: &mut Context<Self>,
13904 ) {
13905 if matches!(self.mode, EditorMode::SingleLine) {
13906 cx.propagate();
13907 return;
13908 }
13909 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13910 self.change_selections(Default::default(), window, cx, |s| {
13911 s.move_with(|map, selection| {
13912 selection.collapse_to(
13913 movement::end_of_paragraph(map, selection.head(), 1),
13914 SelectionGoal::None,
13915 )
13916 });
13917 })
13918 }
13919
13920 pub fn select_to_start_of_paragraph(
13921 &mut self,
13922 _: &SelectToStartOfParagraph,
13923 window: &mut Window,
13924 cx: &mut Context<Self>,
13925 ) {
13926 if matches!(self.mode, EditorMode::SingleLine) {
13927 cx.propagate();
13928 return;
13929 }
13930 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13931 self.change_selections(Default::default(), window, cx, |s| {
13932 s.move_heads_with(|map, head, _| {
13933 (
13934 movement::start_of_paragraph(map, head, 1),
13935 SelectionGoal::None,
13936 )
13937 });
13938 })
13939 }
13940
13941 pub fn select_to_end_of_paragraph(
13942 &mut self,
13943 _: &SelectToEndOfParagraph,
13944 window: &mut Window,
13945 cx: &mut Context<Self>,
13946 ) {
13947 if matches!(self.mode, EditorMode::SingleLine) {
13948 cx.propagate();
13949 return;
13950 }
13951 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13952 self.change_selections(Default::default(), window, cx, |s| {
13953 s.move_heads_with(|map, head, _| {
13954 (
13955 movement::end_of_paragraph(map, head, 1),
13956 SelectionGoal::None,
13957 )
13958 });
13959 })
13960 }
13961
13962 pub fn move_to_start_of_excerpt(
13963 &mut self,
13964 _: &MoveToStartOfExcerpt,
13965 window: &mut Window,
13966 cx: &mut Context<Self>,
13967 ) {
13968 if matches!(self.mode, EditorMode::SingleLine) {
13969 cx.propagate();
13970 return;
13971 }
13972 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13973 self.change_selections(Default::default(), window, cx, |s| {
13974 s.move_with(|map, selection| {
13975 selection.collapse_to(
13976 movement::start_of_excerpt(
13977 map,
13978 selection.head(),
13979 workspace::searchable::Direction::Prev,
13980 ),
13981 SelectionGoal::None,
13982 )
13983 });
13984 })
13985 }
13986
13987 pub fn move_to_start_of_next_excerpt(
13988 &mut self,
13989 _: &MoveToStartOfNextExcerpt,
13990 window: &mut Window,
13991 cx: &mut Context<Self>,
13992 ) {
13993 if matches!(self.mode, EditorMode::SingleLine) {
13994 cx.propagate();
13995 return;
13996 }
13997
13998 self.change_selections(Default::default(), window, cx, |s| {
13999 s.move_with(|map, selection| {
14000 selection.collapse_to(
14001 movement::start_of_excerpt(
14002 map,
14003 selection.head(),
14004 workspace::searchable::Direction::Next,
14005 ),
14006 SelectionGoal::None,
14007 )
14008 });
14009 })
14010 }
14011
14012 pub fn move_to_end_of_excerpt(
14013 &mut self,
14014 _: &MoveToEndOfExcerpt,
14015 window: &mut Window,
14016 cx: &mut Context<Self>,
14017 ) {
14018 if matches!(self.mode, EditorMode::SingleLine) {
14019 cx.propagate();
14020 return;
14021 }
14022 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14023 self.change_selections(Default::default(), window, cx, |s| {
14024 s.move_with(|map, selection| {
14025 selection.collapse_to(
14026 movement::end_of_excerpt(
14027 map,
14028 selection.head(),
14029 workspace::searchable::Direction::Next,
14030 ),
14031 SelectionGoal::None,
14032 )
14033 });
14034 })
14035 }
14036
14037 pub fn move_to_end_of_previous_excerpt(
14038 &mut self,
14039 _: &MoveToEndOfPreviousExcerpt,
14040 window: &mut Window,
14041 cx: &mut Context<Self>,
14042 ) {
14043 if matches!(self.mode, EditorMode::SingleLine) {
14044 cx.propagate();
14045 return;
14046 }
14047 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14048 self.change_selections(Default::default(), window, cx, |s| {
14049 s.move_with(|map, selection| {
14050 selection.collapse_to(
14051 movement::end_of_excerpt(
14052 map,
14053 selection.head(),
14054 workspace::searchable::Direction::Prev,
14055 ),
14056 SelectionGoal::None,
14057 )
14058 });
14059 })
14060 }
14061
14062 pub fn select_to_start_of_excerpt(
14063 &mut self,
14064 _: &SelectToStartOfExcerpt,
14065 window: &mut Window,
14066 cx: &mut Context<Self>,
14067 ) {
14068 if matches!(self.mode, EditorMode::SingleLine) {
14069 cx.propagate();
14070 return;
14071 }
14072 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14073 self.change_selections(Default::default(), window, cx, |s| {
14074 s.move_heads_with(|map, head, _| {
14075 (
14076 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14077 SelectionGoal::None,
14078 )
14079 });
14080 })
14081 }
14082
14083 pub fn select_to_start_of_next_excerpt(
14084 &mut self,
14085 _: &SelectToStartOfNextExcerpt,
14086 window: &mut Window,
14087 cx: &mut Context<Self>,
14088 ) {
14089 if matches!(self.mode, EditorMode::SingleLine) {
14090 cx.propagate();
14091 return;
14092 }
14093 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14094 self.change_selections(Default::default(), window, cx, |s| {
14095 s.move_heads_with(|map, head, _| {
14096 (
14097 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14098 SelectionGoal::None,
14099 )
14100 });
14101 })
14102 }
14103
14104 pub fn select_to_end_of_excerpt(
14105 &mut self,
14106 _: &SelectToEndOfExcerpt,
14107 window: &mut Window,
14108 cx: &mut Context<Self>,
14109 ) {
14110 if matches!(self.mode, EditorMode::SingleLine) {
14111 cx.propagate();
14112 return;
14113 }
14114 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14115 self.change_selections(Default::default(), window, cx, |s| {
14116 s.move_heads_with(|map, head, _| {
14117 (
14118 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14119 SelectionGoal::None,
14120 )
14121 });
14122 })
14123 }
14124
14125 pub fn select_to_end_of_previous_excerpt(
14126 &mut self,
14127 _: &SelectToEndOfPreviousExcerpt,
14128 window: &mut Window,
14129 cx: &mut Context<Self>,
14130 ) {
14131 if matches!(self.mode, EditorMode::SingleLine) {
14132 cx.propagate();
14133 return;
14134 }
14135 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14136 self.change_selections(Default::default(), window, cx, |s| {
14137 s.move_heads_with(|map, head, _| {
14138 (
14139 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14140 SelectionGoal::None,
14141 )
14142 });
14143 })
14144 }
14145
14146 pub fn move_to_beginning(
14147 &mut self,
14148 _: &MoveToBeginning,
14149 window: &mut Window,
14150 cx: &mut Context<Self>,
14151 ) {
14152 if matches!(self.mode, EditorMode::SingleLine) {
14153 cx.propagate();
14154 return;
14155 }
14156 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14157 self.change_selections(Default::default(), window, cx, |s| {
14158 s.select_ranges(vec![0..0]);
14159 });
14160 }
14161
14162 pub fn select_to_beginning(
14163 &mut self,
14164 _: &SelectToBeginning,
14165 window: &mut Window,
14166 cx: &mut Context<Self>,
14167 ) {
14168 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14169 selection.set_head(Point::zero(), SelectionGoal::None);
14170 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14171 self.change_selections(Default::default(), window, cx, |s| {
14172 s.select(vec![selection]);
14173 });
14174 }
14175
14176 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14177 if matches!(self.mode, EditorMode::SingleLine) {
14178 cx.propagate();
14179 return;
14180 }
14181 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14182 let cursor = self.buffer.read(cx).read(cx).len();
14183 self.change_selections(Default::default(), window, cx, |s| {
14184 s.select_ranges(vec![cursor..cursor])
14185 });
14186 }
14187
14188 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14189 self.nav_history = nav_history;
14190 }
14191
14192 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14193 self.nav_history.as_ref()
14194 }
14195
14196 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14197 self.push_to_nav_history(
14198 self.selections.newest_anchor().head(),
14199 None,
14200 false,
14201 true,
14202 cx,
14203 );
14204 }
14205
14206 fn push_to_nav_history(
14207 &mut self,
14208 cursor_anchor: Anchor,
14209 new_position: Option<Point>,
14210 is_deactivate: bool,
14211 always: bool,
14212 cx: &mut Context<Self>,
14213 ) {
14214 if let Some(nav_history) = self.nav_history.as_mut() {
14215 let buffer = self.buffer.read(cx).read(cx);
14216 let cursor_position = cursor_anchor.to_point(&buffer);
14217 let scroll_state = self.scroll_manager.anchor();
14218 let scroll_top_row = scroll_state.top_row(&buffer);
14219 drop(buffer);
14220
14221 if let Some(new_position) = new_position {
14222 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14223 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14224 return;
14225 }
14226 }
14227
14228 nav_history.push(
14229 Some(NavigationData {
14230 cursor_anchor,
14231 cursor_position,
14232 scroll_anchor: scroll_state,
14233 scroll_top_row,
14234 }),
14235 cx,
14236 );
14237 cx.emit(EditorEvent::PushedToNavHistory {
14238 anchor: cursor_anchor,
14239 is_deactivate,
14240 })
14241 }
14242 }
14243
14244 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14245 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14246 let buffer = self.buffer.read(cx).snapshot(cx);
14247 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14248 selection.set_head(buffer.len(), SelectionGoal::None);
14249 self.change_selections(Default::default(), window, cx, |s| {
14250 s.select(vec![selection]);
14251 });
14252 }
14253
14254 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14255 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14256 let end = self.buffer.read(cx).read(cx).len();
14257 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14258 s.select_ranges(vec![0..end]);
14259 });
14260 }
14261
14262 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14263 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14264 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14265 let mut selections = self.selections.all::<Point>(&display_map);
14266 let max_point = display_map.buffer_snapshot().max_point();
14267 for selection in &mut selections {
14268 let rows = selection.spanned_rows(true, &display_map);
14269 selection.start = Point::new(rows.start.0, 0);
14270 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14271 selection.reversed = false;
14272 }
14273 self.change_selections(Default::default(), window, cx, |s| {
14274 s.select(selections);
14275 });
14276 }
14277
14278 pub fn split_selection_into_lines(
14279 &mut self,
14280 action: &SplitSelectionIntoLines,
14281 window: &mut Window,
14282 cx: &mut Context<Self>,
14283 ) {
14284 let selections = self
14285 .selections
14286 .all::<Point>(&self.display_snapshot(cx))
14287 .into_iter()
14288 .map(|selection| selection.start..selection.end)
14289 .collect::<Vec<_>>();
14290 self.unfold_ranges(&selections, true, true, cx);
14291
14292 let mut new_selection_ranges = Vec::new();
14293 {
14294 let buffer = self.buffer.read(cx).read(cx);
14295 for selection in selections {
14296 for row in selection.start.row..selection.end.row {
14297 let line_start = Point::new(row, 0);
14298 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14299
14300 if action.keep_selections {
14301 // Keep the selection range for each line
14302 let selection_start = if row == selection.start.row {
14303 selection.start
14304 } else {
14305 line_start
14306 };
14307 new_selection_ranges.push(selection_start..line_end);
14308 } else {
14309 // Collapse to cursor at end of line
14310 new_selection_ranges.push(line_end..line_end);
14311 }
14312 }
14313
14314 let is_multiline_selection = selection.start.row != selection.end.row;
14315 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14316 // so this action feels more ergonomic when paired with other selection operations
14317 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14318 if !should_skip_last {
14319 if action.keep_selections {
14320 if is_multiline_selection {
14321 let line_start = Point::new(selection.end.row, 0);
14322 new_selection_ranges.push(line_start..selection.end);
14323 } else {
14324 new_selection_ranges.push(selection.start..selection.end);
14325 }
14326 } else {
14327 new_selection_ranges.push(selection.end..selection.end);
14328 }
14329 }
14330 }
14331 }
14332 self.change_selections(Default::default(), window, cx, |s| {
14333 s.select_ranges(new_selection_ranges);
14334 });
14335 }
14336
14337 pub fn add_selection_above(
14338 &mut self,
14339 action: &AddSelectionAbove,
14340 window: &mut Window,
14341 cx: &mut Context<Self>,
14342 ) {
14343 self.add_selection(true, action.skip_soft_wrap, window, cx);
14344 }
14345
14346 pub fn add_selection_below(
14347 &mut self,
14348 action: &AddSelectionBelow,
14349 window: &mut Window,
14350 cx: &mut Context<Self>,
14351 ) {
14352 self.add_selection(false, action.skip_soft_wrap, window, cx);
14353 }
14354
14355 fn add_selection(
14356 &mut self,
14357 above: bool,
14358 skip_soft_wrap: bool,
14359 window: &mut Window,
14360 cx: &mut Context<Self>,
14361 ) {
14362 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14363
14364 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14365 let all_selections = self.selections.all::<Point>(&display_map);
14366 let text_layout_details = self.text_layout_details(window);
14367
14368 let (mut columnar_selections, new_selections_to_columnarize) = {
14369 if let Some(state) = self.add_selections_state.as_ref() {
14370 let columnar_selection_ids: HashSet<_> = state
14371 .groups
14372 .iter()
14373 .flat_map(|group| group.stack.iter())
14374 .copied()
14375 .collect();
14376
14377 all_selections
14378 .into_iter()
14379 .partition(|s| columnar_selection_ids.contains(&s.id))
14380 } else {
14381 (Vec::new(), all_selections)
14382 }
14383 };
14384
14385 let mut state = self
14386 .add_selections_state
14387 .take()
14388 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14389
14390 for selection in new_selections_to_columnarize {
14391 let range = selection.display_range(&display_map).sorted();
14392 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14393 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14394 let positions = start_x.min(end_x)..start_x.max(end_x);
14395 let mut stack = Vec::new();
14396 for row in range.start.row().0..=range.end.row().0 {
14397 if let Some(selection) = self.selections.build_columnar_selection(
14398 &display_map,
14399 DisplayRow(row),
14400 &positions,
14401 selection.reversed,
14402 &text_layout_details,
14403 ) {
14404 stack.push(selection.id);
14405 columnar_selections.push(selection);
14406 }
14407 }
14408 if !stack.is_empty() {
14409 if above {
14410 stack.reverse();
14411 }
14412 state.groups.push(AddSelectionsGroup { above, stack });
14413 }
14414 }
14415
14416 let mut final_selections = Vec::new();
14417 let end_row = if above {
14418 DisplayRow(0)
14419 } else {
14420 display_map.max_point().row()
14421 };
14422
14423 let mut last_added_item_per_group = HashMap::default();
14424 for group in state.groups.iter_mut() {
14425 if let Some(last_id) = group.stack.last() {
14426 last_added_item_per_group.insert(*last_id, group);
14427 }
14428 }
14429
14430 for selection in columnar_selections {
14431 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14432 if above == group.above {
14433 let range = selection.display_range(&display_map).sorted();
14434 debug_assert_eq!(range.start.row(), range.end.row());
14435 let mut row = range.start.row();
14436 let positions =
14437 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14438 Pixels::from(start)..Pixels::from(end)
14439 } else {
14440 let start_x =
14441 display_map.x_for_display_point(range.start, &text_layout_details);
14442 let end_x =
14443 display_map.x_for_display_point(range.end, &text_layout_details);
14444 start_x.min(end_x)..start_x.max(end_x)
14445 };
14446
14447 let mut maybe_new_selection = None;
14448 let direction = if above { -1 } else { 1 };
14449
14450 while row != end_row {
14451 if skip_soft_wrap {
14452 row = display_map
14453 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14454 .row();
14455 } else if above {
14456 row.0 -= 1;
14457 } else {
14458 row.0 += 1;
14459 }
14460
14461 if let Some(new_selection) = self.selections.build_columnar_selection(
14462 &display_map,
14463 row,
14464 &positions,
14465 selection.reversed,
14466 &text_layout_details,
14467 ) {
14468 maybe_new_selection = Some(new_selection);
14469 break;
14470 }
14471 }
14472
14473 if let Some(new_selection) = maybe_new_selection {
14474 group.stack.push(new_selection.id);
14475 if above {
14476 final_selections.push(new_selection);
14477 final_selections.push(selection);
14478 } else {
14479 final_selections.push(selection);
14480 final_selections.push(new_selection);
14481 }
14482 } else {
14483 final_selections.push(selection);
14484 }
14485 } else {
14486 group.stack.pop();
14487 }
14488 } else {
14489 final_selections.push(selection);
14490 }
14491 }
14492
14493 self.change_selections(Default::default(), window, cx, |s| {
14494 s.select(final_selections);
14495 });
14496
14497 let final_selection_ids: HashSet<_> = self
14498 .selections
14499 .all::<Point>(&display_map)
14500 .iter()
14501 .map(|s| s.id)
14502 .collect();
14503 state.groups.retain_mut(|group| {
14504 // selections might get merged above so we remove invalid items from stacks
14505 group.stack.retain(|id| final_selection_ids.contains(id));
14506
14507 // single selection in stack can be treated as initial state
14508 group.stack.len() > 1
14509 });
14510
14511 if !state.groups.is_empty() {
14512 self.add_selections_state = Some(state);
14513 }
14514 }
14515
14516 fn select_match_ranges(
14517 &mut self,
14518 range: Range<usize>,
14519 reversed: bool,
14520 replace_newest: bool,
14521 auto_scroll: Option<Autoscroll>,
14522 window: &mut Window,
14523 cx: &mut Context<Editor>,
14524 ) {
14525 self.unfold_ranges(
14526 std::slice::from_ref(&range),
14527 false,
14528 auto_scroll.is_some(),
14529 cx,
14530 );
14531 let effects = if let Some(scroll) = auto_scroll {
14532 SelectionEffects::scroll(scroll)
14533 } else {
14534 SelectionEffects::no_scroll()
14535 };
14536 self.change_selections(effects, window, cx, |s| {
14537 if replace_newest {
14538 s.delete(s.newest_anchor().id);
14539 }
14540 if reversed {
14541 s.insert_range(range.end..range.start);
14542 } else {
14543 s.insert_range(range);
14544 }
14545 });
14546 }
14547
14548 pub fn select_next_match_internal(
14549 &mut self,
14550 display_map: &DisplaySnapshot,
14551 replace_newest: bool,
14552 autoscroll: Option<Autoscroll>,
14553 window: &mut Window,
14554 cx: &mut Context<Self>,
14555 ) -> Result<()> {
14556 let buffer = display_map.buffer_snapshot();
14557 let mut selections = self.selections.all::<usize>(&display_map);
14558 if let Some(mut select_next_state) = self.select_next_state.take() {
14559 let query = &select_next_state.query;
14560 if !select_next_state.done {
14561 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14562 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14563 let mut next_selected_range = None;
14564
14565 let bytes_after_last_selection =
14566 buffer.bytes_in_range(last_selection.end..buffer.len());
14567 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14568 let query_matches = query
14569 .stream_find_iter(bytes_after_last_selection)
14570 .map(|result| (last_selection.end, result))
14571 .chain(
14572 query
14573 .stream_find_iter(bytes_before_first_selection)
14574 .map(|result| (0, result)),
14575 );
14576
14577 for (start_offset, query_match) in query_matches {
14578 let query_match = query_match.unwrap(); // can only fail due to I/O
14579 let offset_range =
14580 start_offset + query_match.start()..start_offset + query_match.end();
14581
14582 if !select_next_state.wordwise
14583 || (!buffer.is_inside_word(offset_range.start, None)
14584 && !buffer.is_inside_word(offset_range.end, None))
14585 {
14586 let idx = selections
14587 .partition_point(|selection| selection.end <= offset_range.start);
14588 let overlaps = selections
14589 .get(idx)
14590 .map_or(false, |selection| selection.start < offset_range.end);
14591
14592 if !overlaps {
14593 next_selected_range = Some(offset_range);
14594 break;
14595 }
14596 }
14597 }
14598
14599 if let Some(next_selected_range) = next_selected_range {
14600 self.select_match_ranges(
14601 next_selected_range,
14602 last_selection.reversed,
14603 replace_newest,
14604 autoscroll,
14605 window,
14606 cx,
14607 );
14608 } else {
14609 select_next_state.done = true;
14610 }
14611 }
14612
14613 self.select_next_state = Some(select_next_state);
14614 } else {
14615 let mut only_carets = true;
14616 let mut same_text_selected = true;
14617 let mut selected_text = None;
14618
14619 let mut selections_iter = selections.iter().peekable();
14620 while let Some(selection) = selections_iter.next() {
14621 if selection.start != selection.end {
14622 only_carets = false;
14623 }
14624
14625 if same_text_selected {
14626 if selected_text.is_none() {
14627 selected_text =
14628 Some(buffer.text_for_range(selection.range()).collect::<String>());
14629 }
14630
14631 if let Some(next_selection) = selections_iter.peek() {
14632 if next_selection.range().len() == selection.range().len() {
14633 let next_selected_text = buffer
14634 .text_for_range(next_selection.range())
14635 .collect::<String>();
14636 if Some(next_selected_text) != selected_text {
14637 same_text_selected = false;
14638 selected_text = None;
14639 }
14640 } else {
14641 same_text_selected = false;
14642 selected_text = None;
14643 }
14644 }
14645 }
14646 }
14647
14648 if only_carets {
14649 for selection in &mut selections {
14650 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14651 selection.start = word_range.start;
14652 selection.end = word_range.end;
14653 selection.goal = SelectionGoal::None;
14654 selection.reversed = false;
14655 self.select_match_ranges(
14656 selection.start..selection.end,
14657 selection.reversed,
14658 replace_newest,
14659 autoscroll,
14660 window,
14661 cx,
14662 );
14663 }
14664
14665 if selections.len() == 1 {
14666 let selection = selections
14667 .last()
14668 .expect("ensured that there's only one selection");
14669 let query = buffer
14670 .text_for_range(selection.start..selection.end)
14671 .collect::<String>();
14672 let is_empty = query.is_empty();
14673 let select_state = SelectNextState {
14674 query: self.build_query(&[query], cx)?,
14675 wordwise: true,
14676 done: is_empty,
14677 };
14678 self.select_next_state = Some(select_state);
14679 } else {
14680 self.select_next_state = None;
14681 }
14682 } else if let Some(selected_text) = selected_text {
14683 self.select_next_state = Some(SelectNextState {
14684 query: self.build_query(&[selected_text], cx)?,
14685 wordwise: false,
14686 done: false,
14687 });
14688 self.select_next_match_internal(
14689 display_map,
14690 replace_newest,
14691 autoscroll,
14692 window,
14693 cx,
14694 )?;
14695 }
14696 }
14697 Ok(())
14698 }
14699
14700 pub fn select_all_matches(
14701 &mut self,
14702 _action: &SelectAllMatches,
14703 window: &mut Window,
14704 cx: &mut Context<Self>,
14705 ) -> Result<()> {
14706 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14707
14708 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14709
14710 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14711 let Some(select_next_state) = self.select_next_state.as_mut() else {
14712 return Ok(());
14713 };
14714 if select_next_state.done {
14715 return Ok(());
14716 }
14717
14718 let mut new_selections = Vec::new();
14719
14720 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14721 let buffer = display_map.buffer_snapshot();
14722 let query_matches = select_next_state
14723 .query
14724 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14725
14726 for query_match in query_matches.into_iter() {
14727 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14728 let offset_range = if reversed {
14729 query_match.end()..query_match.start()
14730 } else {
14731 query_match.start()..query_match.end()
14732 };
14733
14734 if !select_next_state.wordwise
14735 || (!buffer.is_inside_word(offset_range.start, None)
14736 && !buffer.is_inside_word(offset_range.end, None))
14737 {
14738 new_selections.push(offset_range.start..offset_range.end);
14739 }
14740 }
14741
14742 select_next_state.done = true;
14743
14744 if new_selections.is_empty() {
14745 log::error!("bug: new_selections is empty in select_all_matches");
14746 return Ok(());
14747 }
14748
14749 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14750 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14751 selections.select_ranges(new_selections)
14752 });
14753
14754 Ok(())
14755 }
14756
14757 pub fn select_next(
14758 &mut self,
14759 action: &SelectNext,
14760 window: &mut Window,
14761 cx: &mut Context<Self>,
14762 ) -> Result<()> {
14763 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14764 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14765 self.select_next_match_internal(
14766 &display_map,
14767 action.replace_newest,
14768 Some(Autoscroll::newest()),
14769 window,
14770 cx,
14771 )?;
14772 Ok(())
14773 }
14774
14775 pub fn select_previous(
14776 &mut self,
14777 action: &SelectPrevious,
14778 window: &mut Window,
14779 cx: &mut Context<Self>,
14780 ) -> Result<()> {
14781 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14782 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14783 let buffer = display_map.buffer_snapshot();
14784 let mut selections = self.selections.all::<usize>(&display_map);
14785 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14786 let query = &select_prev_state.query;
14787 if !select_prev_state.done {
14788 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14789 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14790 let mut next_selected_range = None;
14791 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14792 let bytes_before_last_selection =
14793 buffer.reversed_bytes_in_range(0..last_selection.start);
14794 let bytes_after_first_selection =
14795 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14796 let query_matches = query
14797 .stream_find_iter(bytes_before_last_selection)
14798 .map(|result| (last_selection.start, result))
14799 .chain(
14800 query
14801 .stream_find_iter(bytes_after_first_selection)
14802 .map(|result| (buffer.len(), result)),
14803 );
14804 for (end_offset, query_match) in query_matches {
14805 let query_match = query_match.unwrap(); // can only fail due to I/O
14806 let offset_range =
14807 end_offset - query_match.end()..end_offset - query_match.start();
14808
14809 if !select_prev_state.wordwise
14810 || (!buffer.is_inside_word(offset_range.start, None)
14811 && !buffer.is_inside_word(offset_range.end, None))
14812 {
14813 next_selected_range = Some(offset_range);
14814 break;
14815 }
14816 }
14817
14818 if let Some(next_selected_range) = next_selected_range {
14819 self.select_match_ranges(
14820 next_selected_range,
14821 last_selection.reversed,
14822 action.replace_newest,
14823 Some(Autoscroll::newest()),
14824 window,
14825 cx,
14826 );
14827 } else {
14828 select_prev_state.done = true;
14829 }
14830 }
14831
14832 self.select_prev_state = Some(select_prev_state);
14833 } else {
14834 let mut only_carets = true;
14835 let mut same_text_selected = true;
14836 let mut selected_text = None;
14837
14838 let mut selections_iter = selections.iter().peekable();
14839 while let Some(selection) = selections_iter.next() {
14840 if selection.start != selection.end {
14841 only_carets = false;
14842 }
14843
14844 if same_text_selected {
14845 if selected_text.is_none() {
14846 selected_text =
14847 Some(buffer.text_for_range(selection.range()).collect::<String>());
14848 }
14849
14850 if let Some(next_selection) = selections_iter.peek() {
14851 if next_selection.range().len() == selection.range().len() {
14852 let next_selected_text = buffer
14853 .text_for_range(next_selection.range())
14854 .collect::<String>();
14855 if Some(next_selected_text) != selected_text {
14856 same_text_selected = false;
14857 selected_text = None;
14858 }
14859 } else {
14860 same_text_selected = false;
14861 selected_text = None;
14862 }
14863 }
14864 }
14865 }
14866
14867 if only_carets {
14868 for selection in &mut selections {
14869 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14870 selection.start = word_range.start;
14871 selection.end = word_range.end;
14872 selection.goal = SelectionGoal::None;
14873 selection.reversed = false;
14874 self.select_match_ranges(
14875 selection.start..selection.end,
14876 selection.reversed,
14877 action.replace_newest,
14878 Some(Autoscroll::newest()),
14879 window,
14880 cx,
14881 );
14882 }
14883 if selections.len() == 1 {
14884 let selection = selections
14885 .last()
14886 .expect("ensured that there's only one selection");
14887 let query = buffer
14888 .text_for_range(selection.start..selection.end)
14889 .collect::<String>();
14890 let is_empty = query.is_empty();
14891 let select_state = SelectNextState {
14892 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
14893 wordwise: true,
14894 done: is_empty,
14895 };
14896 self.select_prev_state = Some(select_state);
14897 } else {
14898 self.select_prev_state = None;
14899 }
14900 } else if let Some(selected_text) = selected_text {
14901 self.select_prev_state = Some(SelectNextState {
14902 query: self
14903 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
14904 wordwise: false,
14905 done: false,
14906 });
14907 self.select_previous(action, window, cx)?;
14908 }
14909 }
14910 Ok(())
14911 }
14912
14913 /// Builds an `AhoCorasick` automaton from the provided patterns, while
14914 /// setting the case sensitivity based on the global
14915 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
14916 /// editor's settings.
14917 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
14918 where
14919 I: IntoIterator<Item = P>,
14920 P: AsRef<[u8]>,
14921 {
14922 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
14923 || EditorSettings::get_global(cx).search.case_sensitive,
14924 |value| value,
14925 );
14926
14927 let mut builder = AhoCorasickBuilder::new();
14928 builder.ascii_case_insensitive(!case_sensitive);
14929 builder.build(patterns)
14930 }
14931
14932 pub fn find_next_match(
14933 &mut self,
14934 _: &FindNextMatch,
14935 window: &mut Window,
14936 cx: &mut Context<Self>,
14937 ) -> Result<()> {
14938 let selections = self.selections.disjoint_anchors_arc();
14939 match selections.first() {
14940 Some(first) if selections.len() >= 2 => {
14941 self.change_selections(Default::default(), window, cx, |s| {
14942 s.select_ranges([first.range()]);
14943 });
14944 }
14945 _ => self.select_next(
14946 &SelectNext {
14947 replace_newest: true,
14948 },
14949 window,
14950 cx,
14951 )?,
14952 }
14953 Ok(())
14954 }
14955
14956 pub fn find_previous_match(
14957 &mut self,
14958 _: &FindPreviousMatch,
14959 window: &mut Window,
14960 cx: &mut Context<Self>,
14961 ) -> Result<()> {
14962 let selections = self.selections.disjoint_anchors_arc();
14963 match selections.last() {
14964 Some(last) if selections.len() >= 2 => {
14965 self.change_selections(Default::default(), window, cx, |s| {
14966 s.select_ranges([last.range()]);
14967 });
14968 }
14969 _ => self.select_previous(
14970 &SelectPrevious {
14971 replace_newest: true,
14972 },
14973 window,
14974 cx,
14975 )?,
14976 }
14977 Ok(())
14978 }
14979
14980 pub fn toggle_comments(
14981 &mut self,
14982 action: &ToggleComments,
14983 window: &mut Window,
14984 cx: &mut Context<Self>,
14985 ) {
14986 if self.read_only(cx) {
14987 return;
14988 }
14989 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14990 let text_layout_details = &self.text_layout_details(window);
14991 self.transact(window, cx, |this, window, cx| {
14992 let mut selections = this
14993 .selections
14994 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14995 let mut edits = Vec::new();
14996 let mut selection_edit_ranges = Vec::new();
14997 let mut last_toggled_row = None;
14998 let snapshot = this.buffer.read(cx).read(cx);
14999 let empty_str: Arc<str> = Arc::default();
15000 let mut suffixes_inserted = Vec::new();
15001 let ignore_indent = action.ignore_indent;
15002
15003 fn comment_prefix_range(
15004 snapshot: &MultiBufferSnapshot,
15005 row: MultiBufferRow,
15006 comment_prefix: &str,
15007 comment_prefix_whitespace: &str,
15008 ignore_indent: bool,
15009 ) -> Range<Point> {
15010 let indent_size = if ignore_indent {
15011 0
15012 } else {
15013 snapshot.indent_size_for_line(row).len
15014 };
15015
15016 let start = Point::new(row.0, indent_size);
15017
15018 let mut line_bytes = snapshot
15019 .bytes_in_range(start..snapshot.max_point())
15020 .flatten()
15021 .copied();
15022
15023 // If this line currently begins with the line comment prefix, then record
15024 // the range containing the prefix.
15025 if line_bytes
15026 .by_ref()
15027 .take(comment_prefix.len())
15028 .eq(comment_prefix.bytes())
15029 {
15030 // Include any whitespace that matches the comment prefix.
15031 let matching_whitespace_len = line_bytes
15032 .zip(comment_prefix_whitespace.bytes())
15033 .take_while(|(a, b)| a == b)
15034 .count() as u32;
15035 let end = Point::new(
15036 start.row,
15037 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15038 );
15039 start..end
15040 } else {
15041 start..start
15042 }
15043 }
15044
15045 fn comment_suffix_range(
15046 snapshot: &MultiBufferSnapshot,
15047 row: MultiBufferRow,
15048 comment_suffix: &str,
15049 comment_suffix_has_leading_space: bool,
15050 ) -> Range<Point> {
15051 let end = Point::new(row.0, snapshot.line_len(row));
15052 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15053
15054 let mut line_end_bytes = snapshot
15055 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15056 .flatten()
15057 .copied();
15058
15059 let leading_space_len = if suffix_start_column > 0
15060 && line_end_bytes.next() == Some(b' ')
15061 && comment_suffix_has_leading_space
15062 {
15063 1
15064 } else {
15065 0
15066 };
15067
15068 // If this line currently begins with the line comment prefix, then record
15069 // the range containing the prefix.
15070 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15071 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15072 start..end
15073 } else {
15074 end..end
15075 }
15076 }
15077
15078 // TODO: Handle selections that cross excerpts
15079 for selection in &mut selections {
15080 let start_column = snapshot
15081 .indent_size_for_line(MultiBufferRow(selection.start.row))
15082 .len;
15083 let language = if let Some(language) =
15084 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15085 {
15086 language
15087 } else {
15088 continue;
15089 };
15090
15091 selection_edit_ranges.clear();
15092
15093 // If multiple selections contain a given row, avoid processing that
15094 // row more than once.
15095 let mut start_row = MultiBufferRow(selection.start.row);
15096 if last_toggled_row == Some(start_row) {
15097 start_row = start_row.next_row();
15098 }
15099 let end_row =
15100 if selection.end.row > selection.start.row && selection.end.column == 0 {
15101 MultiBufferRow(selection.end.row - 1)
15102 } else {
15103 MultiBufferRow(selection.end.row)
15104 };
15105 last_toggled_row = Some(end_row);
15106
15107 if start_row > end_row {
15108 continue;
15109 }
15110
15111 // If the language has line comments, toggle those.
15112 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15113
15114 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15115 if ignore_indent {
15116 full_comment_prefixes = full_comment_prefixes
15117 .into_iter()
15118 .map(|s| Arc::from(s.trim_end()))
15119 .collect();
15120 }
15121
15122 if !full_comment_prefixes.is_empty() {
15123 let first_prefix = full_comment_prefixes
15124 .first()
15125 .expect("prefixes is non-empty");
15126 let prefix_trimmed_lengths = full_comment_prefixes
15127 .iter()
15128 .map(|p| p.trim_end_matches(' ').len())
15129 .collect::<SmallVec<[usize; 4]>>();
15130
15131 let mut all_selection_lines_are_comments = true;
15132
15133 for row in start_row.0..=end_row.0 {
15134 let row = MultiBufferRow(row);
15135 if start_row < end_row && snapshot.is_line_blank(row) {
15136 continue;
15137 }
15138
15139 let prefix_range = full_comment_prefixes
15140 .iter()
15141 .zip(prefix_trimmed_lengths.iter().copied())
15142 .map(|(prefix, trimmed_prefix_len)| {
15143 comment_prefix_range(
15144 snapshot.deref(),
15145 row,
15146 &prefix[..trimmed_prefix_len],
15147 &prefix[trimmed_prefix_len..],
15148 ignore_indent,
15149 )
15150 })
15151 .max_by_key(|range| range.end.column - range.start.column)
15152 .expect("prefixes is non-empty");
15153
15154 if prefix_range.is_empty() {
15155 all_selection_lines_are_comments = false;
15156 }
15157
15158 selection_edit_ranges.push(prefix_range);
15159 }
15160
15161 if all_selection_lines_are_comments {
15162 edits.extend(
15163 selection_edit_ranges
15164 .iter()
15165 .cloned()
15166 .map(|range| (range, empty_str.clone())),
15167 );
15168 } else {
15169 let min_column = selection_edit_ranges
15170 .iter()
15171 .map(|range| range.start.column)
15172 .min()
15173 .unwrap_or(0);
15174 edits.extend(selection_edit_ranges.iter().map(|range| {
15175 let position = Point::new(range.start.row, min_column);
15176 (position..position, first_prefix.clone())
15177 }));
15178 }
15179 } else if let Some(BlockCommentConfig {
15180 start: full_comment_prefix,
15181 end: comment_suffix,
15182 ..
15183 }) = language.block_comment()
15184 {
15185 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15186 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15187 let prefix_range = comment_prefix_range(
15188 snapshot.deref(),
15189 start_row,
15190 comment_prefix,
15191 comment_prefix_whitespace,
15192 ignore_indent,
15193 );
15194 let suffix_range = comment_suffix_range(
15195 snapshot.deref(),
15196 end_row,
15197 comment_suffix.trim_start_matches(' '),
15198 comment_suffix.starts_with(' '),
15199 );
15200
15201 if prefix_range.is_empty() || suffix_range.is_empty() {
15202 edits.push((
15203 prefix_range.start..prefix_range.start,
15204 full_comment_prefix.clone(),
15205 ));
15206 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15207 suffixes_inserted.push((end_row, comment_suffix.len()));
15208 } else {
15209 edits.push((prefix_range, empty_str.clone()));
15210 edits.push((suffix_range, empty_str.clone()));
15211 }
15212 } else {
15213 continue;
15214 }
15215 }
15216
15217 drop(snapshot);
15218 this.buffer.update(cx, |buffer, cx| {
15219 buffer.edit(edits, None, cx);
15220 });
15221
15222 // Adjust selections so that they end before any comment suffixes that
15223 // were inserted.
15224 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15225 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15226 let snapshot = this.buffer.read(cx).read(cx);
15227 for selection in &mut selections {
15228 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15229 match row.cmp(&MultiBufferRow(selection.end.row)) {
15230 Ordering::Less => {
15231 suffixes_inserted.next();
15232 continue;
15233 }
15234 Ordering::Greater => break,
15235 Ordering::Equal => {
15236 if selection.end.column == snapshot.line_len(row) {
15237 if selection.is_empty() {
15238 selection.start.column -= suffix_len as u32;
15239 }
15240 selection.end.column -= suffix_len as u32;
15241 }
15242 break;
15243 }
15244 }
15245 }
15246 }
15247
15248 drop(snapshot);
15249 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15250
15251 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15252 let selections_on_single_row = selections.windows(2).all(|selections| {
15253 selections[0].start.row == selections[1].start.row
15254 && selections[0].end.row == selections[1].end.row
15255 && selections[0].start.row == selections[0].end.row
15256 });
15257 let selections_selecting = selections
15258 .iter()
15259 .any(|selection| selection.start != selection.end);
15260 let advance_downwards = action.advance_downwards
15261 && selections_on_single_row
15262 && !selections_selecting
15263 && !matches!(this.mode, EditorMode::SingleLine);
15264
15265 if advance_downwards {
15266 let snapshot = this.buffer.read(cx).snapshot(cx);
15267
15268 this.change_selections(Default::default(), window, cx, |s| {
15269 s.move_cursors_with(|display_snapshot, display_point, _| {
15270 let mut point = display_point.to_point(display_snapshot);
15271 point.row += 1;
15272 point = snapshot.clip_point(point, Bias::Left);
15273 let display_point = point.to_display_point(display_snapshot);
15274 let goal = SelectionGoal::HorizontalPosition(
15275 display_snapshot
15276 .x_for_display_point(display_point, text_layout_details)
15277 .into(),
15278 );
15279 (display_point, goal)
15280 })
15281 });
15282 }
15283 });
15284 }
15285
15286 pub fn select_enclosing_symbol(
15287 &mut self,
15288 _: &SelectEnclosingSymbol,
15289 window: &mut Window,
15290 cx: &mut Context<Self>,
15291 ) {
15292 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15293
15294 let buffer = self.buffer.read(cx).snapshot(cx);
15295 let old_selections = self
15296 .selections
15297 .all::<usize>(&self.display_snapshot(cx))
15298 .into_boxed_slice();
15299
15300 fn update_selection(
15301 selection: &Selection<usize>,
15302 buffer_snap: &MultiBufferSnapshot,
15303 ) -> Option<Selection<usize>> {
15304 let cursor = selection.head();
15305 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15306 for symbol in symbols.iter().rev() {
15307 let start = symbol.range.start.to_offset(buffer_snap);
15308 let end = symbol.range.end.to_offset(buffer_snap);
15309 let new_range = start..end;
15310 if start < selection.start || end > selection.end {
15311 return Some(Selection {
15312 id: selection.id,
15313 start: new_range.start,
15314 end: new_range.end,
15315 goal: SelectionGoal::None,
15316 reversed: selection.reversed,
15317 });
15318 }
15319 }
15320 None
15321 }
15322
15323 let mut selected_larger_symbol = false;
15324 let new_selections = old_selections
15325 .iter()
15326 .map(|selection| match update_selection(selection, &buffer) {
15327 Some(new_selection) => {
15328 if new_selection.range() != selection.range() {
15329 selected_larger_symbol = true;
15330 }
15331 new_selection
15332 }
15333 None => selection.clone(),
15334 })
15335 .collect::<Vec<_>>();
15336
15337 if selected_larger_symbol {
15338 self.change_selections(Default::default(), window, cx, |s| {
15339 s.select(new_selections);
15340 });
15341 }
15342 }
15343
15344 pub fn select_larger_syntax_node(
15345 &mut self,
15346 _: &SelectLargerSyntaxNode,
15347 window: &mut Window,
15348 cx: &mut Context<Self>,
15349 ) {
15350 let Some(visible_row_count) = self.visible_row_count() else {
15351 return;
15352 };
15353 let old_selections: Box<[_]> = self
15354 .selections
15355 .all::<usize>(&self.display_snapshot(cx))
15356 .into();
15357 if old_selections.is_empty() {
15358 return;
15359 }
15360
15361 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15362
15363 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15364 let buffer = self.buffer.read(cx).snapshot(cx);
15365
15366 let mut selected_larger_node = false;
15367 let mut new_selections = old_selections
15368 .iter()
15369 .map(|selection| {
15370 let old_range = selection.start..selection.end;
15371
15372 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15373 // manually select word at selection
15374 if ["string_content", "inline"].contains(&node.kind()) {
15375 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15376 // ignore if word is already selected
15377 if !word_range.is_empty() && old_range != word_range {
15378 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15379 // only select word if start and end point belongs to same word
15380 if word_range == last_word_range {
15381 selected_larger_node = true;
15382 return Selection {
15383 id: selection.id,
15384 start: word_range.start,
15385 end: word_range.end,
15386 goal: SelectionGoal::None,
15387 reversed: selection.reversed,
15388 };
15389 }
15390 }
15391 }
15392 }
15393
15394 let mut new_range = old_range.clone();
15395 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15396 new_range = range;
15397 if !node.is_named() {
15398 continue;
15399 }
15400 if !display_map.intersects_fold(new_range.start)
15401 && !display_map.intersects_fold(new_range.end)
15402 {
15403 break;
15404 }
15405 }
15406
15407 selected_larger_node |= new_range != old_range;
15408 Selection {
15409 id: selection.id,
15410 start: new_range.start,
15411 end: new_range.end,
15412 goal: SelectionGoal::None,
15413 reversed: selection.reversed,
15414 }
15415 })
15416 .collect::<Vec<_>>();
15417
15418 if !selected_larger_node {
15419 return; // don't put this call in the history
15420 }
15421
15422 // scroll based on transformation done to the last selection created by the user
15423 let (last_old, last_new) = old_selections
15424 .last()
15425 .zip(new_selections.last().cloned())
15426 .expect("old_selections isn't empty");
15427
15428 // revert selection
15429 let is_selection_reversed = {
15430 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15431 new_selections.last_mut().expect("checked above").reversed =
15432 should_newest_selection_be_reversed;
15433 should_newest_selection_be_reversed
15434 };
15435
15436 if selected_larger_node {
15437 self.select_syntax_node_history.disable_clearing = true;
15438 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15439 s.select(new_selections.clone());
15440 });
15441 self.select_syntax_node_history.disable_clearing = false;
15442 }
15443
15444 let start_row = last_new.start.to_display_point(&display_map).row().0;
15445 let end_row = last_new.end.to_display_point(&display_map).row().0;
15446 let selection_height = end_row - start_row + 1;
15447 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15448
15449 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15450 let scroll_behavior = if fits_on_the_screen {
15451 self.request_autoscroll(Autoscroll::fit(), cx);
15452 SelectSyntaxNodeScrollBehavior::FitSelection
15453 } else if is_selection_reversed {
15454 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15455 SelectSyntaxNodeScrollBehavior::CursorTop
15456 } else {
15457 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15458 SelectSyntaxNodeScrollBehavior::CursorBottom
15459 };
15460
15461 self.select_syntax_node_history.push((
15462 old_selections,
15463 scroll_behavior,
15464 is_selection_reversed,
15465 ));
15466 }
15467
15468 pub fn select_smaller_syntax_node(
15469 &mut self,
15470 _: &SelectSmallerSyntaxNode,
15471 window: &mut Window,
15472 cx: &mut Context<Self>,
15473 ) {
15474 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15475
15476 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15477 self.select_syntax_node_history.pop()
15478 {
15479 if let Some(selection) = selections.last_mut() {
15480 selection.reversed = is_selection_reversed;
15481 }
15482
15483 self.select_syntax_node_history.disable_clearing = true;
15484 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15485 s.select(selections.to_vec());
15486 });
15487 self.select_syntax_node_history.disable_clearing = false;
15488
15489 match scroll_behavior {
15490 SelectSyntaxNodeScrollBehavior::CursorTop => {
15491 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15492 }
15493 SelectSyntaxNodeScrollBehavior::FitSelection => {
15494 self.request_autoscroll(Autoscroll::fit(), cx);
15495 }
15496 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15497 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15498 }
15499 }
15500 }
15501 }
15502
15503 pub fn unwrap_syntax_node(
15504 &mut self,
15505 _: &UnwrapSyntaxNode,
15506 window: &mut Window,
15507 cx: &mut Context<Self>,
15508 ) {
15509 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15510
15511 let buffer = self.buffer.read(cx).snapshot(cx);
15512 let selections = self
15513 .selections
15514 .all::<usize>(&self.display_snapshot(cx))
15515 .into_iter()
15516 // subtracting the offset requires sorting
15517 .sorted_by_key(|i| i.start);
15518
15519 let full_edits = selections
15520 .into_iter()
15521 .filter_map(|selection| {
15522 let child = if selection.is_empty()
15523 && let Some((_, ancestor_range)) =
15524 buffer.syntax_ancestor(selection.start..selection.end)
15525 {
15526 ancestor_range
15527 } else {
15528 selection.range()
15529 };
15530
15531 let mut parent = child.clone();
15532 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15533 parent = ancestor_range;
15534 if parent.start < child.start || parent.end > child.end {
15535 break;
15536 }
15537 }
15538
15539 if parent == child {
15540 return None;
15541 }
15542 let text = buffer.text_for_range(child).collect::<String>();
15543 Some((selection.id, parent, text))
15544 })
15545 .collect::<Vec<_>>();
15546 if full_edits.is_empty() {
15547 return;
15548 }
15549
15550 self.transact(window, cx, |this, window, cx| {
15551 this.buffer.update(cx, |buffer, cx| {
15552 buffer.edit(
15553 full_edits
15554 .iter()
15555 .map(|(_, p, t)| (p.clone(), t.clone()))
15556 .collect::<Vec<_>>(),
15557 None,
15558 cx,
15559 );
15560 });
15561 this.change_selections(Default::default(), window, cx, |s| {
15562 let mut offset = 0;
15563 let mut selections = vec![];
15564 for (id, parent, text) in full_edits {
15565 let start = parent.start - offset;
15566 offset += parent.len() - text.len();
15567 selections.push(Selection {
15568 id,
15569 start,
15570 end: start + text.len(),
15571 reversed: false,
15572 goal: Default::default(),
15573 });
15574 }
15575 s.select(selections);
15576 });
15577 });
15578 }
15579
15580 pub fn select_next_syntax_node(
15581 &mut self,
15582 _: &SelectNextSyntaxNode,
15583 window: &mut Window,
15584 cx: &mut Context<Self>,
15585 ) {
15586 let old_selections: Box<[_]> = self
15587 .selections
15588 .all::<usize>(&self.display_snapshot(cx))
15589 .into();
15590 if old_selections.is_empty() {
15591 return;
15592 }
15593
15594 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15595
15596 let buffer = self.buffer.read(cx).snapshot(cx);
15597 let mut selected_sibling = false;
15598
15599 let new_selections = old_selections
15600 .iter()
15601 .map(|selection| {
15602 let old_range = selection.start..selection.end;
15603
15604 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15605 let new_range = node.byte_range();
15606 selected_sibling = true;
15607 Selection {
15608 id: selection.id,
15609 start: new_range.start,
15610 end: new_range.end,
15611 goal: SelectionGoal::None,
15612 reversed: selection.reversed,
15613 }
15614 } else {
15615 selection.clone()
15616 }
15617 })
15618 .collect::<Vec<_>>();
15619
15620 if selected_sibling {
15621 self.change_selections(
15622 SelectionEffects::scroll(Autoscroll::fit()),
15623 window,
15624 cx,
15625 |s| {
15626 s.select(new_selections);
15627 },
15628 );
15629 }
15630 }
15631
15632 pub fn select_prev_syntax_node(
15633 &mut self,
15634 _: &SelectPreviousSyntaxNode,
15635 window: &mut Window,
15636 cx: &mut Context<Self>,
15637 ) {
15638 let old_selections: Box<[_]> = self
15639 .selections
15640 .all::<usize>(&self.display_snapshot(cx))
15641 .into();
15642 if old_selections.is_empty() {
15643 return;
15644 }
15645
15646 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15647
15648 let buffer = self.buffer.read(cx).snapshot(cx);
15649 let mut selected_sibling = false;
15650
15651 let new_selections = old_selections
15652 .iter()
15653 .map(|selection| {
15654 let old_range = selection.start..selection.end;
15655
15656 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15657 let new_range = node.byte_range();
15658 selected_sibling = true;
15659 Selection {
15660 id: selection.id,
15661 start: new_range.start,
15662 end: new_range.end,
15663 goal: SelectionGoal::None,
15664 reversed: selection.reversed,
15665 }
15666 } else {
15667 selection.clone()
15668 }
15669 })
15670 .collect::<Vec<_>>();
15671
15672 if selected_sibling {
15673 self.change_selections(
15674 SelectionEffects::scroll(Autoscroll::fit()),
15675 window,
15676 cx,
15677 |s| {
15678 s.select(new_selections);
15679 },
15680 );
15681 }
15682 }
15683
15684 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15685 if !EditorSettings::get_global(cx).gutter.runnables {
15686 self.clear_tasks();
15687 return Task::ready(());
15688 }
15689 let project = self.project().map(Entity::downgrade);
15690 let task_sources = self.lsp_task_sources(cx);
15691 let multi_buffer = self.buffer.downgrade();
15692 cx.spawn_in(window, async move |editor, cx| {
15693 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15694 let Some(project) = project.and_then(|p| p.upgrade()) else {
15695 return;
15696 };
15697 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15698 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15699 }) else {
15700 return;
15701 };
15702
15703 let hide_runnables = project
15704 .update(cx, |project, _| project.is_via_collab())
15705 .unwrap_or(true);
15706 if hide_runnables {
15707 return;
15708 }
15709 let new_rows =
15710 cx.background_spawn({
15711 let snapshot = display_snapshot.clone();
15712 async move {
15713 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15714 }
15715 })
15716 .await;
15717 let Ok(lsp_tasks) =
15718 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15719 else {
15720 return;
15721 };
15722 let lsp_tasks = lsp_tasks.await;
15723
15724 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15725 lsp_tasks
15726 .into_iter()
15727 .flat_map(|(kind, tasks)| {
15728 tasks.into_iter().filter_map(move |(location, task)| {
15729 Some((kind.clone(), location?, task))
15730 })
15731 })
15732 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15733 let buffer = location.target.buffer;
15734 let buffer_snapshot = buffer.read(cx).snapshot();
15735 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15736 |(excerpt_id, snapshot, _)| {
15737 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15738 display_snapshot
15739 .buffer_snapshot()
15740 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15741 } else {
15742 None
15743 }
15744 },
15745 );
15746 if let Some(offset) = offset {
15747 let task_buffer_range =
15748 location.target.range.to_point(&buffer_snapshot);
15749 let context_buffer_range =
15750 task_buffer_range.to_offset(&buffer_snapshot);
15751 let context_range = BufferOffset(context_buffer_range.start)
15752 ..BufferOffset(context_buffer_range.end);
15753
15754 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15755 .or_insert_with(|| RunnableTasks {
15756 templates: Vec::new(),
15757 offset,
15758 column: task_buffer_range.start.column,
15759 extra_variables: HashMap::default(),
15760 context_range,
15761 })
15762 .templates
15763 .push((kind, task.original_task().clone()));
15764 }
15765
15766 acc
15767 })
15768 }) else {
15769 return;
15770 };
15771
15772 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15773 buffer.language_settings(cx).tasks.prefer_lsp
15774 }) else {
15775 return;
15776 };
15777
15778 let rows = Self::runnable_rows(
15779 project,
15780 display_snapshot,
15781 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15782 new_rows,
15783 cx.clone(),
15784 )
15785 .await;
15786 editor
15787 .update(cx, |editor, _| {
15788 editor.clear_tasks();
15789 for (key, mut value) in rows {
15790 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15791 value.templates.extend(lsp_tasks.templates);
15792 }
15793
15794 editor.insert_tasks(key, value);
15795 }
15796 for (key, value) in lsp_tasks_by_rows {
15797 editor.insert_tasks(key, value);
15798 }
15799 })
15800 .ok();
15801 })
15802 }
15803 fn fetch_runnable_ranges(
15804 snapshot: &DisplaySnapshot,
15805 range: Range<Anchor>,
15806 ) -> Vec<language::RunnableRange> {
15807 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15808 }
15809
15810 fn runnable_rows(
15811 project: Entity<Project>,
15812 snapshot: DisplaySnapshot,
15813 prefer_lsp: bool,
15814 runnable_ranges: Vec<RunnableRange>,
15815 cx: AsyncWindowContext,
15816 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15817 cx.spawn(async move |cx| {
15818 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15819 for mut runnable in runnable_ranges {
15820 let Some(tasks) = cx
15821 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15822 .ok()
15823 else {
15824 continue;
15825 };
15826 let mut tasks = tasks.await;
15827
15828 if prefer_lsp {
15829 tasks.retain(|(task_kind, _)| {
15830 !matches!(task_kind, TaskSourceKind::Language { .. })
15831 });
15832 }
15833 if tasks.is_empty() {
15834 continue;
15835 }
15836
15837 let point = runnable
15838 .run_range
15839 .start
15840 .to_point(&snapshot.buffer_snapshot());
15841 let Some(row) = snapshot
15842 .buffer_snapshot()
15843 .buffer_line_for_row(MultiBufferRow(point.row))
15844 .map(|(_, range)| range.start.row)
15845 else {
15846 continue;
15847 };
15848
15849 let context_range =
15850 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15851 runnable_rows.push((
15852 (runnable.buffer_id, row),
15853 RunnableTasks {
15854 templates: tasks,
15855 offset: snapshot
15856 .buffer_snapshot()
15857 .anchor_before(runnable.run_range.start),
15858 context_range,
15859 column: point.column,
15860 extra_variables: runnable.extra_captures,
15861 },
15862 ));
15863 }
15864 runnable_rows
15865 })
15866 }
15867
15868 fn templates_with_tags(
15869 project: &Entity<Project>,
15870 runnable: &mut Runnable,
15871 cx: &mut App,
15872 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15873 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15874 let (worktree_id, file) = project
15875 .buffer_for_id(runnable.buffer, cx)
15876 .and_then(|buffer| buffer.read(cx).file())
15877 .map(|file| (file.worktree_id(cx), file.clone()))
15878 .unzip();
15879
15880 (
15881 project.task_store().read(cx).task_inventory().cloned(),
15882 worktree_id,
15883 file,
15884 )
15885 });
15886
15887 let tags = mem::take(&mut runnable.tags);
15888 let language = runnable.language.clone();
15889 cx.spawn(async move |cx| {
15890 let mut templates_with_tags = Vec::new();
15891 if let Some(inventory) = inventory {
15892 for RunnableTag(tag) in tags {
15893 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15894 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15895 }) else {
15896 return templates_with_tags;
15897 };
15898 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15899 move |(_, template)| {
15900 template.tags.iter().any(|source_tag| source_tag == &tag)
15901 },
15902 ));
15903 }
15904 }
15905 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15906
15907 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15908 // Strongest source wins; if we have worktree tag binding, prefer that to
15909 // global and language bindings;
15910 // if we have a global binding, prefer that to language binding.
15911 let first_mismatch = templates_with_tags
15912 .iter()
15913 .position(|(tag_source, _)| tag_source != leading_tag_source);
15914 if let Some(index) = first_mismatch {
15915 templates_with_tags.truncate(index);
15916 }
15917 }
15918
15919 templates_with_tags
15920 })
15921 }
15922
15923 pub fn move_to_enclosing_bracket(
15924 &mut self,
15925 _: &MoveToEnclosingBracket,
15926 window: &mut Window,
15927 cx: &mut Context<Self>,
15928 ) {
15929 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15930 self.change_selections(Default::default(), window, cx, |s| {
15931 s.move_offsets_with(|snapshot, selection| {
15932 let Some(enclosing_bracket_ranges) =
15933 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15934 else {
15935 return;
15936 };
15937
15938 let mut best_length = usize::MAX;
15939 let mut best_inside = false;
15940 let mut best_in_bracket_range = false;
15941 let mut best_destination = None;
15942 for (open, close) in enclosing_bracket_ranges {
15943 let close = close.to_inclusive();
15944 let length = close.end() - open.start;
15945 let inside = selection.start >= open.end && selection.end <= *close.start();
15946 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15947 || close.contains(&selection.head());
15948
15949 // If best is next to a bracket and current isn't, skip
15950 if !in_bracket_range && best_in_bracket_range {
15951 continue;
15952 }
15953
15954 // Prefer smaller lengths unless best is inside and current isn't
15955 if length > best_length && (best_inside || !inside) {
15956 continue;
15957 }
15958
15959 best_length = length;
15960 best_inside = inside;
15961 best_in_bracket_range = in_bracket_range;
15962 best_destination = Some(
15963 if close.contains(&selection.start) && close.contains(&selection.end) {
15964 if inside { open.end } else { open.start }
15965 } else if inside {
15966 *close.start()
15967 } else {
15968 *close.end()
15969 },
15970 );
15971 }
15972
15973 if let Some(destination) = best_destination {
15974 selection.collapse_to(destination, SelectionGoal::None);
15975 }
15976 })
15977 });
15978 }
15979
15980 pub fn undo_selection(
15981 &mut self,
15982 _: &UndoSelection,
15983 window: &mut Window,
15984 cx: &mut Context<Self>,
15985 ) {
15986 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15987 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15988 self.selection_history.mode = SelectionHistoryMode::Undoing;
15989 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15990 this.end_selection(window, cx);
15991 this.change_selections(
15992 SelectionEffects::scroll(Autoscroll::newest()),
15993 window,
15994 cx,
15995 |s| s.select_anchors(entry.selections.to_vec()),
15996 );
15997 });
15998 self.selection_history.mode = SelectionHistoryMode::Normal;
15999
16000 self.select_next_state = entry.select_next_state;
16001 self.select_prev_state = entry.select_prev_state;
16002 self.add_selections_state = entry.add_selections_state;
16003 }
16004 }
16005
16006 pub fn redo_selection(
16007 &mut self,
16008 _: &RedoSelection,
16009 window: &mut Window,
16010 cx: &mut Context<Self>,
16011 ) {
16012 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16013 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16014 self.selection_history.mode = SelectionHistoryMode::Redoing;
16015 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16016 this.end_selection(window, cx);
16017 this.change_selections(
16018 SelectionEffects::scroll(Autoscroll::newest()),
16019 window,
16020 cx,
16021 |s| s.select_anchors(entry.selections.to_vec()),
16022 );
16023 });
16024 self.selection_history.mode = SelectionHistoryMode::Normal;
16025
16026 self.select_next_state = entry.select_next_state;
16027 self.select_prev_state = entry.select_prev_state;
16028 self.add_selections_state = entry.add_selections_state;
16029 }
16030 }
16031
16032 pub fn expand_excerpts(
16033 &mut self,
16034 action: &ExpandExcerpts,
16035 _: &mut Window,
16036 cx: &mut Context<Self>,
16037 ) {
16038 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16039 }
16040
16041 pub fn expand_excerpts_down(
16042 &mut self,
16043 action: &ExpandExcerptsDown,
16044 _: &mut Window,
16045 cx: &mut Context<Self>,
16046 ) {
16047 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16048 }
16049
16050 pub fn expand_excerpts_up(
16051 &mut self,
16052 action: &ExpandExcerptsUp,
16053 _: &mut Window,
16054 cx: &mut Context<Self>,
16055 ) {
16056 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16057 }
16058
16059 pub fn expand_excerpts_for_direction(
16060 &mut self,
16061 lines: u32,
16062 direction: ExpandExcerptDirection,
16063
16064 cx: &mut Context<Self>,
16065 ) {
16066 let selections = self.selections.disjoint_anchors_arc();
16067
16068 let lines = if lines == 0 {
16069 EditorSettings::get_global(cx).expand_excerpt_lines
16070 } else {
16071 lines
16072 };
16073
16074 self.buffer.update(cx, |buffer, cx| {
16075 let snapshot = buffer.snapshot(cx);
16076 let mut excerpt_ids = selections
16077 .iter()
16078 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16079 .collect::<Vec<_>>();
16080 excerpt_ids.sort();
16081 excerpt_ids.dedup();
16082 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16083 })
16084 }
16085
16086 pub fn expand_excerpt(
16087 &mut self,
16088 excerpt: ExcerptId,
16089 direction: ExpandExcerptDirection,
16090 window: &mut Window,
16091 cx: &mut Context<Self>,
16092 ) {
16093 let current_scroll_position = self.scroll_position(cx);
16094 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16095 let mut scroll = None;
16096
16097 if direction == ExpandExcerptDirection::Down {
16098 let multi_buffer = self.buffer.read(cx);
16099 let snapshot = multi_buffer.snapshot(cx);
16100 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16101 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16102 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16103 {
16104 let buffer_snapshot = buffer.read(cx).snapshot();
16105 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16106 let last_row = buffer_snapshot.max_point().row;
16107 let lines_below = last_row.saturating_sub(excerpt_end_row);
16108 if lines_below >= lines_to_expand {
16109 scroll = Some(
16110 current_scroll_position
16111 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16112 );
16113 }
16114 }
16115 }
16116 if direction == ExpandExcerptDirection::Up
16117 && self
16118 .buffer
16119 .read(cx)
16120 .snapshot(cx)
16121 .excerpt_before(excerpt)
16122 .is_none()
16123 {
16124 scroll = Some(current_scroll_position);
16125 }
16126
16127 self.buffer.update(cx, |buffer, cx| {
16128 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16129 });
16130
16131 if let Some(new_scroll_position) = scroll {
16132 self.set_scroll_position(new_scroll_position, window, cx);
16133 }
16134 }
16135
16136 pub fn go_to_singleton_buffer_point(
16137 &mut self,
16138 point: Point,
16139 window: &mut Window,
16140 cx: &mut Context<Self>,
16141 ) {
16142 self.go_to_singleton_buffer_range(point..point, window, cx);
16143 }
16144
16145 pub fn go_to_singleton_buffer_range(
16146 &mut self,
16147 range: Range<Point>,
16148 window: &mut Window,
16149 cx: &mut Context<Self>,
16150 ) {
16151 let multibuffer = self.buffer().read(cx);
16152 let Some(buffer) = multibuffer.as_singleton() else {
16153 return;
16154 };
16155 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16156 return;
16157 };
16158 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16159 return;
16160 };
16161 self.change_selections(
16162 SelectionEffects::default().nav_history(true),
16163 window,
16164 cx,
16165 |s| s.select_anchor_ranges([start..end]),
16166 );
16167 }
16168
16169 pub fn go_to_diagnostic(
16170 &mut self,
16171 action: &GoToDiagnostic,
16172 window: &mut Window,
16173 cx: &mut Context<Self>,
16174 ) {
16175 if !self.diagnostics_enabled() {
16176 return;
16177 }
16178 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16179 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16180 }
16181
16182 pub fn go_to_prev_diagnostic(
16183 &mut self,
16184 action: &GoToPreviousDiagnostic,
16185 window: &mut Window,
16186 cx: &mut Context<Self>,
16187 ) {
16188 if !self.diagnostics_enabled() {
16189 return;
16190 }
16191 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16192 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16193 }
16194
16195 pub fn go_to_diagnostic_impl(
16196 &mut self,
16197 direction: Direction,
16198 severity: GoToDiagnosticSeverityFilter,
16199 window: &mut Window,
16200 cx: &mut Context<Self>,
16201 ) {
16202 let buffer = self.buffer.read(cx).snapshot(cx);
16203 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16204
16205 let mut active_group_id = None;
16206 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16207 && active_group.active_range.start.to_offset(&buffer) == selection.start
16208 {
16209 active_group_id = Some(active_group.group_id);
16210 }
16211
16212 fn filtered<'a>(
16213 severity: GoToDiagnosticSeverityFilter,
16214 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16215 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16216 diagnostics
16217 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16218 .filter(|entry| entry.range.start != entry.range.end)
16219 .filter(|entry| !entry.diagnostic.is_unnecessary)
16220 }
16221
16222 let before = filtered(
16223 severity,
16224 buffer
16225 .diagnostics_in_range(0..selection.start)
16226 .filter(|entry| entry.range.start <= selection.start),
16227 );
16228 let after = filtered(
16229 severity,
16230 buffer
16231 .diagnostics_in_range(selection.start..buffer.len())
16232 .filter(|entry| entry.range.start >= selection.start),
16233 );
16234
16235 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16236 if direction == Direction::Prev {
16237 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16238 {
16239 for diagnostic in prev_diagnostics.into_iter().rev() {
16240 if diagnostic.range.start != selection.start
16241 || active_group_id
16242 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16243 {
16244 found = Some(diagnostic);
16245 break 'outer;
16246 }
16247 }
16248 }
16249 } else {
16250 for diagnostic in after.chain(before) {
16251 if diagnostic.range.start != selection.start
16252 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16253 {
16254 found = Some(diagnostic);
16255 break;
16256 }
16257 }
16258 }
16259 let Some(next_diagnostic) = found else {
16260 return;
16261 };
16262
16263 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16264 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16265 return;
16266 };
16267 let snapshot = self.snapshot(window, cx);
16268 if snapshot.intersects_fold(next_diagnostic.range.start) {
16269 self.unfold_ranges(
16270 std::slice::from_ref(&next_diagnostic.range),
16271 true,
16272 false,
16273 cx,
16274 );
16275 }
16276 self.change_selections(Default::default(), window, cx, |s| {
16277 s.select_ranges(vec![
16278 next_diagnostic.range.start..next_diagnostic.range.start,
16279 ])
16280 });
16281 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16282 self.refresh_edit_prediction(false, true, window, cx);
16283 }
16284
16285 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16286 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16287 let snapshot = self.snapshot(window, cx);
16288 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16289 self.go_to_hunk_before_or_after_position(
16290 &snapshot,
16291 selection.head(),
16292 Direction::Next,
16293 window,
16294 cx,
16295 );
16296 }
16297
16298 pub fn go_to_hunk_before_or_after_position(
16299 &mut self,
16300 snapshot: &EditorSnapshot,
16301 position: Point,
16302 direction: Direction,
16303 window: &mut Window,
16304 cx: &mut Context<Editor>,
16305 ) {
16306 let row = if direction == Direction::Next {
16307 self.hunk_after_position(snapshot, position)
16308 .map(|hunk| hunk.row_range.start)
16309 } else {
16310 self.hunk_before_position(snapshot, position)
16311 };
16312
16313 if let Some(row) = row {
16314 let destination = Point::new(row.0, 0);
16315 let autoscroll = Autoscroll::center();
16316
16317 self.unfold_ranges(&[destination..destination], false, false, cx);
16318 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16319 s.select_ranges([destination..destination]);
16320 });
16321 }
16322 }
16323
16324 fn hunk_after_position(
16325 &mut self,
16326 snapshot: &EditorSnapshot,
16327 position: Point,
16328 ) -> Option<MultiBufferDiffHunk> {
16329 snapshot
16330 .buffer_snapshot()
16331 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16332 .find(|hunk| hunk.row_range.start.0 > position.row)
16333 .or_else(|| {
16334 snapshot
16335 .buffer_snapshot()
16336 .diff_hunks_in_range(Point::zero()..position)
16337 .find(|hunk| hunk.row_range.end.0 < position.row)
16338 })
16339 }
16340
16341 fn go_to_prev_hunk(
16342 &mut self,
16343 _: &GoToPreviousHunk,
16344 window: &mut Window,
16345 cx: &mut Context<Self>,
16346 ) {
16347 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16348 let snapshot = self.snapshot(window, cx);
16349 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16350 self.go_to_hunk_before_or_after_position(
16351 &snapshot,
16352 selection.head(),
16353 Direction::Prev,
16354 window,
16355 cx,
16356 );
16357 }
16358
16359 fn hunk_before_position(
16360 &mut self,
16361 snapshot: &EditorSnapshot,
16362 position: Point,
16363 ) -> Option<MultiBufferRow> {
16364 snapshot
16365 .buffer_snapshot()
16366 .diff_hunk_before(position)
16367 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16368 }
16369
16370 fn go_to_next_change(
16371 &mut self,
16372 _: &GoToNextChange,
16373 window: &mut Window,
16374 cx: &mut Context<Self>,
16375 ) {
16376 if let Some(selections) = self
16377 .change_list
16378 .next_change(1, Direction::Next)
16379 .map(|s| s.to_vec())
16380 {
16381 self.change_selections(Default::default(), window, cx, |s| {
16382 let map = s.display_snapshot();
16383 s.select_display_ranges(selections.iter().map(|a| {
16384 let point = a.to_display_point(&map);
16385 point..point
16386 }))
16387 })
16388 }
16389 }
16390
16391 fn go_to_previous_change(
16392 &mut self,
16393 _: &GoToPreviousChange,
16394 window: &mut Window,
16395 cx: &mut Context<Self>,
16396 ) {
16397 if let Some(selections) = self
16398 .change_list
16399 .next_change(1, Direction::Prev)
16400 .map(|s| s.to_vec())
16401 {
16402 self.change_selections(Default::default(), window, cx, |s| {
16403 let map = s.display_snapshot();
16404 s.select_display_ranges(selections.iter().map(|a| {
16405 let point = a.to_display_point(&map);
16406 point..point
16407 }))
16408 })
16409 }
16410 }
16411
16412 pub fn go_to_next_document_highlight(
16413 &mut self,
16414 _: &GoToNextDocumentHighlight,
16415 window: &mut Window,
16416 cx: &mut Context<Self>,
16417 ) {
16418 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16419 }
16420
16421 pub fn go_to_prev_document_highlight(
16422 &mut self,
16423 _: &GoToPreviousDocumentHighlight,
16424 window: &mut Window,
16425 cx: &mut Context<Self>,
16426 ) {
16427 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16428 }
16429
16430 pub fn go_to_document_highlight_before_or_after_position(
16431 &mut self,
16432 direction: Direction,
16433 window: &mut Window,
16434 cx: &mut Context<Editor>,
16435 ) {
16436 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16437 let snapshot = self.snapshot(window, cx);
16438 let buffer = &snapshot.buffer_snapshot();
16439 let position = self
16440 .selections
16441 .newest::<Point>(&snapshot.display_snapshot)
16442 .head();
16443 let anchor_position = buffer.anchor_after(position);
16444
16445 // Get all document highlights (both read and write)
16446 let mut all_highlights = Vec::new();
16447
16448 if let Some((_, read_highlights)) = self
16449 .background_highlights
16450 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16451 {
16452 all_highlights.extend(read_highlights.iter());
16453 }
16454
16455 if let Some((_, write_highlights)) = self
16456 .background_highlights
16457 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16458 {
16459 all_highlights.extend(write_highlights.iter());
16460 }
16461
16462 if all_highlights.is_empty() {
16463 return;
16464 }
16465
16466 // Sort highlights by position
16467 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16468
16469 let target_highlight = match direction {
16470 Direction::Next => {
16471 // Find the first highlight after the current position
16472 all_highlights
16473 .iter()
16474 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16475 }
16476 Direction::Prev => {
16477 // Find the last highlight before the current position
16478 all_highlights
16479 .iter()
16480 .rev()
16481 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16482 }
16483 };
16484
16485 if let Some(highlight) = target_highlight {
16486 let destination = highlight.start.to_point(buffer);
16487 let autoscroll = Autoscroll::center();
16488
16489 self.unfold_ranges(&[destination..destination], false, false, cx);
16490 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16491 s.select_ranges([destination..destination]);
16492 });
16493 }
16494 }
16495
16496 fn go_to_line<T: 'static>(
16497 &mut self,
16498 position: Anchor,
16499 highlight_color: Option<Hsla>,
16500 window: &mut Window,
16501 cx: &mut Context<Self>,
16502 ) {
16503 let snapshot = self.snapshot(window, cx).display_snapshot;
16504 let position = position.to_point(&snapshot.buffer_snapshot());
16505 let start = snapshot
16506 .buffer_snapshot()
16507 .clip_point(Point::new(position.row, 0), Bias::Left);
16508 let end = start + Point::new(1, 0);
16509 let start = snapshot.buffer_snapshot().anchor_before(start);
16510 let end = snapshot.buffer_snapshot().anchor_before(end);
16511
16512 self.highlight_rows::<T>(
16513 start..end,
16514 highlight_color
16515 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16516 Default::default(),
16517 cx,
16518 );
16519
16520 if self.buffer.read(cx).is_singleton() {
16521 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16522 }
16523 }
16524
16525 pub fn go_to_definition(
16526 &mut self,
16527 _: &GoToDefinition,
16528 window: &mut Window,
16529 cx: &mut Context<Self>,
16530 ) -> Task<Result<Navigated>> {
16531 let definition =
16532 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16533 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16534 cx.spawn_in(window, async move |editor, cx| {
16535 if definition.await? == Navigated::Yes {
16536 return Ok(Navigated::Yes);
16537 }
16538 match fallback_strategy {
16539 GoToDefinitionFallback::None => Ok(Navigated::No),
16540 GoToDefinitionFallback::FindAllReferences => {
16541 match editor.update_in(cx, |editor, window, cx| {
16542 editor.find_all_references(&FindAllReferences, window, cx)
16543 })? {
16544 Some(references) => references.await,
16545 None => Ok(Navigated::No),
16546 }
16547 }
16548 }
16549 })
16550 }
16551
16552 pub fn go_to_declaration(
16553 &mut self,
16554 _: &GoToDeclaration,
16555 window: &mut Window,
16556 cx: &mut Context<Self>,
16557 ) -> Task<Result<Navigated>> {
16558 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16559 }
16560
16561 pub fn go_to_declaration_split(
16562 &mut self,
16563 _: &GoToDeclaration,
16564 window: &mut Window,
16565 cx: &mut Context<Self>,
16566 ) -> Task<Result<Navigated>> {
16567 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16568 }
16569
16570 pub fn go_to_implementation(
16571 &mut self,
16572 _: &GoToImplementation,
16573 window: &mut Window,
16574 cx: &mut Context<Self>,
16575 ) -> Task<Result<Navigated>> {
16576 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16577 }
16578
16579 pub fn go_to_implementation_split(
16580 &mut self,
16581 _: &GoToImplementationSplit,
16582 window: &mut Window,
16583 cx: &mut Context<Self>,
16584 ) -> Task<Result<Navigated>> {
16585 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16586 }
16587
16588 pub fn go_to_type_definition(
16589 &mut self,
16590 _: &GoToTypeDefinition,
16591 window: &mut Window,
16592 cx: &mut Context<Self>,
16593 ) -> Task<Result<Navigated>> {
16594 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16595 }
16596
16597 pub fn go_to_definition_split(
16598 &mut self,
16599 _: &GoToDefinitionSplit,
16600 window: &mut Window,
16601 cx: &mut Context<Self>,
16602 ) -> Task<Result<Navigated>> {
16603 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16604 }
16605
16606 pub fn go_to_type_definition_split(
16607 &mut self,
16608 _: &GoToTypeDefinitionSplit,
16609 window: &mut Window,
16610 cx: &mut Context<Self>,
16611 ) -> Task<Result<Navigated>> {
16612 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16613 }
16614
16615 fn go_to_definition_of_kind(
16616 &mut self,
16617 kind: GotoDefinitionKind,
16618 split: bool,
16619 window: &mut Window,
16620 cx: &mut Context<Self>,
16621 ) -> Task<Result<Navigated>> {
16622 let Some(provider) = self.semantics_provider.clone() else {
16623 return Task::ready(Ok(Navigated::No));
16624 };
16625 let head = self
16626 .selections
16627 .newest::<usize>(&self.display_snapshot(cx))
16628 .head();
16629 let buffer = self.buffer.read(cx);
16630 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16631 return Task::ready(Ok(Navigated::No));
16632 };
16633 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16634 return Task::ready(Ok(Navigated::No));
16635 };
16636
16637 cx.spawn_in(window, async move |editor, cx| {
16638 let Some(definitions) = definitions.await? else {
16639 return Ok(Navigated::No);
16640 };
16641 let navigated = editor
16642 .update_in(cx, |editor, window, cx| {
16643 editor.navigate_to_hover_links(
16644 Some(kind),
16645 definitions
16646 .into_iter()
16647 .filter(|location| {
16648 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16649 })
16650 .map(HoverLink::Text)
16651 .collect::<Vec<_>>(),
16652 split,
16653 window,
16654 cx,
16655 )
16656 })?
16657 .await?;
16658 anyhow::Ok(navigated)
16659 })
16660 }
16661
16662 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16663 let selection = self.selections.newest_anchor();
16664 let head = selection.head();
16665 let tail = selection.tail();
16666
16667 let Some((buffer, start_position)) =
16668 self.buffer.read(cx).text_anchor_for_position(head, cx)
16669 else {
16670 return;
16671 };
16672
16673 let end_position = if head != tail {
16674 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16675 return;
16676 };
16677 Some(pos)
16678 } else {
16679 None
16680 };
16681
16682 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16683 let url = if let Some(end_pos) = end_position {
16684 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16685 } else {
16686 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16687 };
16688
16689 if let Some(url) = url {
16690 cx.update(|window, cx| {
16691 if parse_zed_link(&url, cx).is_some() {
16692 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16693 } else {
16694 cx.open_url(&url);
16695 }
16696 })?;
16697 }
16698
16699 anyhow::Ok(())
16700 });
16701
16702 url_finder.detach();
16703 }
16704
16705 pub fn open_selected_filename(
16706 &mut self,
16707 _: &OpenSelectedFilename,
16708 window: &mut Window,
16709 cx: &mut Context<Self>,
16710 ) {
16711 let Some(workspace) = self.workspace() else {
16712 return;
16713 };
16714
16715 let position = self.selections.newest_anchor().head();
16716
16717 let Some((buffer, buffer_position)) =
16718 self.buffer.read(cx).text_anchor_for_position(position, cx)
16719 else {
16720 return;
16721 };
16722
16723 let project = self.project.clone();
16724
16725 cx.spawn_in(window, async move |_, cx| {
16726 let result = find_file(&buffer, project, buffer_position, cx).await;
16727
16728 if let Some((_, path)) = result {
16729 workspace
16730 .update_in(cx, |workspace, window, cx| {
16731 workspace.open_resolved_path(path, window, cx)
16732 })?
16733 .await?;
16734 }
16735 anyhow::Ok(())
16736 })
16737 .detach();
16738 }
16739
16740 pub(crate) fn navigate_to_hover_links(
16741 &mut self,
16742 kind: Option<GotoDefinitionKind>,
16743 definitions: Vec<HoverLink>,
16744 split: bool,
16745 window: &mut Window,
16746 cx: &mut Context<Editor>,
16747 ) -> Task<Result<Navigated>> {
16748 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16749 let mut first_url_or_file = None;
16750 let definitions: Vec<_> = definitions
16751 .into_iter()
16752 .filter_map(|def| match def {
16753 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16754 HoverLink::InlayHint(lsp_location, server_id) => {
16755 let computation =
16756 self.compute_target_location(lsp_location, server_id, window, cx);
16757 Some(cx.background_spawn(computation))
16758 }
16759 HoverLink::Url(url) => {
16760 first_url_or_file = Some(Either::Left(url));
16761 None
16762 }
16763 HoverLink::File(path) => {
16764 first_url_or_file = Some(Either::Right(path));
16765 None
16766 }
16767 })
16768 .collect();
16769
16770 let workspace = self.workspace();
16771
16772 cx.spawn_in(window, async move |editor, cx| {
16773 let locations: Vec<Location> = future::join_all(definitions)
16774 .await
16775 .into_iter()
16776 .filter_map(|location| location.transpose())
16777 .collect::<Result<_>>()
16778 .context("location tasks")?;
16779 let mut locations = cx.update(|_, cx| {
16780 locations
16781 .into_iter()
16782 .map(|location| {
16783 let buffer = location.buffer.read(cx);
16784 (location.buffer, location.range.to_point(buffer))
16785 })
16786 .into_group_map()
16787 })?;
16788 let mut num_locations = 0;
16789 for ranges in locations.values_mut() {
16790 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16791 ranges.dedup();
16792 num_locations += ranges.len();
16793 }
16794
16795 if num_locations > 1 {
16796 let Some(workspace) = workspace else {
16797 return Ok(Navigated::No);
16798 };
16799
16800 let tab_kind = match kind {
16801 Some(GotoDefinitionKind::Implementation) => "Implementations",
16802 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16803 Some(GotoDefinitionKind::Declaration) => "Declarations",
16804 Some(GotoDefinitionKind::Type) => "Types",
16805 };
16806 let title = editor
16807 .update_in(cx, |_, _, cx| {
16808 let target = locations
16809 .iter()
16810 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16811 .map(|(buffer, location)| {
16812 buffer
16813 .read(cx)
16814 .text_for_range(location.clone())
16815 .collect::<String>()
16816 })
16817 .filter(|text| !text.contains('\n'))
16818 .unique()
16819 .take(3)
16820 .join(", ");
16821 if target.is_empty() {
16822 tab_kind.to_owned()
16823 } else {
16824 format!("{tab_kind} for {target}")
16825 }
16826 })
16827 .context("buffer title")?;
16828
16829 let opened = workspace
16830 .update_in(cx, |workspace, window, cx| {
16831 Self::open_locations_in_multibuffer(
16832 workspace,
16833 locations,
16834 title,
16835 split,
16836 MultibufferSelectionMode::First,
16837 window,
16838 cx,
16839 )
16840 })
16841 .is_ok();
16842
16843 anyhow::Ok(Navigated::from_bool(opened))
16844 } else if num_locations == 0 {
16845 // If there is one url or file, open it directly
16846 match first_url_or_file {
16847 Some(Either::Left(url)) => {
16848 cx.update(|_, cx| cx.open_url(&url))?;
16849 Ok(Navigated::Yes)
16850 }
16851 Some(Either::Right(path)) => {
16852 let Some(workspace) = workspace else {
16853 return Ok(Navigated::No);
16854 };
16855
16856 workspace
16857 .update_in(cx, |workspace, window, cx| {
16858 workspace.open_resolved_path(path, window, cx)
16859 })?
16860 .await?;
16861 Ok(Navigated::Yes)
16862 }
16863 None => Ok(Navigated::No),
16864 }
16865 } else {
16866 let Some(workspace) = workspace else {
16867 return Ok(Navigated::No);
16868 };
16869
16870 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16871 let target_range = target_ranges.first().unwrap().clone();
16872
16873 editor.update_in(cx, |editor, window, cx| {
16874 let range = target_range.to_point(target_buffer.read(cx));
16875 let range = editor.range_for_match(&range, false);
16876 let range = collapse_multiline_range(range);
16877
16878 if !split
16879 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16880 {
16881 editor.go_to_singleton_buffer_range(range, window, cx);
16882 } else {
16883 let pane = workspace.read(cx).active_pane().clone();
16884 window.defer(cx, move |window, cx| {
16885 let target_editor: Entity<Self> =
16886 workspace.update(cx, |workspace, cx| {
16887 let pane = if split {
16888 workspace.adjacent_pane(window, cx)
16889 } else {
16890 workspace.active_pane().clone()
16891 };
16892
16893 workspace.open_project_item(
16894 pane,
16895 target_buffer.clone(),
16896 true,
16897 true,
16898 window,
16899 cx,
16900 )
16901 });
16902 target_editor.update(cx, |target_editor, cx| {
16903 // When selecting a definition in a different buffer, disable the nav history
16904 // to avoid creating a history entry at the previous cursor location.
16905 pane.update(cx, |pane, _| pane.disable_history());
16906 target_editor.go_to_singleton_buffer_range(range, window, cx);
16907 pane.update(cx, |pane, _| pane.enable_history());
16908 });
16909 });
16910 }
16911 Navigated::Yes
16912 })
16913 }
16914 })
16915 }
16916
16917 fn compute_target_location(
16918 &self,
16919 lsp_location: lsp::Location,
16920 server_id: LanguageServerId,
16921 window: &mut Window,
16922 cx: &mut Context<Self>,
16923 ) -> Task<anyhow::Result<Option<Location>>> {
16924 let Some(project) = self.project.clone() else {
16925 return Task::ready(Ok(None));
16926 };
16927
16928 cx.spawn_in(window, async move |editor, cx| {
16929 let location_task = editor.update(cx, |_, cx| {
16930 project.update(cx, |project, cx| {
16931 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16932 })
16933 })?;
16934 let location = Some({
16935 let target_buffer_handle = location_task.await.context("open local buffer")?;
16936 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16937 let target_start = target_buffer
16938 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16939 let target_end = target_buffer
16940 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16941 target_buffer.anchor_after(target_start)
16942 ..target_buffer.anchor_before(target_end)
16943 })?;
16944 Location {
16945 buffer: target_buffer_handle,
16946 range,
16947 }
16948 });
16949 Ok(location)
16950 })
16951 }
16952
16953 fn go_to_next_reference(
16954 &mut self,
16955 _: &GoToNextReference,
16956 window: &mut Window,
16957 cx: &mut Context<Self>,
16958 ) {
16959 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
16960 if let Some(task) = task {
16961 task.detach();
16962 };
16963 }
16964
16965 fn go_to_prev_reference(
16966 &mut self,
16967 _: &GoToPreviousReference,
16968 window: &mut Window,
16969 cx: &mut Context<Self>,
16970 ) {
16971 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
16972 if let Some(task) = task {
16973 task.detach();
16974 };
16975 }
16976
16977 pub fn go_to_reference_before_or_after_position(
16978 &mut self,
16979 direction: Direction,
16980 count: usize,
16981 window: &mut Window,
16982 cx: &mut Context<Self>,
16983 ) -> Option<Task<Result<()>>> {
16984 let selection = self.selections.newest_anchor();
16985 let head = selection.head();
16986
16987 let multi_buffer = self.buffer.read(cx);
16988
16989 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
16990 let workspace = self.workspace()?;
16991 let project = workspace.read(cx).project().clone();
16992 let references =
16993 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
16994 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
16995 let Some(locations) = references.await? else {
16996 return Ok(());
16997 };
16998
16999 if locations.is_empty() {
17000 // totally normal - the cursor may be on something which is not
17001 // a symbol (e.g. a keyword)
17002 log::info!("no references found under cursor");
17003 return Ok(());
17004 }
17005
17006 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17007
17008 let multi_buffer_snapshot =
17009 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
17010
17011 let (locations, current_location_index) =
17012 multi_buffer.update(cx, |multi_buffer, cx| {
17013 let mut locations = locations
17014 .into_iter()
17015 .filter_map(|loc| {
17016 let start = multi_buffer.buffer_anchor_to_anchor(
17017 &loc.buffer,
17018 loc.range.start,
17019 cx,
17020 )?;
17021 let end = multi_buffer.buffer_anchor_to_anchor(
17022 &loc.buffer,
17023 loc.range.end,
17024 cx,
17025 )?;
17026 Some(start..end)
17027 })
17028 .collect::<Vec<_>>();
17029
17030 // There is an O(n) implementation, but given this list will be
17031 // small (usually <100 items), the extra O(log(n)) factor isn't
17032 // worth the (surprisingly large amount of) extra complexity.
17033 locations
17034 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17035
17036 let head_offset = head.to_offset(&multi_buffer_snapshot);
17037
17038 let current_location_index = locations.iter().position(|loc| {
17039 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17040 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17041 });
17042
17043 (locations, current_location_index)
17044 })?;
17045
17046 let Some(current_location_index) = current_location_index else {
17047 // This indicates something has gone wrong, because we already
17048 // handle the "no references" case above
17049 log::error!(
17050 "failed to find current reference under cursor. Total references: {}",
17051 locations.len()
17052 );
17053 return Ok(());
17054 };
17055
17056 let destination_location_index = match direction {
17057 Direction::Next => (current_location_index + count) % locations.len(),
17058 Direction::Prev => {
17059 (current_location_index + locations.len() - count % locations.len())
17060 % locations.len()
17061 }
17062 };
17063
17064 // TODO(cameron): is this needed?
17065 // the thinking is to avoid "jumping to the current location" (avoid
17066 // polluting "jumplist" in vim terms)
17067 if current_location_index == destination_location_index {
17068 return Ok(());
17069 }
17070
17071 let Range { start, end } = locations[destination_location_index];
17072
17073 editor.update_in(cx, |editor, window, cx| {
17074 let effects = SelectionEffects::default();
17075
17076 editor.unfold_ranges(&[start..end], false, false, cx);
17077 editor.change_selections(effects, window, cx, |s| {
17078 s.select_ranges([start..start]);
17079 });
17080 })?;
17081
17082 Ok(())
17083 }))
17084 }
17085
17086 pub fn find_all_references(
17087 &mut self,
17088 _: &FindAllReferences,
17089 window: &mut Window,
17090 cx: &mut Context<Self>,
17091 ) -> Option<Task<Result<Navigated>>> {
17092 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
17093 let multi_buffer = self.buffer.read(cx);
17094 let head = selection.head();
17095
17096 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17097 let head_anchor = multi_buffer_snapshot.anchor_at(
17098 head,
17099 if head < selection.tail() {
17100 Bias::Right
17101 } else {
17102 Bias::Left
17103 },
17104 );
17105
17106 match self
17107 .find_all_references_task_sources
17108 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17109 {
17110 Ok(_) => {
17111 log::info!(
17112 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17113 );
17114 return None;
17115 }
17116 Err(i) => {
17117 self.find_all_references_task_sources.insert(i, head_anchor);
17118 }
17119 }
17120
17121 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17122 let workspace = self.workspace()?;
17123 let project = workspace.read(cx).project().clone();
17124 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17125 Some(cx.spawn_in(window, async move |editor, cx| {
17126 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17127 if let Ok(i) = editor
17128 .find_all_references_task_sources
17129 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17130 {
17131 editor.find_all_references_task_sources.remove(i);
17132 }
17133 });
17134
17135 let Some(locations) = references.await? else {
17136 return anyhow::Ok(Navigated::No);
17137 };
17138 let mut locations = cx.update(|_, cx| {
17139 locations
17140 .into_iter()
17141 .map(|location| {
17142 let buffer = location.buffer.read(cx);
17143 (location.buffer, location.range.to_point(buffer))
17144 })
17145 .into_group_map()
17146 })?;
17147 if locations.is_empty() {
17148 return anyhow::Ok(Navigated::No);
17149 }
17150 for ranges in locations.values_mut() {
17151 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17152 ranges.dedup();
17153 }
17154
17155 workspace.update_in(cx, |workspace, window, cx| {
17156 let target = locations
17157 .iter()
17158 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17159 .map(|(buffer, location)| {
17160 buffer
17161 .read(cx)
17162 .text_for_range(location.clone())
17163 .collect::<String>()
17164 })
17165 .filter(|text| !text.contains('\n'))
17166 .unique()
17167 .take(3)
17168 .join(", ");
17169 let title = if target.is_empty() {
17170 "References".to_owned()
17171 } else {
17172 format!("References to {target}")
17173 };
17174 Self::open_locations_in_multibuffer(
17175 workspace,
17176 locations,
17177 title,
17178 false,
17179 MultibufferSelectionMode::First,
17180 window,
17181 cx,
17182 );
17183 Navigated::Yes
17184 })
17185 }))
17186 }
17187
17188 /// Opens a multibuffer with the given project locations in it
17189 pub fn open_locations_in_multibuffer(
17190 workspace: &mut Workspace,
17191 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17192 title: String,
17193 split: bool,
17194 multibuffer_selection_mode: MultibufferSelectionMode,
17195 window: &mut Window,
17196 cx: &mut Context<Workspace>,
17197 ) {
17198 if locations.is_empty() {
17199 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17200 return;
17201 }
17202
17203 let capability = workspace.project().read(cx).capability();
17204 let mut ranges = <Vec<Range<Anchor>>>::new();
17205
17206 // a key to find existing multibuffer editors with the same set of locations
17207 // to prevent us from opening more and more multibuffer tabs for searches and the like
17208 let mut key = (title.clone(), vec![]);
17209 let excerpt_buffer = cx.new(|cx| {
17210 let key = &mut key.1;
17211 let mut multibuffer = MultiBuffer::new(capability);
17212 for (buffer, mut ranges_for_buffer) in locations {
17213 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17214 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17215 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17216 PathKey::for_buffer(&buffer, cx),
17217 buffer.clone(),
17218 ranges_for_buffer,
17219 multibuffer_context_lines(cx),
17220 cx,
17221 );
17222 ranges.extend(new_ranges)
17223 }
17224
17225 multibuffer.with_title(title)
17226 });
17227 let existing = workspace.active_pane().update(cx, |pane, cx| {
17228 pane.items()
17229 .filter_map(|item| item.downcast::<Editor>())
17230 .find(|editor| {
17231 editor
17232 .read(cx)
17233 .lookup_key
17234 .as_ref()
17235 .and_then(|it| {
17236 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17237 })
17238 .is_some_and(|it| *it == key)
17239 })
17240 });
17241 let editor = existing.unwrap_or_else(|| {
17242 cx.new(|cx| {
17243 let mut editor = Editor::for_multibuffer(
17244 excerpt_buffer,
17245 Some(workspace.project().clone()),
17246 window,
17247 cx,
17248 );
17249 editor.lookup_key = Some(Box::new(key));
17250 editor
17251 })
17252 });
17253 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17254 MultibufferSelectionMode::First => {
17255 if let Some(first_range) = ranges.first() {
17256 editor.change_selections(
17257 SelectionEffects::no_scroll(),
17258 window,
17259 cx,
17260 |selections| {
17261 selections.clear_disjoint();
17262 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17263 },
17264 );
17265 }
17266 editor.highlight_background::<Self>(
17267 &ranges,
17268 |theme| theme.colors().editor_highlighted_line_background,
17269 cx,
17270 );
17271 }
17272 MultibufferSelectionMode::All => {
17273 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17274 selections.clear_disjoint();
17275 selections.select_anchor_ranges(ranges);
17276 });
17277 }
17278 });
17279
17280 let item = Box::new(editor);
17281 let item_id = item.item_id();
17282
17283 if split {
17284 let pane = workspace.adjacent_pane(window, cx);
17285 workspace.add_item(pane, item, None, true, true, window, cx);
17286 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17287 let (preview_item_id, preview_item_idx) =
17288 workspace.active_pane().read_with(cx, |pane, _| {
17289 (pane.preview_item_id(), pane.preview_item_idx())
17290 });
17291
17292 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17293
17294 if let Some(preview_item_id) = preview_item_id {
17295 workspace.active_pane().update(cx, |pane, cx| {
17296 pane.remove_item(preview_item_id, false, false, window, cx);
17297 });
17298 }
17299 } else {
17300 workspace.add_item_to_active_pane(item, None, true, window, cx);
17301 }
17302 workspace.active_pane().update(cx, |pane, cx| {
17303 pane.set_preview_item_id(Some(item_id), cx);
17304 });
17305 }
17306
17307 pub fn rename(
17308 &mut self,
17309 _: &Rename,
17310 window: &mut Window,
17311 cx: &mut Context<Self>,
17312 ) -> Option<Task<Result<()>>> {
17313 use language::ToOffset as _;
17314
17315 let provider = self.semantics_provider.clone()?;
17316 let selection = self.selections.newest_anchor().clone();
17317 let (cursor_buffer, cursor_buffer_position) = self
17318 .buffer
17319 .read(cx)
17320 .text_anchor_for_position(selection.head(), cx)?;
17321 let (tail_buffer, cursor_buffer_position_end) = self
17322 .buffer
17323 .read(cx)
17324 .text_anchor_for_position(selection.tail(), cx)?;
17325 if tail_buffer != cursor_buffer {
17326 return None;
17327 }
17328
17329 let snapshot = cursor_buffer.read(cx).snapshot();
17330 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17331 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17332 let prepare_rename = provider
17333 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17334 .unwrap_or_else(|| Task::ready(Ok(None)));
17335 drop(snapshot);
17336
17337 Some(cx.spawn_in(window, async move |this, cx| {
17338 let rename_range = if let Some(range) = prepare_rename.await? {
17339 Some(range)
17340 } else {
17341 this.update(cx, |this, cx| {
17342 let buffer = this.buffer.read(cx).snapshot(cx);
17343 let mut buffer_highlights = this
17344 .document_highlights_for_position(selection.head(), &buffer)
17345 .filter(|highlight| {
17346 highlight.start.excerpt_id == selection.head().excerpt_id
17347 && highlight.end.excerpt_id == selection.head().excerpt_id
17348 });
17349 buffer_highlights
17350 .next()
17351 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17352 })?
17353 };
17354 if let Some(rename_range) = rename_range {
17355 this.update_in(cx, |this, window, cx| {
17356 let snapshot = cursor_buffer.read(cx).snapshot();
17357 let rename_buffer_range = rename_range.to_offset(&snapshot);
17358 let cursor_offset_in_rename_range =
17359 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17360 let cursor_offset_in_rename_range_end =
17361 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17362
17363 this.take_rename(false, window, cx);
17364 let buffer = this.buffer.read(cx).read(cx);
17365 let cursor_offset = selection.head().to_offset(&buffer);
17366 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17367 let rename_end = rename_start + rename_buffer_range.len();
17368 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17369 let mut old_highlight_id = None;
17370 let old_name: Arc<str> = buffer
17371 .chunks(rename_start..rename_end, true)
17372 .map(|chunk| {
17373 if old_highlight_id.is_none() {
17374 old_highlight_id = chunk.syntax_highlight_id;
17375 }
17376 chunk.text
17377 })
17378 .collect::<String>()
17379 .into();
17380
17381 drop(buffer);
17382
17383 // Position the selection in the rename editor so that it matches the current selection.
17384 this.show_local_selections = false;
17385 let rename_editor = cx.new(|cx| {
17386 let mut editor = Editor::single_line(window, cx);
17387 editor.buffer.update(cx, |buffer, cx| {
17388 buffer.edit([(0..0, old_name.clone())], None, cx)
17389 });
17390 let rename_selection_range = match cursor_offset_in_rename_range
17391 .cmp(&cursor_offset_in_rename_range_end)
17392 {
17393 Ordering::Equal => {
17394 editor.select_all(&SelectAll, window, cx);
17395 return editor;
17396 }
17397 Ordering::Less => {
17398 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17399 }
17400 Ordering::Greater => {
17401 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17402 }
17403 };
17404 if rename_selection_range.end > old_name.len() {
17405 editor.select_all(&SelectAll, window, cx);
17406 } else {
17407 editor.change_selections(Default::default(), window, cx, |s| {
17408 s.select_ranges([rename_selection_range]);
17409 });
17410 }
17411 editor
17412 });
17413 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17414 if e == &EditorEvent::Focused {
17415 cx.emit(EditorEvent::FocusedIn)
17416 }
17417 })
17418 .detach();
17419
17420 let write_highlights =
17421 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17422 let read_highlights =
17423 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17424 let ranges = write_highlights
17425 .iter()
17426 .flat_map(|(_, ranges)| ranges.iter())
17427 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17428 .cloned()
17429 .collect();
17430
17431 this.highlight_text::<Rename>(
17432 ranges,
17433 HighlightStyle {
17434 fade_out: Some(0.6),
17435 ..Default::default()
17436 },
17437 cx,
17438 );
17439 let rename_focus_handle = rename_editor.focus_handle(cx);
17440 window.focus(&rename_focus_handle);
17441 let block_id = this.insert_blocks(
17442 [BlockProperties {
17443 style: BlockStyle::Flex,
17444 placement: BlockPlacement::Below(range.start),
17445 height: Some(1),
17446 render: Arc::new({
17447 let rename_editor = rename_editor.clone();
17448 move |cx: &mut BlockContext| {
17449 let mut text_style = cx.editor_style.text.clone();
17450 if let Some(highlight_style) = old_highlight_id
17451 .and_then(|h| h.style(&cx.editor_style.syntax))
17452 {
17453 text_style = text_style.highlight(highlight_style);
17454 }
17455 div()
17456 .block_mouse_except_scroll()
17457 .pl(cx.anchor_x)
17458 .child(EditorElement::new(
17459 &rename_editor,
17460 EditorStyle {
17461 background: cx.theme().system().transparent,
17462 local_player: cx.editor_style.local_player,
17463 text: text_style,
17464 scrollbar_width: cx.editor_style.scrollbar_width,
17465 syntax: cx.editor_style.syntax.clone(),
17466 status: cx.editor_style.status.clone(),
17467 inlay_hints_style: HighlightStyle {
17468 font_weight: Some(FontWeight::BOLD),
17469 ..make_inlay_hints_style(cx.app)
17470 },
17471 edit_prediction_styles: make_suggestion_styles(
17472 cx.app,
17473 ),
17474 ..EditorStyle::default()
17475 },
17476 ))
17477 .into_any_element()
17478 }
17479 }),
17480 priority: 0,
17481 }],
17482 Some(Autoscroll::fit()),
17483 cx,
17484 )[0];
17485 this.pending_rename = Some(RenameState {
17486 range,
17487 old_name,
17488 editor: rename_editor,
17489 block_id,
17490 });
17491 })?;
17492 }
17493
17494 Ok(())
17495 }))
17496 }
17497
17498 pub fn confirm_rename(
17499 &mut self,
17500 _: &ConfirmRename,
17501 window: &mut Window,
17502 cx: &mut Context<Self>,
17503 ) -> Option<Task<Result<()>>> {
17504 let rename = self.take_rename(false, window, cx)?;
17505 let workspace = self.workspace()?.downgrade();
17506 let (buffer, start) = self
17507 .buffer
17508 .read(cx)
17509 .text_anchor_for_position(rename.range.start, cx)?;
17510 let (end_buffer, _) = self
17511 .buffer
17512 .read(cx)
17513 .text_anchor_for_position(rename.range.end, cx)?;
17514 if buffer != end_buffer {
17515 return None;
17516 }
17517
17518 let old_name = rename.old_name;
17519 let new_name = rename.editor.read(cx).text(cx);
17520
17521 let rename = self.semantics_provider.as_ref()?.perform_rename(
17522 &buffer,
17523 start,
17524 new_name.clone(),
17525 cx,
17526 )?;
17527
17528 Some(cx.spawn_in(window, async move |editor, cx| {
17529 let project_transaction = rename.await?;
17530 Self::open_project_transaction(
17531 &editor,
17532 workspace,
17533 project_transaction,
17534 format!("Rename: {} → {}", old_name, new_name),
17535 cx,
17536 )
17537 .await?;
17538
17539 editor.update(cx, |editor, cx| {
17540 editor.refresh_document_highlights(cx);
17541 })?;
17542 Ok(())
17543 }))
17544 }
17545
17546 fn take_rename(
17547 &mut self,
17548 moving_cursor: bool,
17549 window: &mut Window,
17550 cx: &mut Context<Self>,
17551 ) -> Option<RenameState> {
17552 let rename = self.pending_rename.take()?;
17553 if rename.editor.focus_handle(cx).is_focused(window) {
17554 window.focus(&self.focus_handle);
17555 }
17556
17557 self.remove_blocks(
17558 [rename.block_id].into_iter().collect(),
17559 Some(Autoscroll::fit()),
17560 cx,
17561 );
17562 self.clear_highlights::<Rename>(cx);
17563 self.show_local_selections = true;
17564
17565 if moving_cursor {
17566 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17567 editor
17568 .selections
17569 .newest::<usize>(&editor.display_snapshot(cx))
17570 .head()
17571 });
17572
17573 // Update the selection to match the position of the selection inside
17574 // the rename editor.
17575 let snapshot = self.buffer.read(cx).read(cx);
17576 let rename_range = rename.range.to_offset(&snapshot);
17577 let cursor_in_editor = snapshot
17578 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17579 .min(rename_range.end);
17580 drop(snapshot);
17581
17582 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17583 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17584 });
17585 } else {
17586 self.refresh_document_highlights(cx);
17587 }
17588
17589 Some(rename)
17590 }
17591
17592 pub fn pending_rename(&self) -> Option<&RenameState> {
17593 self.pending_rename.as_ref()
17594 }
17595
17596 fn format(
17597 &mut self,
17598 _: &Format,
17599 window: &mut Window,
17600 cx: &mut Context<Self>,
17601 ) -> Option<Task<Result<()>>> {
17602 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17603
17604 let project = match &self.project {
17605 Some(project) => project.clone(),
17606 None => return None,
17607 };
17608
17609 Some(self.perform_format(
17610 project,
17611 FormatTrigger::Manual,
17612 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17613 window,
17614 cx,
17615 ))
17616 }
17617
17618 fn format_selections(
17619 &mut self,
17620 _: &FormatSelections,
17621 window: &mut Window,
17622 cx: &mut Context<Self>,
17623 ) -> Option<Task<Result<()>>> {
17624 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17625
17626 let project = match &self.project {
17627 Some(project) => project.clone(),
17628 None => return None,
17629 };
17630
17631 let ranges = self
17632 .selections
17633 .all_adjusted(&self.display_snapshot(cx))
17634 .into_iter()
17635 .map(|selection| selection.range())
17636 .collect_vec();
17637
17638 Some(self.perform_format(
17639 project,
17640 FormatTrigger::Manual,
17641 FormatTarget::Ranges(ranges),
17642 window,
17643 cx,
17644 ))
17645 }
17646
17647 fn perform_format(
17648 &mut self,
17649 project: Entity<Project>,
17650 trigger: FormatTrigger,
17651 target: FormatTarget,
17652 window: &mut Window,
17653 cx: &mut Context<Self>,
17654 ) -> Task<Result<()>> {
17655 let buffer = self.buffer.clone();
17656 let (buffers, target) = match target {
17657 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17658 FormatTarget::Ranges(selection_ranges) => {
17659 let multi_buffer = buffer.read(cx);
17660 let snapshot = multi_buffer.read(cx);
17661 let mut buffers = HashSet::default();
17662 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17663 BTreeMap::new();
17664 for selection_range in selection_ranges {
17665 for (buffer, buffer_range, _) in
17666 snapshot.range_to_buffer_ranges(selection_range)
17667 {
17668 let buffer_id = buffer.remote_id();
17669 let start = buffer.anchor_before(buffer_range.start);
17670 let end = buffer.anchor_after(buffer_range.end);
17671 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17672 buffer_id_to_ranges
17673 .entry(buffer_id)
17674 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17675 .or_insert_with(|| vec![start..end]);
17676 }
17677 }
17678 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17679 }
17680 };
17681
17682 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17683 let selections_prev = transaction_id_prev
17684 .and_then(|transaction_id_prev| {
17685 // default to selections as they were after the last edit, if we have them,
17686 // instead of how they are now.
17687 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17688 // will take you back to where you made the last edit, instead of staying where you scrolled
17689 self.selection_history
17690 .transaction(transaction_id_prev)
17691 .map(|t| t.0.clone())
17692 })
17693 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17694
17695 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17696 let format = project.update(cx, |project, cx| {
17697 project.format(buffers, target, true, trigger, cx)
17698 });
17699
17700 cx.spawn_in(window, async move |editor, cx| {
17701 let transaction = futures::select_biased! {
17702 transaction = format.log_err().fuse() => transaction,
17703 () = timeout => {
17704 log::warn!("timed out waiting for formatting");
17705 None
17706 }
17707 };
17708
17709 buffer
17710 .update(cx, |buffer, cx| {
17711 if let Some(transaction) = transaction
17712 && !buffer.is_singleton()
17713 {
17714 buffer.push_transaction(&transaction.0, cx);
17715 }
17716 cx.notify();
17717 })
17718 .ok();
17719
17720 if let Some(transaction_id_now) =
17721 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17722 {
17723 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17724 if has_new_transaction {
17725 _ = editor.update(cx, |editor, _| {
17726 editor
17727 .selection_history
17728 .insert_transaction(transaction_id_now, selections_prev);
17729 });
17730 }
17731 }
17732
17733 Ok(())
17734 })
17735 }
17736
17737 fn organize_imports(
17738 &mut self,
17739 _: &OrganizeImports,
17740 window: &mut Window,
17741 cx: &mut Context<Self>,
17742 ) -> Option<Task<Result<()>>> {
17743 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17744 let project = match &self.project {
17745 Some(project) => project.clone(),
17746 None => return None,
17747 };
17748 Some(self.perform_code_action_kind(
17749 project,
17750 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17751 window,
17752 cx,
17753 ))
17754 }
17755
17756 fn perform_code_action_kind(
17757 &mut self,
17758 project: Entity<Project>,
17759 kind: CodeActionKind,
17760 window: &mut Window,
17761 cx: &mut Context<Self>,
17762 ) -> Task<Result<()>> {
17763 let buffer = self.buffer.clone();
17764 let buffers = buffer.read(cx).all_buffers();
17765 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17766 let apply_action = project.update(cx, |project, cx| {
17767 project.apply_code_action_kind(buffers, kind, true, cx)
17768 });
17769 cx.spawn_in(window, async move |_, cx| {
17770 let transaction = futures::select_biased! {
17771 () = timeout => {
17772 log::warn!("timed out waiting for executing code action");
17773 None
17774 }
17775 transaction = apply_action.log_err().fuse() => transaction,
17776 };
17777 buffer
17778 .update(cx, |buffer, cx| {
17779 // check if we need this
17780 if let Some(transaction) = transaction
17781 && !buffer.is_singleton()
17782 {
17783 buffer.push_transaction(&transaction.0, cx);
17784 }
17785 cx.notify();
17786 })
17787 .ok();
17788 Ok(())
17789 })
17790 }
17791
17792 pub fn restart_language_server(
17793 &mut self,
17794 _: &RestartLanguageServer,
17795 _: &mut Window,
17796 cx: &mut Context<Self>,
17797 ) {
17798 if let Some(project) = self.project.clone() {
17799 self.buffer.update(cx, |multi_buffer, cx| {
17800 project.update(cx, |project, cx| {
17801 project.restart_language_servers_for_buffers(
17802 multi_buffer.all_buffers().into_iter().collect(),
17803 HashSet::default(),
17804 cx,
17805 );
17806 });
17807 })
17808 }
17809 }
17810
17811 pub fn stop_language_server(
17812 &mut self,
17813 _: &StopLanguageServer,
17814 _: &mut Window,
17815 cx: &mut Context<Self>,
17816 ) {
17817 if let Some(project) = self.project.clone() {
17818 self.buffer.update(cx, |multi_buffer, cx| {
17819 project.update(cx, |project, cx| {
17820 project.stop_language_servers_for_buffers(
17821 multi_buffer.all_buffers().into_iter().collect(),
17822 HashSet::default(),
17823 cx,
17824 );
17825 });
17826 });
17827 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17828 }
17829 }
17830
17831 fn cancel_language_server_work(
17832 workspace: &mut Workspace,
17833 _: &actions::CancelLanguageServerWork,
17834 _: &mut Window,
17835 cx: &mut Context<Workspace>,
17836 ) {
17837 let project = workspace.project();
17838 let buffers = workspace
17839 .active_item(cx)
17840 .and_then(|item| item.act_as::<Editor>(cx))
17841 .map_or(HashSet::default(), |editor| {
17842 editor.read(cx).buffer.read(cx).all_buffers()
17843 });
17844 project.update(cx, |project, cx| {
17845 project.cancel_language_server_work_for_buffers(buffers, cx);
17846 });
17847 }
17848
17849 fn show_character_palette(
17850 &mut self,
17851 _: &ShowCharacterPalette,
17852 window: &mut Window,
17853 _: &mut Context<Self>,
17854 ) {
17855 window.show_character_palette();
17856 }
17857
17858 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17859 if !self.diagnostics_enabled() {
17860 return;
17861 }
17862
17863 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17864 let buffer = self.buffer.read(cx).snapshot(cx);
17865 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17866 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17867 let is_valid = buffer
17868 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17869 .any(|entry| {
17870 entry.diagnostic.is_primary
17871 && !entry.range.is_empty()
17872 && entry.range.start == primary_range_start
17873 && entry.diagnostic.message == active_diagnostics.active_message
17874 });
17875
17876 if !is_valid {
17877 self.dismiss_diagnostics(cx);
17878 }
17879 }
17880 }
17881
17882 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17883 match &self.active_diagnostics {
17884 ActiveDiagnostic::Group(group) => Some(group),
17885 _ => None,
17886 }
17887 }
17888
17889 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17890 if !self.diagnostics_enabled() {
17891 return;
17892 }
17893 self.dismiss_diagnostics(cx);
17894 self.active_diagnostics = ActiveDiagnostic::All;
17895 }
17896
17897 fn activate_diagnostics(
17898 &mut self,
17899 buffer_id: BufferId,
17900 diagnostic: DiagnosticEntryRef<'_, usize>,
17901 window: &mut Window,
17902 cx: &mut Context<Self>,
17903 ) {
17904 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17905 return;
17906 }
17907 self.dismiss_diagnostics(cx);
17908 let snapshot = self.snapshot(window, cx);
17909 let buffer = self.buffer.read(cx).snapshot(cx);
17910 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17911 return;
17912 };
17913
17914 let diagnostic_group = buffer
17915 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17916 .collect::<Vec<_>>();
17917
17918 let blocks =
17919 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17920
17921 let blocks = self.display_map.update(cx, |display_map, cx| {
17922 display_map.insert_blocks(blocks, cx).into_iter().collect()
17923 });
17924 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17925 active_range: buffer.anchor_before(diagnostic.range.start)
17926 ..buffer.anchor_after(diagnostic.range.end),
17927 active_message: diagnostic.diagnostic.message.clone(),
17928 group_id: diagnostic.diagnostic.group_id,
17929 blocks,
17930 });
17931 cx.notify();
17932 }
17933
17934 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17935 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17936 return;
17937 };
17938
17939 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17940 if let ActiveDiagnostic::Group(group) = prev {
17941 self.display_map.update(cx, |display_map, cx| {
17942 display_map.remove_blocks(group.blocks, cx);
17943 });
17944 cx.notify();
17945 }
17946 }
17947
17948 /// Disable inline diagnostics rendering for this editor.
17949 pub fn disable_inline_diagnostics(&mut self) {
17950 self.inline_diagnostics_enabled = false;
17951 self.inline_diagnostics_update = Task::ready(());
17952 self.inline_diagnostics.clear();
17953 }
17954
17955 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17956 self.diagnostics_enabled = false;
17957 self.dismiss_diagnostics(cx);
17958 self.inline_diagnostics_update = Task::ready(());
17959 self.inline_diagnostics.clear();
17960 }
17961
17962 pub fn disable_word_completions(&mut self) {
17963 self.word_completions_enabled = false;
17964 }
17965
17966 pub fn diagnostics_enabled(&self) -> bool {
17967 self.diagnostics_enabled && self.mode.is_full()
17968 }
17969
17970 pub fn inline_diagnostics_enabled(&self) -> bool {
17971 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17972 }
17973
17974 pub fn show_inline_diagnostics(&self) -> bool {
17975 self.show_inline_diagnostics
17976 }
17977
17978 pub fn toggle_inline_diagnostics(
17979 &mut self,
17980 _: &ToggleInlineDiagnostics,
17981 window: &mut Window,
17982 cx: &mut Context<Editor>,
17983 ) {
17984 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17985 self.refresh_inline_diagnostics(false, window, cx);
17986 }
17987
17988 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17989 self.diagnostics_max_severity = severity;
17990 self.display_map.update(cx, |display_map, _| {
17991 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17992 });
17993 }
17994
17995 pub fn toggle_diagnostics(
17996 &mut self,
17997 _: &ToggleDiagnostics,
17998 window: &mut Window,
17999 cx: &mut Context<Editor>,
18000 ) {
18001 if !self.diagnostics_enabled() {
18002 return;
18003 }
18004
18005 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18006 EditorSettings::get_global(cx)
18007 .diagnostics_max_severity
18008 .filter(|severity| severity != &DiagnosticSeverity::Off)
18009 .unwrap_or(DiagnosticSeverity::Hint)
18010 } else {
18011 DiagnosticSeverity::Off
18012 };
18013 self.set_max_diagnostics_severity(new_severity, cx);
18014 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18015 self.active_diagnostics = ActiveDiagnostic::None;
18016 self.inline_diagnostics_update = Task::ready(());
18017 self.inline_diagnostics.clear();
18018 } else {
18019 self.refresh_inline_diagnostics(false, window, cx);
18020 }
18021
18022 cx.notify();
18023 }
18024
18025 pub fn toggle_minimap(
18026 &mut self,
18027 _: &ToggleMinimap,
18028 window: &mut Window,
18029 cx: &mut Context<Editor>,
18030 ) {
18031 if self.supports_minimap(cx) {
18032 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18033 }
18034 }
18035
18036 fn refresh_inline_diagnostics(
18037 &mut self,
18038 debounce: bool,
18039 window: &mut Window,
18040 cx: &mut Context<Self>,
18041 ) {
18042 let max_severity = ProjectSettings::get_global(cx)
18043 .diagnostics
18044 .inline
18045 .max_severity
18046 .unwrap_or(self.diagnostics_max_severity);
18047
18048 if !self.inline_diagnostics_enabled()
18049 || !self.diagnostics_enabled()
18050 || !self.show_inline_diagnostics
18051 || max_severity == DiagnosticSeverity::Off
18052 {
18053 self.inline_diagnostics_update = Task::ready(());
18054 self.inline_diagnostics.clear();
18055 return;
18056 }
18057
18058 let debounce_ms = ProjectSettings::get_global(cx)
18059 .diagnostics
18060 .inline
18061 .update_debounce_ms;
18062 let debounce = if debounce && debounce_ms > 0 {
18063 Some(Duration::from_millis(debounce_ms))
18064 } else {
18065 None
18066 };
18067 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18068 if let Some(debounce) = debounce {
18069 cx.background_executor().timer(debounce).await;
18070 }
18071 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18072 editor
18073 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18074 .ok()
18075 }) else {
18076 return;
18077 };
18078
18079 let new_inline_diagnostics = cx
18080 .background_spawn(async move {
18081 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18082 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
18083 let message = diagnostic_entry
18084 .diagnostic
18085 .message
18086 .split_once('\n')
18087 .map(|(line, _)| line)
18088 .map(SharedString::new)
18089 .unwrap_or_else(|| {
18090 SharedString::new(&*diagnostic_entry.diagnostic.message)
18091 });
18092 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18093 let (Ok(i) | Err(i)) = inline_diagnostics
18094 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18095 inline_diagnostics.insert(
18096 i,
18097 (
18098 start_anchor,
18099 InlineDiagnostic {
18100 message,
18101 group_id: diagnostic_entry.diagnostic.group_id,
18102 start: diagnostic_entry.range.start.to_point(&snapshot),
18103 is_primary: diagnostic_entry.diagnostic.is_primary,
18104 severity: diagnostic_entry.diagnostic.severity,
18105 },
18106 ),
18107 );
18108 }
18109 inline_diagnostics
18110 })
18111 .await;
18112
18113 editor
18114 .update(cx, |editor, cx| {
18115 editor.inline_diagnostics = new_inline_diagnostics;
18116 cx.notify();
18117 })
18118 .ok();
18119 });
18120 }
18121
18122 fn pull_diagnostics(
18123 &mut self,
18124 buffer_id: Option<BufferId>,
18125 window: &Window,
18126 cx: &mut Context<Self>,
18127 ) -> Option<()> {
18128 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18129 return None;
18130 }
18131 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18132 .diagnostics
18133 .lsp_pull_diagnostics;
18134 if !pull_diagnostics_settings.enabled {
18135 return None;
18136 }
18137 let project = self.project()?.downgrade();
18138 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18139 let mut buffers = self.buffer.read(cx).all_buffers();
18140 buffers.retain(|buffer| {
18141 let buffer_id_to_retain = buffer.read(cx).remote_id();
18142 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18143 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18144 });
18145 if buffers.is_empty() {
18146 self.pull_diagnostics_task = Task::ready(());
18147 return None;
18148 }
18149
18150 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18151 cx.background_executor().timer(debounce).await;
18152
18153 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18154 buffers
18155 .into_iter()
18156 .filter_map(|buffer| {
18157 project
18158 .update(cx, |project, cx| {
18159 project.lsp_store().update(cx, |lsp_store, cx| {
18160 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18161 })
18162 })
18163 .ok()
18164 })
18165 .collect::<FuturesUnordered<_>>()
18166 }) else {
18167 return;
18168 };
18169
18170 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18171 match pull_task {
18172 Ok(()) => {
18173 if editor
18174 .update_in(cx, |editor, window, cx| {
18175 editor.update_diagnostics_state(window, cx);
18176 })
18177 .is_err()
18178 {
18179 return;
18180 }
18181 }
18182 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18183 }
18184 }
18185 });
18186
18187 Some(())
18188 }
18189
18190 pub fn set_selections_from_remote(
18191 &mut self,
18192 selections: Vec<Selection<Anchor>>,
18193 pending_selection: Option<Selection<Anchor>>,
18194 window: &mut Window,
18195 cx: &mut Context<Self>,
18196 ) {
18197 let old_cursor_position = self.selections.newest_anchor().head();
18198 self.selections
18199 .change_with(&self.display_snapshot(cx), |s| {
18200 s.select_anchors(selections);
18201 if let Some(pending_selection) = pending_selection {
18202 s.set_pending(pending_selection, SelectMode::Character);
18203 } else {
18204 s.clear_pending();
18205 }
18206 });
18207 self.selections_did_change(
18208 false,
18209 &old_cursor_position,
18210 SelectionEffects::default(),
18211 window,
18212 cx,
18213 );
18214 }
18215
18216 pub fn transact(
18217 &mut self,
18218 window: &mut Window,
18219 cx: &mut Context<Self>,
18220 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18221 ) -> Option<TransactionId> {
18222 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18223 this.start_transaction_at(Instant::now(), window, cx);
18224 update(this, window, cx);
18225 this.end_transaction_at(Instant::now(), cx)
18226 })
18227 }
18228
18229 pub fn start_transaction_at(
18230 &mut self,
18231 now: Instant,
18232 window: &mut Window,
18233 cx: &mut Context<Self>,
18234 ) -> Option<TransactionId> {
18235 self.end_selection(window, cx);
18236 if let Some(tx_id) = self
18237 .buffer
18238 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18239 {
18240 self.selection_history
18241 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18242 cx.emit(EditorEvent::TransactionBegun {
18243 transaction_id: tx_id,
18244 });
18245 Some(tx_id)
18246 } else {
18247 None
18248 }
18249 }
18250
18251 pub fn end_transaction_at(
18252 &mut self,
18253 now: Instant,
18254 cx: &mut Context<Self>,
18255 ) -> Option<TransactionId> {
18256 if let Some(transaction_id) = self
18257 .buffer
18258 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18259 {
18260 if let Some((_, end_selections)) =
18261 self.selection_history.transaction_mut(transaction_id)
18262 {
18263 *end_selections = Some(self.selections.disjoint_anchors_arc());
18264 } else {
18265 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18266 }
18267
18268 cx.emit(EditorEvent::Edited { transaction_id });
18269 Some(transaction_id)
18270 } else {
18271 None
18272 }
18273 }
18274
18275 pub fn modify_transaction_selection_history(
18276 &mut self,
18277 transaction_id: TransactionId,
18278 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18279 ) -> bool {
18280 self.selection_history
18281 .transaction_mut(transaction_id)
18282 .map(modify)
18283 .is_some()
18284 }
18285
18286 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18287 if self.selection_mark_mode {
18288 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18289 s.move_with(|_, sel| {
18290 sel.collapse_to(sel.head(), SelectionGoal::None);
18291 });
18292 })
18293 }
18294 self.selection_mark_mode = true;
18295 cx.notify();
18296 }
18297
18298 pub fn swap_selection_ends(
18299 &mut self,
18300 _: &actions::SwapSelectionEnds,
18301 window: &mut Window,
18302 cx: &mut Context<Self>,
18303 ) {
18304 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18305 s.move_with(|_, sel| {
18306 if sel.start != sel.end {
18307 sel.reversed = !sel.reversed
18308 }
18309 });
18310 });
18311 self.request_autoscroll(Autoscroll::newest(), cx);
18312 cx.notify();
18313 }
18314
18315 pub fn toggle_focus(
18316 workspace: &mut Workspace,
18317 _: &actions::ToggleFocus,
18318 window: &mut Window,
18319 cx: &mut Context<Workspace>,
18320 ) {
18321 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18322 return;
18323 };
18324 workspace.activate_item(&item, true, true, window, cx);
18325 }
18326
18327 pub fn toggle_fold(
18328 &mut self,
18329 _: &actions::ToggleFold,
18330 window: &mut Window,
18331 cx: &mut Context<Self>,
18332 ) {
18333 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18334 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18335 let selection = self.selections.newest::<Point>(&display_map);
18336
18337 let range = if selection.is_empty() {
18338 let point = selection.head().to_display_point(&display_map);
18339 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18340 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18341 .to_point(&display_map);
18342 start..end
18343 } else {
18344 selection.range()
18345 };
18346 if display_map.folds_in_range(range).next().is_some() {
18347 self.unfold_lines(&Default::default(), window, cx)
18348 } else {
18349 self.fold(&Default::default(), window, cx)
18350 }
18351 } else {
18352 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18353 let buffer_ids: HashSet<_> = self
18354 .selections
18355 .disjoint_anchor_ranges()
18356 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18357 .collect();
18358
18359 let should_unfold = buffer_ids
18360 .iter()
18361 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18362
18363 for buffer_id in buffer_ids {
18364 if should_unfold {
18365 self.unfold_buffer(buffer_id, cx);
18366 } else {
18367 self.fold_buffer(buffer_id, cx);
18368 }
18369 }
18370 }
18371 }
18372
18373 pub fn toggle_fold_recursive(
18374 &mut self,
18375 _: &actions::ToggleFoldRecursive,
18376 window: &mut Window,
18377 cx: &mut Context<Self>,
18378 ) {
18379 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18380
18381 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18382 let range = if selection.is_empty() {
18383 let point = selection.head().to_display_point(&display_map);
18384 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18385 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18386 .to_point(&display_map);
18387 start..end
18388 } else {
18389 selection.range()
18390 };
18391 if display_map.folds_in_range(range).next().is_some() {
18392 self.unfold_recursive(&Default::default(), window, cx)
18393 } else {
18394 self.fold_recursive(&Default::default(), window, cx)
18395 }
18396 }
18397
18398 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18399 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18400 let mut to_fold = Vec::new();
18401 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18402 let selections = self.selections.all_adjusted(&display_map);
18403
18404 for selection in selections {
18405 let range = selection.range().sorted();
18406 let buffer_start_row = range.start.row;
18407
18408 if range.start.row != range.end.row {
18409 let mut found = false;
18410 let mut row = range.start.row;
18411 while row <= range.end.row {
18412 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18413 {
18414 found = true;
18415 row = crease.range().end.row + 1;
18416 to_fold.push(crease);
18417 } else {
18418 row += 1
18419 }
18420 }
18421 if found {
18422 continue;
18423 }
18424 }
18425
18426 for row in (0..=range.start.row).rev() {
18427 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18428 && crease.range().end.row >= buffer_start_row
18429 {
18430 to_fold.push(crease);
18431 if row <= range.start.row {
18432 break;
18433 }
18434 }
18435 }
18436 }
18437
18438 self.fold_creases(to_fold, true, window, cx);
18439 } else {
18440 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18441 let buffer_ids = self
18442 .selections
18443 .disjoint_anchor_ranges()
18444 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18445 .collect::<HashSet<_>>();
18446 for buffer_id in buffer_ids {
18447 self.fold_buffer(buffer_id, cx);
18448 }
18449 }
18450 }
18451
18452 pub fn toggle_fold_all(
18453 &mut self,
18454 _: &actions::ToggleFoldAll,
18455 window: &mut Window,
18456 cx: &mut Context<Self>,
18457 ) {
18458 if self.buffer.read(cx).is_singleton() {
18459 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18460 let has_folds = display_map
18461 .folds_in_range(0..display_map.buffer_snapshot().len())
18462 .next()
18463 .is_some();
18464
18465 if has_folds {
18466 self.unfold_all(&actions::UnfoldAll, window, cx);
18467 } else {
18468 self.fold_all(&actions::FoldAll, window, cx);
18469 }
18470 } else {
18471 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18472 let should_unfold = buffer_ids
18473 .iter()
18474 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18475
18476 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18477 editor
18478 .update_in(cx, |editor, _, cx| {
18479 for buffer_id in buffer_ids {
18480 if should_unfold {
18481 editor.unfold_buffer(buffer_id, cx);
18482 } else {
18483 editor.fold_buffer(buffer_id, cx);
18484 }
18485 }
18486 })
18487 .ok();
18488 });
18489 }
18490 }
18491
18492 fn fold_at_level(
18493 &mut self,
18494 fold_at: &FoldAtLevel,
18495 window: &mut Window,
18496 cx: &mut Context<Self>,
18497 ) {
18498 if !self.buffer.read(cx).is_singleton() {
18499 return;
18500 }
18501
18502 let fold_at_level = fold_at.0;
18503 let snapshot = self.buffer.read(cx).snapshot(cx);
18504 let mut to_fold = Vec::new();
18505 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18506
18507 let row_ranges_to_keep: Vec<Range<u32>> = self
18508 .selections
18509 .all::<Point>(&self.display_snapshot(cx))
18510 .into_iter()
18511 .map(|sel| sel.start.row..sel.end.row)
18512 .collect();
18513
18514 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18515 while start_row < end_row {
18516 match self
18517 .snapshot(window, cx)
18518 .crease_for_buffer_row(MultiBufferRow(start_row))
18519 {
18520 Some(crease) => {
18521 let nested_start_row = crease.range().start.row + 1;
18522 let nested_end_row = crease.range().end.row;
18523
18524 if current_level < fold_at_level {
18525 stack.push((nested_start_row, nested_end_row, current_level + 1));
18526 } else if current_level == fold_at_level {
18527 // Fold iff there is no selection completely contained within the fold region
18528 if !row_ranges_to_keep.iter().any(|selection| {
18529 selection.end >= nested_start_row
18530 && selection.start <= nested_end_row
18531 }) {
18532 to_fold.push(crease);
18533 }
18534 }
18535
18536 start_row = nested_end_row + 1;
18537 }
18538 None => start_row += 1,
18539 }
18540 }
18541 }
18542
18543 self.fold_creases(to_fold, true, window, cx);
18544 }
18545
18546 pub fn fold_at_level_1(
18547 &mut self,
18548 _: &actions::FoldAtLevel1,
18549 window: &mut Window,
18550 cx: &mut Context<Self>,
18551 ) {
18552 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18553 }
18554
18555 pub fn fold_at_level_2(
18556 &mut self,
18557 _: &actions::FoldAtLevel2,
18558 window: &mut Window,
18559 cx: &mut Context<Self>,
18560 ) {
18561 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18562 }
18563
18564 pub fn fold_at_level_3(
18565 &mut self,
18566 _: &actions::FoldAtLevel3,
18567 window: &mut Window,
18568 cx: &mut Context<Self>,
18569 ) {
18570 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18571 }
18572
18573 pub fn fold_at_level_4(
18574 &mut self,
18575 _: &actions::FoldAtLevel4,
18576 window: &mut Window,
18577 cx: &mut Context<Self>,
18578 ) {
18579 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18580 }
18581
18582 pub fn fold_at_level_5(
18583 &mut self,
18584 _: &actions::FoldAtLevel5,
18585 window: &mut Window,
18586 cx: &mut Context<Self>,
18587 ) {
18588 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18589 }
18590
18591 pub fn fold_at_level_6(
18592 &mut self,
18593 _: &actions::FoldAtLevel6,
18594 window: &mut Window,
18595 cx: &mut Context<Self>,
18596 ) {
18597 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18598 }
18599
18600 pub fn fold_at_level_7(
18601 &mut self,
18602 _: &actions::FoldAtLevel7,
18603 window: &mut Window,
18604 cx: &mut Context<Self>,
18605 ) {
18606 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18607 }
18608
18609 pub fn fold_at_level_8(
18610 &mut self,
18611 _: &actions::FoldAtLevel8,
18612 window: &mut Window,
18613 cx: &mut Context<Self>,
18614 ) {
18615 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18616 }
18617
18618 pub fn fold_at_level_9(
18619 &mut self,
18620 _: &actions::FoldAtLevel9,
18621 window: &mut Window,
18622 cx: &mut Context<Self>,
18623 ) {
18624 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18625 }
18626
18627 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18628 if self.buffer.read(cx).is_singleton() {
18629 let mut fold_ranges = Vec::new();
18630 let snapshot = self.buffer.read(cx).snapshot(cx);
18631
18632 for row in 0..snapshot.max_row().0 {
18633 if let Some(foldable_range) = self
18634 .snapshot(window, cx)
18635 .crease_for_buffer_row(MultiBufferRow(row))
18636 {
18637 fold_ranges.push(foldable_range);
18638 }
18639 }
18640
18641 self.fold_creases(fold_ranges, true, window, cx);
18642 } else {
18643 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18644 editor
18645 .update_in(cx, |editor, _, cx| {
18646 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18647 editor.fold_buffer(buffer_id, cx);
18648 }
18649 })
18650 .ok();
18651 });
18652 }
18653 }
18654
18655 pub fn fold_function_bodies(
18656 &mut self,
18657 _: &actions::FoldFunctionBodies,
18658 window: &mut Window,
18659 cx: &mut Context<Self>,
18660 ) {
18661 let snapshot = self.buffer.read(cx).snapshot(cx);
18662
18663 let ranges = snapshot
18664 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18665 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18666 .collect::<Vec<_>>();
18667
18668 let creases = ranges
18669 .into_iter()
18670 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18671 .collect();
18672
18673 self.fold_creases(creases, true, window, cx);
18674 }
18675
18676 pub fn fold_recursive(
18677 &mut self,
18678 _: &actions::FoldRecursive,
18679 window: &mut Window,
18680 cx: &mut Context<Self>,
18681 ) {
18682 let mut to_fold = Vec::new();
18683 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18684 let selections = self.selections.all_adjusted(&display_map);
18685
18686 for selection in selections {
18687 let range = selection.range().sorted();
18688 let buffer_start_row = range.start.row;
18689
18690 if range.start.row != range.end.row {
18691 let mut found = false;
18692 for row in range.start.row..=range.end.row {
18693 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18694 found = true;
18695 to_fold.push(crease);
18696 }
18697 }
18698 if found {
18699 continue;
18700 }
18701 }
18702
18703 for row in (0..=range.start.row).rev() {
18704 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18705 if crease.range().end.row >= buffer_start_row {
18706 to_fold.push(crease);
18707 } else {
18708 break;
18709 }
18710 }
18711 }
18712 }
18713
18714 self.fold_creases(to_fold, true, window, cx);
18715 }
18716
18717 pub fn fold_at(
18718 &mut self,
18719 buffer_row: MultiBufferRow,
18720 window: &mut Window,
18721 cx: &mut Context<Self>,
18722 ) {
18723 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18724
18725 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18726 let autoscroll = self
18727 .selections
18728 .all::<Point>(&display_map)
18729 .iter()
18730 .any(|selection| crease.range().overlaps(&selection.range()));
18731
18732 self.fold_creases(vec![crease], autoscroll, window, cx);
18733 }
18734 }
18735
18736 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18737 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18738 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18739 let buffer = display_map.buffer_snapshot();
18740 let selections = self.selections.all::<Point>(&display_map);
18741 let ranges = selections
18742 .iter()
18743 .map(|s| {
18744 let range = s.display_range(&display_map).sorted();
18745 let mut start = range.start.to_point(&display_map);
18746 let mut end = range.end.to_point(&display_map);
18747 start.column = 0;
18748 end.column = buffer.line_len(MultiBufferRow(end.row));
18749 start..end
18750 })
18751 .collect::<Vec<_>>();
18752
18753 self.unfold_ranges(&ranges, true, true, cx);
18754 } else {
18755 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18756 let buffer_ids = self
18757 .selections
18758 .disjoint_anchor_ranges()
18759 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18760 .collect::<HashSet<_>>();
18761 for buffer_id in buffer_ids {
18762 self.unfold_buffer(buffer_id, cx);
18763 }
18764 }
18765 }
18766
18767 pub fn unfold_recursive(
18768 &mut self,
18769 _: &UnfoldRecursive,
18770 _window: &mut Window,
18771 cx: &mut Context<Self>,
18772 ) {
18773 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18774 let selections = self.selections.all::<Point>(&display_map);
18775 let ranges = selections
18776 .iter()
18777 .map(|s| {
18778 let mut range = s.display_range(&display_map).sorted();
18779 *range.start.column_mut() = 0;
18780 *range.end.column_mut() = display_map.line_len(range.end.row());
18781 let start = range.start.to_point(&display_map);
18782 let end = range.end.to_point(&display_map);
18783 start..end
18784 })
18785 .collect::<Vec<_>>();
18786
18787 self.unfold_ranges(&ranges, true, true, cx);
18788 }
18789
18790 pub fn unfold_at(
18791 &mut self,
18792 buffer_row: MultiBufferRow,
18793 _window: &mut Window,
18794 cx: &mut Context<Self>,
18795 ) {
18796 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18797
18798 let intersection_range = Point::new(buffer_row.0, 0)
18799 ..Point::new(
18800 buffer_row.0,
18801 display_map.buffer_snapshot().line_len(buffer_row),
18802 );
18803
18804 let autoscroll = self
18805 .selections
18806 .all::<Point>(&display_map)
18807 .iter()
18808 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18809
18810 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18811 }
18812
18813 pub fn unfold_all(
18814 &mut self,
18815 _: &actions::UnfoldAll,
18816 _window: &mut Window,
18817 cx: &mut Context<Self>,
18818 ) {
18819 if self.buffer.read(cx).is_singleton() {
18820 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18821 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18822 } else {
18823 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18824 editor
18825 .update(cx, |editor, cx| {
18826 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18827 editor.unfold_buffer(buffer_id, cx);
18828 }
18829 })
18830 .ok();
18831 });
18832 }
18833 }
18834
18835 pub fn fold_selected_ranges(
18836 &mut self,
18837 _: &FoldSelectedRanges,
18838 window: &mut Window,
18839 cx: &mut Context<Self>,
18840 ) {
18841 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18842 let selections = self.selections.all_adjusted(&display_map);
18843 let ranges = selections
18844 .into_iter()
18845 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18846 .collect::<Vec<_>>();
18847 self.fold_creases(ranges, true, window, cx);
18848 }
18849
18850 pub fn fold_ranges<T: ToOffset + Clone>(
18851 &mut self,
18852 ranges: Vec<Range<T>>,
18853 auto_scroll: bool,
18854 window: &mut Window,
18855 cx: &mut Context<Self>,
18856 ) {
18857 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18858 let ranges = ranges
18859 .into_iter()
18860 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18861 .collect::<Vec<_>>();
18862 self.fold_creases(ranges, auto_scroll, window, cx);
18863 }
18864
18865 pub fn fold_creases<T: ToOffset + Clone>(
18866 &mut self,
18867 creases: Vec<Crease<T>>,
18868 auto_scroll: bool,
18869 _window: &mut Window,
18870 cx: &mut Context<Self>,
18871 ) {
18872 if creases.is_empty() {
18873 return;
18874 }
18875
18876 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18877
18878 if auto_scroll {
18879 self.request_autoscroll(Autoscroll::fit(), cx);
18880 }
18881
18882 cx.notify();
18883
18884 self.scrollbar_marker_state.dirty = true;
18885 self.folds_did_change(cx);
18886 }
18887
18888 /// Removes any folds whose ranges intersect any of the given ranges.
18889 pub fn unfold_ranges<T: ToOffset + Clone>(
18890 &mut self,
18891 ranges: &[Range<T>],
18892 inclusive: bool,
18893 auto_scroll: bool,
18894 cx: &mut Context<Self>,
18895 ) {
18896 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18897 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18898 });
18899 self.folds_did_change(cx);
18900 }
18901
18902 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18903 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18904 return;
18905 }
18906
18907 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18908 self.display_map.update(cx, |display_map, cx| {
18909 display_map.fold_buffers([buffer_id], cx)
18910 });
18911
18912 let snapshot = self.display_snapshot(cx);
18913 self.selections.change_with(&snapshot, |selections| {
18914 selections.remove_selections_from_buffer(buffer_id);
18915 });
18916
18917 cx.emit(EditorEvent::BufferFoldToggled {
18918 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18919 folded: true,
18920 });
18921 cx.notify();
18922 }
18923
18924 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18925 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18926 return;
18927 }
18928 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18929 self.display_map.update(cx, |display_map, cx| {
18930 display_map.unfold_buffers([buffer_id], cx);
18931 });
18932 cx.emit(EditorEvent::BufferFoldToggled {
18933 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18934 folded: false,
18935 });
18936 cx.notify();
18937 }
18938
18939 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18940 self.display_map.read(cx).is_buffer_folded(buffer)
18941 }
18942
18943 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18944 self.display_map.read(cx).folded_buffers()
18945 }
18946
18947 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18948 self.display_map.update(cx, |display_map, cx| {
18949 display_map.disable_header_for_buffer(buffer_id, cx);
18950 });
18951 cx.notify();
18952 }
18953
18954 /// Removes any folds with the given ranges.
18955 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18956 &mut self,
18957 ranges: &[Range<T>],
18958 type_id: TypeId,
18959 auto_scroll: bool,
18960 cx: &mut Context<Self>,
18961 ) {
18962 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18963 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18964 });
18965 self.folds_did_change(cx);
18966 }
18967
18968 fn remove_folds_with<T: ToOffset + Clone>(
18969 &mut self,
18970 ranges: &[Range<T>],
18971 auto_scroll: bool,
18972 cx: &mut Context<Self>,
18973 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18974 ) {
18975 if ranges.is_empty() {
18976 return;
18977 }
18978
18979 let mut buffers_affected = HashSet::default();
18980 let multi_buffer = self.buffer().read(cx);
18981 for range in ranges {
18982 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18983 buffers_affected.insert(buffer.read(cx).remote_id());
18984 };
18985 }
18986
18987 self.display_map.update(cx, update);
18988
18989 if auto_scroll {
18990 self.request_autoscroll(Autoscroll::fit(), cx);
18991 }
18992
18993 cx.notify();
18994 self.scrollbar_marker_state.dirty = true;
18995 self.active_indent_guides_state.dirty = true;
18996 }
18997
18998 pub fn update_renderer_widths(
18999 &mut self,
19000 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19001 cx: &mut Context<Self>,
19002 ) -> bool {
19003 self.display_map
19004 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19005 }
19006
19007 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19008 self.display_map.read(cx).fold_placeholder.clone()
19009 }
19010
19011 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19012 self.buffer.update(cx, |buffer, cx| {
19013 buffer.set_all_diff_hunks_expanded(cx);
19014 });
19015 }
19016
19017 pub fn expand_all_diff_hunks(
19018 &mut self,
19019 _: &ExpandAllDiffHunks,
19020 _window: &mut Window,
19021 cx: &mut Context<Self>,
19022 ) {
19023 self.buffer.update(cx, |buffer, cx| {
19024 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19025 });
19026 }
19027
19028 pub fn collapse_all_diff_hunks(
19029 &mut self,
19030 _: &CollapseAllDiffHunks,
19031 _window: &mut Window,
19032 cx: &mut Context<Self>,
19033 ) {
19034 self.buffer.update(cx, |buffer, cx| {
19035 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19036 });
19037 }
19038
19039 pub fn toggle_selected_diff_hunks(
19040 &mut self,
19041 _: &ToggleSelectedDiffHunks,
19042 _window: &mut Window,
19043 cx: &mut Context<Self>,
19044 ) {
19045 let ranges: Vec<_> = self
19046 .selections
19047 .disjoint_anchors()
19048 .iter()
19049 .map(|s| s.range())
19050 .collect();
19051 self.toggle_diff_hunks_in_ranges(ranges, cx);
19052 }
19053
19054 pub fn diff_hunks_in_ranges<'a>(
19055 &'a self,
19056 ranges: &'a [Range<Anchor>],
19057 buffer: &'a MultiBufferSnapshot,
19058 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19059 ranges.iter().flat_map(move |range| {
19060 let end_excerpt_id = range.end.excerpt_id;
19061 let range = range.to_point(buffer);
19062 let mut peek_end = range.end;
19063 if range.end.row < buffer.max_row().0 {
19064 peek_end = Point::new(range.end.row + 1, 0);
19065 }
19066 buffer
19067 .diff_hunks_in_range(range.start..peek_end)
19068 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19069 })
19070 }
19071
19072 pub fn has_stageable_diff_hunks_in_ranges(
19073 &self,
19074 ranges: &[Range<Anchor>],
19075 snapshot: &MultiBufferSnapshot,
19076 ) -> bool {
19077 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19078 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19079 }
19080
19081 pub fn toggle_staged_selected_diff_hunks(
19082 &mut self,
19083 _: &::git::ToggleStaged,
19084 _: &mut Window,
19085 cx: &mut Context<Self>,
19086 ) {
19087 let snapshot = self.buffer.read(cx).snapshot(cx);
19088 let ranges: Vec<_> = self
19089 .selections
19090 .disjoint_anchors()
19091 .iter()
19092 .map(|s| s.range())
19093 .collect();
19094 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19095 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19096 }
19097
19098 pub fn set_render_diff_hunk_controls(
19099 &mut self,
19100 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19101 cx: &mut Context<Self>,
19102 ) {
19103 self.render_diff_hunk_controls = render_diff_hunk_controls;
19104 cx.notify();
19105 }
19106
19107 pub fn stage_and_next(
19108 &mut self,
19109 _: &::git::StageAndNext,
19110 window: &mut Window,
19111 cx: &mut Context<Self>,
19112 ) {
19113 self.do_stage_or_unstage_and_next(true, window, cx);
19114 }
19115
19116 pub fn unstage_and_next(
19117 &mut self,
19118 _: &::git::UnstageAndNext,
19119 window: &mut Window,
19120 cx: &mut Context<Self>,
19121 ) {
19122 self.do_stage_or_unstage_and_next(false, window, cx);
19123 }
19124
19125 pub fn stage_or_unstage_diff_hunks(
19126 &mut self,
19127 stage: bool,
19128 ranges: Vec<Range<Anchor>>,
19129 cx: &mut Context<Self>,
19130 ) {
19131 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19132 cx.spawn(async move |this, cx| {
19133 task.await?;
19134 this.update(cx, |this, cx| {
19135 let snapshot = this.buffer.read(cx).snapshot(cx);
19136 let chunk_by = this
19137 .diff_hunks_in_ranges(&ranges, &snapshot)
19138 .chunk_by(|hunk| hunk.buffer_id);
19139 for (buffer_id, hunks) in &chunk_by {
19140 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19141 }
19142 })
19143 })
19144 .detach_and_log_err(cx);
19145 }
19146
19147 fn save_buffers_for_ranges_if_needed(
19148 &mut self,
19149 ranges: &[Range<Anchor>],
19150 cx: &mut Context<Editor>,
19151 ) -> Task<Result<()>> {
19152 let multibuffer = self.buffer.read(cx);
19153 let snapshot = multibuffer.read(cx);
19154 let buffer_ids: HashSet<_> = ranges
19155 .iter()
19156 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19157 .collect();
19158 drop(snapshot);
19159
19160 let mut buffers = HashSet::default();
19161 for buffer_id in buffer_ids {
19162 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19163 let buffer = buffer_entity.read(cx);
19164 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19165 {
19166 buffers.insert(buffer_entity);
19167 }
19168 }
19169 }
19170
19171 if let Some(project) = &self.project {
19172 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19173 } else {
19174 Task::ready(Ok(()))
19175 }
19176 }
19177
19178 fn do_stage_or_unstage_and_next(
19179 &mut self,
19180 stage: bool,
19181 window: &mut Window,
19182 cx: &mut Context<Self>,
19183 ) {
19184 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19185
19186 if ranges.iter().any(|range| range.start != range.end) {
19187 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19188 return;
19189 }
19190
19191 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19192 let snapshot = self.snapshot(window, cx);
19193 let position = self
19194 .selections
19195 .newest::<Point>(&snapshot.display_snapshot)
19196 .head();
19197 let mut row = snapshot
19198 .buffer_snapshot()
19199 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19200 .find(|hunk| hunk.row_range.start.0 > position.row)
19201 .map(|hunk| hunk.row_range.start);
19202
19203 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19204 // Outside of the project diff editor, wrap around to the beginning.
19205 if !all_diff_hunks_expanded {
19206 row = row.or_else(|| {
19207 snapshot
19208 .buffer_snapshot()
19209 .diff_hunks_in_range(Point::zero()..position)
19210 .find(|hunk| hunk.row_range.end.0 < position.row)
19211 .map(|hunk| hunk.row_range.start)
19212 });
19213 }
19214
19215 if let Some(row) = row {
19216 let destination = Point::new(row.0, 0);
19217 let autoscroll = Autoscroll::center();
19218
19219 self.unfold_ranges(&[destination..destination], false, false, cx);
19220 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19221 s.select_ranges([destination..destination]);
19222 });
19223 }
19224 }
19225
19226 fn do_stage_or_unstage(
19227 &self,
19228 stage: bool,
19229 buffer_id: BufferId,
19230 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19231 cx: &mut App,
19232 ) -> Option<()> {
19233 let project = self.project()?;
19234 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19235 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19236 let buffer_snapshot = buffer.read(cx).snapshot();
19237 let file_exists = buffer_snapshot
19238 .file()
19239 .is_some_and(|file| file.disk_state().exists());
19240 diff.update(cx, |diff, cx| {
19241 diff.stage_or_unstage_hunks(
19242 stage,
19243 &hunks
19244 .map(|hunk| buffer_diff::DiffHunk {
19245 buffer_range: hunk.buffer_range,
19246 diff_base_byte_range: hunk.diff_base_byte_range,
19247 secondary_status: hunk.secondary_status,
19248 range: Point::zero()..Point::zero(), // unused
19249 })
19250 .collect::<Vec<_>>(),
19251 &buffer_snapshot,
19252 file_exists,
19253 cx,
19254 )
19255 });
19256 None
19257 }
19258
19259 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19260 let ranges: Vec<_> = self
19261 .selections
19262 .disjoint_anchors()
19263 .iter()
19264 .map(|s| s.range())
19265 .collect();
19266 self.buffer
19267 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19268 }
19269
19270 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19271 self.buffer.update(cx, |buffer, cx| {
19272 let ranges = vec![Anchor::min()..Anchor::max()];
19273 if !buffer.all_diff_hunks_expanded()
19274 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19275 {
19276 buffer.collapse_diff_hunks(ranges, cx);
19277 true
19278 } else {
19279 false
19280 }
19281 })
19282 }
19283
19284 fn toggle_diff_hunks_in_ranges(
19285 &mut self,
19286 ranges: Vec<Range<Anchor>>,
19287 cx: &mut Context<Editor>,
19288 ) {
19289 self.buffer.update(cx, |buffer, cx| {
19290 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19291 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19292 })
19293 }
19294
19295 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19296 self.buffer.update(cx, |buffer, cx| {
19297 let snapshot = buffer.snapshot(cx);
19298 let excerpt_id = range.end.excerpt_id;
19299 let point_range = range.to_point(&snapshot);
19300 let expand = !buffer.single_hunk_is_expanded(range, cx);
19301 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19302 })
19303 }
19304
19305 pub(crate) fn apply_all_diff_hunks(
19306 &mut self,
19307 _: &ApplyAllDiffHunks,
19308 window: &mut Window,
19309 cx: &mut Context<Self>,
19310 ) {
19311 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19312
19313 let buffers = self.buffer.read(cx).all_buffers();
19314 for branch_buffer in buffers {
19315 branch_buffer.update(cx, |branch_buffer, cx| {
19316 branch_buffer.merge_into_base(Vec::new(), cx);
19317 });
19318 }
19319
19320 if let Some(project) = self.project.clone() {
19321 self.save(
19322 SaveOptions {
19323 format: true,
19324 autosave: false,
19325 },
19326 project,
19327 window,
19328 cx,
19329 )
19330 .detach_and_log_err(cx);
19331 }
19332 }
19333
19334 pub(crate) fn apply_selected_diff_hunks(
19335 &mut self,
19336 _: &ApplyDiffHunk,
19337 window: &mut Window,
19338 cx: &mut Context<Self>,
19339 ) {
19340 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19341 let snapshot = self.snapshot(window, cx);
19342 let hunks = snapshot.hunks_for_ranges(
19343 self.selections
19344 .all(&snapshot.display_snapshot)
19345 .into_iter()
19346 .map(|selection| selection.range()),
19347 );
19348 let mut ranges_by_buffer = HashMap::default();
19349 self.transact(window, cx, |editor, _window, cx| {
19350 for hunk in hunks {
19351 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19352 ranges_by_buffer
19353 .entry(buffer.clone())
19354 .or_insert_with(Vec::new)
19355 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19356 }
19357 }
19358
19359 for (buffer, ranges) in ranges_by_buffer {
19360 buffer.update(cx, |buffer, cx| {
19361 buffer.merge_into_base(ranges, cx);
19362 });
19363 }
19364 });
19365
19366 if let Some(project) = self.project.clone() {
19367 self.save(
19368 SaveOptions {
19369 format: true,
19370 autosave: false,
19371 },
19372 project,
19373 window,
19374 cx,
19375 )
19376 .detach_and_log_err(cx);
19377 }
19378 }
19379
19380 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19381 if hovered != self.gutter_hovered {
19382 self.gutter_hovered = hovered;
19383 cx.notify();
19384 }
19385 }
19386
19387 pub fn insert_blocks(
19388 &mut self,
19389 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19390 autoscroll: Option<Autoscroll>,
19391 cx: &mut Context<Self>,
19392 ) -> Vec<CustomBlockId> {
19393 let blocks = self
19394 .display_map
19395 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19396 if let Some(autoscroll) = autoscroll {
19397 self.request_autoscroll(autoscroll, cx);
19398 }
19399 cx.notify();
19400 blocks
19401 }
19402
19403 pub fn resize_blocks(
19404 &mut self,
19405 heights: HashMap<CustomBlockId, u32>,
19406 autoscroll: Option<Autoscroll>,
19407 cx: &mut Context<Self>,
19408 ) {
19409 self.display_map
19410 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19411 if let Some(autoscroll) = autoscroll {
19412 self.request_autoscroll(autoscroll, cx);
19413 }
19414 cx.notify();
19415 }
19416
19417 pub fn replace_blocks(
19418 &mut self,
19419 renderers: HashMap<CustomBlockId, RenderBlock>,
19420 autoscroll: Option<Autoscroll>,
19421 cx: &mut Context<Self>,
19422 ) {
19423 self.display_map
19424 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19425 if let Some(autoscroll) = autoscroll {
19426 self.request_autoscroll(autoscroll, cx);
19427 }
19428 cx.notify();
19429 }
19430
19431 pub fn remove_blocks(
19432 &mut self,
19433 block_ids: HashSet<CustomBlockId>,
19434 autoscroll: Option<Autoscroll>,
19435 cx: &mut Context<Self>,
19436 ) {
19437 self.display_map.update(cx, |display_map, cx| {
19438 display_map.remove_blocks(block_ids, cx)
19439 });
19440 if let Some(autoscroll) = autoscroll {
19441 self.request_autoscroll(autoscroll, cx);
19442 }
19443 cx.notify();
19444 }
19445
19446 pub fn row_for_block(
19447 &self,
19448 block_id: CustomBlockId,
19449 cx: &mut Context<Self>,
19450 ) -> Option<DisplayRow> {
19451 self.display_map
19452 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19453 }
19454
19455 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19456 self.focused_block = Some(focused_block);
19457 }
19458
19459 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19460 self.focused_block.take()
19461 }
19462
19463 pub fn insert_creases(
19464 &mut self,
19465 creases: impl IntoIterator<Item = Crease<Anchor>>,
19466 cx: &mut Context<Self>,
19467 ) -> Vec<CreaseId> {
19468 self.display_map
19469 .update(cx, |map, cx| map.insert_creases(creases, cx))
19470 }
19471
19472 pub fn remove_creases(
19473 &mut self,
19474 ids: impl IntoIterator<Item = CreaseId>,
19475 cx: &mut Context<Self>,
19476 ) -> Vec<(CreaseId, Range<Anchor>)> {
19477 self.display_map
19478 .update(cx, |map, cx| map.remove_creases(ids, cx))
19479 }
19480
19481 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19482 self.display_map
19483 .update(cx, |map, cx| map.snapshot(cx))
19484 .longest_row()
19485 }
19486
19487 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19488 self.display_map
19489 .update(cx, |map, cx| map.snapshot(cx))
19490 .max_point()
19491 }
19492
19493 pub fn text(&self, cx: &App) -> String {
19494 self.buffer.read(cx).read(cx).text()
19495 }
19496
19497 pub fn is_empty(&self, cx: &App) -> bool {
19498 self.buffer.read(cx).read(cx).is_empty()
19499 }
19500
19501 pub fn text_option(&self, cx: &App) -> Option<String> {
19502 let text = self.text(cx);
19503 let text = text.trim();
19504
19505 if text.is_empty() {
19506 return None;
19507 }
19508
19509 Some(text.to_string())
19510 }
19511
19512 pub fn set_text(
19513 &mut self,
19514 text: impl Into<Arc<str>>,
19515 window: &mut Window,
19516 cx: &mut Context<Self>,
19517 ) {
19518 self.transact(window, cx, |this, _, cx| {
19519 this.buffer
19520 .read(cx)
19521 .as_singleton()
19522 .expect("you can only call set_text on editors for singleton buffers")
19523 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19524 });
19525 }
19526
19527 pub fn display_text(&self, cx: &mut App) -> String {
19528 self.display_map
19529 .update(cx, |map, cx| map.snapshot(cx))
19530 .text()
19531 }
19532
19533 fn create_minimap(
19534 &self,
19535 minimap_settings: MinimapSettings,
19536 window: &mut Window,
19537 cx: &mut Context<Self>,
19538 ) -> Option<Entity<Self>> {
19539 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19540 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19541 }
19542
19543 fn initialize_new_minimap(
19544 &self,
19545 minimap_settings: MinimapSettings,
19546 window: &mut Window,
19547 cx: &mut Context<Self>,
19548 ) -> Entity<Self> {
19549 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19550
19551 let mut minimap = Editor::new_internal(
19552 EditorMode::Minimap {
19553 parent: cx.weak_entity(),
19554 },
19555 self.buffer.clone(),
19556 None,
19557 Some(self.display_map.clone()),
19558 window,
19559 cx,
19560 );
19561 minimap.scroll_manager.clone_state(&self.scroll_manager);
19562 minimap.set_text_style_refinement(TextStyleRefinement {
19563 font_size: Some(MINIMAP_FONT_SIZE),
19564 font_weight: Some(MINIMAP_FONT_WEIGHT),
19565 ..Default::default()
19566 });
19567 minimap.update_minimap_configuration(minimap_settings, cx);
19568 cx.new(|_| minimap)
19569 }
19570
19571 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19572 let current_line_highlight = minimap_settings
19573 .current_line_highlight
19574 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19575 self.set_current_line_highlight(Some(current_line_highlight));
19576 }
19577
19578 pub fn minimap(&self) -> Option<&Entity<Self>> {
19579 self.minimap
19580 .as_ref()
19581 .filter(|_| self.minimap_visibility.visible())
19582 }
19583
19584 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19585 let mut wrap_guides = smallvec![];
19586
19587 if self.show_wrap_guides == Some(false) {
19588 return wrap_guides;
19589 }
19590
19591 let settings = self.buffer.read(cx).language_settings(cx);
19592 if settings.show_wrap_guides {
19593 match self.soft_wrap_mode(cx) {
19594 SoftWrap::Column(soft_wrap) => {
19595 wrap_guides.push((soft_wrap as usize, true));
19596 }
19597 SoftWrap::Bounded(soft_wrap) => {
19598 wrap_guides.push((soft_wrap as usize, true));
19599 }
19600 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19601 }
19602 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19603 }
19604
19605 wrap_guides
19606 }
19607
19608 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19609 let settings = self.buffer.read(cx).language_settings(cx);
19610 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19611 match mode {
19612 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19613 SoftWrap::None
19614 }
19615 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19616 language_settings::SoftWrap::PreferredLineLength => {
19617 SoftWrap::Column(settings.preferred_line_length)
19618 }
19619 language_settings::SoftWrap::Bounded => {
19620 SoftWrap::Bounded(settings.preferred_line_length)
19621 }
19622 }
19623 }
19624
19625 pub fn set_soft_wrap_mode(
19626 &mut self,
19627 mode: language_settings::SoftWrap,
19628
19629 cx: &mut Context<Self>,
19630 ) {
19631 self.soft_wrap_mode_override = Some(mode);
19632 cx.notify();
19633 }
19634
19635 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19636 self.hard_wrap = hard_wrap;
19637 cx.notify();
19638 }
19639
19640 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19641 self.text_style_refinement = Some(style);
19642 }
19643
19644 /// called by the Element so we know what style we were most recently rendered with.
19645 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19646 // We intentionally do not inform the display map about the minimap style
19647 // so that wrapping is not recalculated and stays consistent for the editor
19648 // and its linked minimap.
19649 if !self.mode.is_minimap() {
19650 let font = style.text.font();
19651 let font_size = style.text.font_size.to_pixels(window.rem_size());
19652 let display_map = self
19653 .placeholder_display_map
19654 .as_ref()
19655 .filter(|_| self.is_empty(cx))
19656 .unwrap_or(&self.display_map);
19657
19658 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19659 }
19660 self.style = Some(style);
19661 }
19662
19663 pub fn style(&self) -> Option<&EditorStyle> {
19664 self.style.as_ref()
19665 }
19666
19667 // Called by the element. This method is not designed to be called outside of the editor
19668 // element's layout code because it does not notify when rewrapping is computed synchronously.
19669 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19670 if self.is_empty(cx) {
19671 self.placeholder_display_map
19672 .as_ref()
19673 .map_or(false, |display_map| {
19674 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19675 })
19676 } else {
19677 self.display_map
19678 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19679 }
19680 }
19681
19682 pub fn set_soft_wrap(&mut self) {
19683 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19684 }
19685
19686 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19687 if self.soft_wrap_mode_override.is_some() {
19688 self.soft_wrap_mode_override.take();
19689 } else {
19690 let soft_wrap = match self.soft_wrap_mode(cx) {
19691 SoftWrap::GitDiff => return,
19692 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19693 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19694 language_settings::SoftWrap::None
19695 }
19696 };
19697 self.soft_wrap_mode_override = Some(soft_wrap);
19698 }
19699 cx.notify();
19700 }
19701
19702 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19703 let Some(workspace) = self.workspace() else {
19704 return;
19705 };
19706 let fs = workspace.read(cx).app_state().fs.clone();
19707 let current_show = TabBarSettings::get_global(cx).show;
19708 update_settings_file(fs, cx, move |setting, _| {
19709 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19710 });
19711 }
19712
19713 pub fn toggle_indent_guides(
19714 &mut self,
19715 _: &ToggleIndentGuides,
19716 _: &mut Window,
19717 cx: &mut Context<Self>,
19718 ) {
19719 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19720 self.buffer
19721 .read(cx)
19722 .language_settings(cx)
19723 .indent_guides
19724 .enabled
19725 });
19726 self.show_indent_guides = Some(!currently_enabled);
19727 cx.notify();
19728 }
19729
19730 fn should_show_indent_guides(&self) -> Option<bool> {
19731 self.show_indent_guides
19732 }
19733
19734 pub fn toggle_line_numbers(
19735 &mut self,
19736 _: &ToggleLineNumbers,
19737 _: &mut Window,
19738 cx: &mut Context<Self>,
19739 ) {
19740 let mut editor_settings = EditorSettings::get_global(cx).clone();
19741 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19742 EditorSettings::override_global(editor_settings, cx);
19743 }
19744
19745 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19746 if let Some(show_line_numbers) = self.show_line_numbers {
19747 return show_line_numbers;
19748 }
19749 EditorSettings::get_global(cx).gutter.line_numbers
19750 }
19751
19752 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19753 match (
19754 self.use_relative_line_numbers,
19755 EditorSettings::get_global(cx).relative_line_numbers,
19756 ) {
19757 (None, setting) => setting,
19758 (Some(false), _) => RelativeLineNumbers::Disabled,
19759 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19760 (Some(true), _) => RelativeLineNumbers::Enabled,
19761 }
19762 }
19763
19764 pub fn toggle_relative_line_numbers(
19765 &mut self,
19766 _: &ToggleRelativeLineNumbers,
19767 _: &mut Window,
19768 cx: &mut Context<Self>,
19769 ) {
19770 let is_relative = self.relative_line_numbers(cx);
19771 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19772 }
19773
19774 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19775 self.use_relative_line_numbers = is_relative;
19776 cx.notify();
19777 }
19778
19779 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19780 self.show_gutter = show_gutter;
19781 cx.notify();
19782 }
19783
19784 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19785 self.show_scrollbars = ScrollbarAxes {
19786 horizontal: show,
19787 vertical: show,
19788 };
19789 cx.notify();
19790 }
19791
19792 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19793 self.show_scrollbars.vertical = show;
19794 cx.notify();
19795 }
19796
19797 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19798 self.show_scrollbars.horizontal = show;
19799 cx.notify();
19800 }
19801
19802 pub fn set_minimap_visibility(
19803 &mut self,
19804 minimap_visibility: MinimapVisibility,
19805 window: &mut Window,
19806 cx: &mut Context<Self>,
19807 ) {
19808 if self.minimap_visibility != minimap_visibility {
19809 if minimap_visibility.visible() && self.minimap.is_none() {
19810 let minimap_settings = EditorSettings::get_global(cx).minimap;
19811 self.minimap =
19812 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19813 }
19814 self.minimap_visibility = minimap_visibility;
19815 cx.notify();
19816 }
19817 }
19818
19819 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19820 self.set_show_scrollbars(false, cx);
19821 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19822 }
19823
19824 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19825 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19826 }
19827
19828 /// Normally the text in full mode and auto height editors is padded on the
19829 /// left side by roughly half a character width for improved hit testing.
19830 ///
19831 /// Use this method to disable this for cases where this is not wanted (e.g.
19832 /// if you want to align the editor text with some other text above or below)
19833 /// or if you want to add this padding to single-line editors.
19834 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19835 self.offset_content = offset_content;
19836 cx.notify();
19837 }
19838
19839 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19840 self.show_line_numbers = Some(show_line_numbers);
19841 cx.notify();
19842 }
19843
19844 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19845 self.disable_expand_excerpt_buttons = true;
19846 cx.notify();
19847 }
19848
19849 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19850 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19851 cx.notify();
19852 }
19853
19854 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19855 self.show_code_actions = Some(show_code_actions);
19856 cx.notify();
19857 }
19858
19859 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19860 self.show_runnables = Some(show_runnables);
19861 cx.notify();
19862 }
19863
19864 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19865 self.show_breakpoints = Some(show_breakpoints);
19866 cx.notify();
19867 }
19868
19869 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19870 if self.display_map.read(cx).masked != masked {
19871 self.display_map.update(cx, |map, _| map.masked = masked);
19872 }
19873 cx.notify()
19874 }
19875
19876 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19877 self.show_wrap_guides = Some(show_wrap_guides);
19878 cx.notify();
19879 }
19880
19881 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19882 self.show_indent_guides = Some(show_indent_guides);
19883 cx.notify();
19884 }
19885
19886 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19887 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19888 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19889 && let Some(dir) = file.abs_path(cx).parent()
19890 {
19891 return Some(dir.to_owned());
19892 }
19893 }
19894
19895 None
19896 }
19897
19898 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19899 self.active_excerpt(cx)?
19900 .1
19901 .read(cx)
19902 .file()
19903 .and_then(|f| f.as_local())
19904 }
19905
19906 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19907 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19908 let buffer = buffer.read(cx);
19909 if let Some(project_path) = buffer.project_path(cx) {
19910 let project = self.project()?.read(cx);
19911 project.absolute_path(&project_path, cx)
19912 } else {
19913 buffer
19914 .file()
19915 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19916 }
19917 })
19918 }
19919
19920 pub fn reveal_in_finder(
19921 &mut self,
19922 _: &RevealInFileManager,
19923 _window: &mut Window,
19924 cx: &mut Context<Self>,
19925 ) {
19926 if let Some(target) = self.target_file(cx) {
19927 cx.reveal_path(&target.abs_path(cx));
19928 }
19929 }
19930
19931 pub fn copy_path(
19932 &mut self,
19933 _: &zed_actions::workspace::CopyPath,
19934 _window: &mut Window,
19935 cx: &mut Context<Self>,
19936 ) {
19937 if let Some(path) = self.target_file_abs_path(cx)
19938 && let Some(path) = path.to_str()
19939 {
19940 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19941 } else {
19942 cx.propagate();
19943 }
19944 }
19945
19946 pub fn copy_relative_path(
19947 &mut self,
19948 _: &zed_actions::workspace::CopyRelativePath,
19949 _window: &mut Window,
19950 cx: &mut Context<Self>,
19951 ) {
19952 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19953 let project = self.project()?.read(cx);
19954 let path = buffer.read(cx).file()?.path();
19955 let path = path.display(project.path_style(cx));
19956 Some(path)
19957 }) {
19958 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19959 } else {
19960 cx.propagate();
19961 }
19962 }
19963
19964 /// Returns the project path for the editor's buffer, if any buffer is
19965 /// opened in the editor.
19966 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19967 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19968 buffer.read(cx).project_path(cx)
19969 } else {
19970 None
19971 }
19972 }
19973
19974 // Returns true if the editor handled a go-to-line request
19975 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19976 maybe!({
19977 let breakpoint_store = self.breakpoint_store.as_ref()?;
19978
19979 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19980 else {
19981 self.clear_row_highlights::<ActiveDebugLine>();
19982 return None;
19983 };
19984
19985 let position = active_stack_frame.position;
19986 let buffer_id = position.buffer_id?;
19987 let snapshot = self
19988 .project
19989 .as_ref()?
19990 .read(cx)
19991 .buffer_for_id(buffer_id, cx)?
19992 .read(cx)
19993 .snapshot();
19994
19995 let mut handled = false;
19996 for (id, ExcerptRange { context, .. }) in
19997 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19998 {
19999 if context.start.cmp(&position, &snapshot).is_ge()
20000 || context.end.cmp(&position, &snapshot).is_lt()
20001 {
20002 continue;
20003 }
20004 let snapshot = self.buffer.read(cx).snapshot(cx);
20005 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20006
20007 handled = true;
20008 self.clear_row_highlights::<ActiveDebugLine>();
20009
20010 self.go_to_line::<ActiveDebugLine>(
20011 multibuffer_anchor,
20012 Some(cx.theme().colors().editor_debugger_active_line_background),
20013 window,
20014 cx,
20015 );
20016
20017 cx.notify();
20018 }
20019
20020 handled.then_some(())
20021 })
20022 .is_some()
20023 }
20024
20025 pub fn copy_file_name_without_extension(
20026 &mut self,
20027 _: &CopyFileNameWithoutExtension,
20028 _: &mut Window,
20029 cx: &mut Context<Self>,
20030 ) {
20031 if let Some(file) = self.target_file(cx)
20032 && let Some(file_stem) = file.path().file_stem()
20033 {
20034 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20035 }
20036 }
20037
20038 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20039 if let Some(file) = self.target_file(cx)
20040 && let Some(name) = file.path().file_name()
20041 {
20042 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
20043 }
20044 }
20045
20046 pub fn toggle_git_blame(
20047 &mut self,
20048 _: &::git::Blame,
20049 window: &mut Window,
20050 cx: &mut Context<Self>,
20051 ) {
20052 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20053
20054 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20055 self.start_git_blame(true, window, cx);
20056 }
20057
20058 cx.notify();
20059 }
20060
20061 pub fn toggle_git_blame_inline(
20062 &mut self,
20063 _: &ToggleGitBlameInline,
20064 window: &mut Window,
20065 cx: &mut Context<Self>,
20066 ) {
20067 self.toggle_git_blame_inline_internal(true, window, cx);
20068 cx.notify();
20069 }
20070
20071 pub fn open_git_blame_commit(
20072 &mut self,
20073 _: &OpenGitBlameCommit,
20074 window: &mut Window,
20075 cx: &mut Context<Self>,
20076 ) {
20077 self.open_git_blame_commit_internal(window, cx);
20078 }
20079
20080 fn open_git_blame_commit_internal(
20081 &mut self,
20082 window: &mut Window,
20083 cx: &mut Context<Self>,
20084 ) -> Option<()> {
20085 let blame = self.blame.as_ref()?;
20086 let snapshot = self.snapshot(window, cx);
20087 let cursor = self
20088 .selections
20089 .newest::<Point>(&snapshot.display_snapshot)
20090 .head();
20091 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20092 let (_, blame_entry) = blame
20093 .update(cx, |blame, cx| {
20094 blame
20095 .blame_for_rows(
20096 &[RowInfo {
20097 buffer_id: Some(buffer.remote_id()),
20098 buffer_row: Some(point.row),
20099 ..Default::default()
20100 }],
20101 cx,
20102 )
20103 .next()
20104 })
20105 .flatten()?;
20106 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20107 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20108 let workspace = self.workspace()?.downgrade();
20109 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20110 None
20111 }
20112
20113 pub fn git_blame_inline_enabled(&self) -> bool {
20114 self.git_blame_inline_enabled
20115 }
20116
20117 pub fn toggle_selection_menu(
20118 &mut self,
20119 _: &ToggleSelectionMenu,
20120 _: &mut Window,
20121 cx: &mut Context<Self>,
20122 ) {
20123 self.show_selection_menu = self
20124 .show_selection_menu
20125 .map(|show_selections_menu| !show_selections_menu)
20126 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20127
20128 cx.notify();
20129 }
20130
20131 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20132 self.show_selection_menu
20133 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20134 }
20135
20136 fn start_git_blame(
20137 &mut self,
20138 user_triggered: bool,
20139 window: &mut Window,
20140 cx: &mut Context<Self>,
20141 ) {
20142 if let Some(project) = self.project() {
20143 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20144 && buffer.read(cx).file().is_none()
20145 {
20146 return;
20147 }
20148
20149 let focused = self.focus_handle(cx).contains_focused(window, cx);
20150
20151 let project = project.clone();
20152 let blame = cx
20153 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20154 self.blame_subscription =
20155 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20156 self.blame = Some(blame);
20157 }
20158 }
20159
20160 fn toggle_git_blame_inline_internal(
20161 &mut self,
20162 user_triggered: bool,
20163 window: &mut Window,
20164 cx: &mut Context<Self>,
20165 ) {
20166 if self.git_blame_inline_enabled {
20167 self.git_blame_inline_enabled = false;
20168 self.show_git_blame_inline = false;
20169 self.show_git_blame_inline_delay_task.take();
20170 } else {
20171 self.git_blame_inline_enabled = true;
20172 self.start_git_blame_inline(user_triggered, window, cx);
20173 }
20174
20175 cx.notify();
20176 }
20177
20178 fn start_git_blame_inline(
20179 &mut self,
20180 user_triggered: bool,
20181 window: &mut Window,
20182 cx: &mut Context<Self>,
20183 ) {
20184 self.start_git_blame(user_triggered, window, cx);
20185
20186 if ProjectSettings::get_global(cx)
20187 .git
20188 .inline_blame_delay()
20189 .is_some()
20190 {
20191 self.start_inline_blame_timer(window, cx);
20192 } else {
20193 self.show_git_blame_inline = true
20194 }
20195 }
20196
20197 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20198 self.blame.as_ref()
20199 }
20200
20201 pub fn show_git_blame_gutter(&self) -> bool {
20202 self.show_git_blame_gutter
20203 }
20204
20205 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20206 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20207 }
20208
20209 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20210 self.show_git_blame_inline
20211 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20212 && !self.newest_selection_head_on_empty_line(cx)
20213 && self.has_blame_entries(cx)
20214 }
20215
20216 fn has_blame_entries(&self, cx: &App) -> bool {
20217 self.blame()
20218 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20219 }
20220
20221 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20222 let cursor_anchor = self.selections.newest_anchor().head();
20223
20224 let snapshot = self.buffer.read(cx).snapshot(cx);
20225 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20226
20227 snapshot.line_len(buffer_row) == 0
20228 }
20229
20230 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20231 let buffer_and_selection = maybe!({
20232 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20233 let selection_range = selection.range();
20234
20235 let multi_buffer = self.buffer().read(cx);
20236 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20237 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20238
20239 let (buffer, range, _) = if selection.reversed {
20240 buffer_ranges.first()
20241 } else {
20242 buffer_ranges.last()
20243 }?;
20244
20245 let selection = text::ToPoint::to_point(&range.start, buffer).row
20246 ..text::ToPoint::to_point(&range.end, buffer).row;
20247 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20248 });
20249
20250 let Some((buffer, selection)) = buffer_and_selection else {
20251 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20252 };
20253
20254 let Some(project) = self.project() else {
20255 return Task::ready(Err(anyhow!("editor does not have project")));
20256 };
20257
20258 project.update(cx, |project, cx| {
20259 project.get_permalink_to_line(&buffer, selection, cx)
20260 })
20261 }
20262
20263 pub fn copy_permalink_to_line(
20264 &mut self,
20265 _: &CopyPermalinkToLine,
20266 window: &mut Window,
20267 cx: &mut Context<Self>,
20268 ) {
20269 let permalink_task = self.get_permalink_to_line(cx);
20270 let workspace = self.workspace();
20271
20272 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20273 Ok(permalink) => {
20274 cx.update(|_, cx| {
20275 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20276 })
20277 .ok();
20278 }
20279 Err(err) => {
20280 let message = format!("Failed to copy permalink: {err}");
20281
20282 anyhow::Result::<()>::Err(err).log_err();
20283
20284 if let Some(workspace) = workspace {
20285 workspace
20286 .update_in(cx, |workspace, _, cx| {
20287 struct CopyPermalinkToLine;
20288
20289 workspace.show_toast(
20290 Toast::new(
20291 NotificationId::unique::<CopyPermalinkToLine>(),
20292 message,
20293 ),
20294 cx,
20295 )
20296 })
20297 .ok();
20298 }
20299 }
20300 })
20301 .detach();
20302 }
20303
20304 pub fn copy_file_location(
20305 &mut self,
20306 _: &CopyFileLocation,
20307 _: &mut Window,
20308 cx: &mut Context<Self>,
20309 ) {
20310 let selection = self
20311 .selections
20312 .newest::<Point>(&self.display_snapshot(cx))
20313 .start
20314 .row
20315 + 1;
20316 if let Some(file) = self.target_file(cx) {
20317 let path = file.path().display(file.path_style(cx));
20318 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20319 }
20320 }
20321
20322 pub fn open_permalink_to_line(
20323 &mut self,
20324 _: &OpenPermalinkToLine,
20325 window: &mut Window,
20326 cx: &mut Context<Self>,
20327 ) {
20328 let permalink_task = self.get_permalink_to_line(cx);
20329 let workspace = self.workspace();
20330
20331 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20332 Ok(permalink) => {
20333 cx.update(|_, cx| {
20334 cx.open_url(permalink.as_ref());
20335 })
20336 .ok();
20337 }
20338 Err(err) => {
20339 let message = format!("Failed to open permalink: {err}");
20340
20341 anyhow::Result::<()>::Err(err).log_err();
20342
20343 if let Some(workspace) = workspace {
20344 workspace
20345 .update(cx, |workspace, cx| {
20346 struct OpenPermalinkToLine;
20347
20348 workspace.show_toast(
20349 Toast::new(
20350 NotificationId::unique::<OpenPermalinkToLine>(),
20351 message,
20352 ),
20353 cx,
20354 )
20355 })
20356 .ok();
20357 }
20358 }
20359 })
20360 .detach();
20361 }
20362
20363 pub fn insert_uuid_v4(
20364 &mut self,
20365 _: &InsertUuidV4,
20366 window: &mut Window,
20367 cx: &mut Context<Self>,
20368 ) {
20369 self.insert_uuid(UuidVersion::V4, window, cx);
20370 }
20371
20372 pub fn insert_uuid_v7(
20373 &mut self,
20374 _: &InsertUuidV7,
20375 window: &mut Window,
20376 cx: &mut Context<Self>,
20377 ) {
20378 self.insert_uuid(UuidVersion::V7, window, cx);
20379 }
20380
20381 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20382 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20383 self.transact(window, cx, |this, window, cx| {
20384 let edits = this
20385 .selections
20386 .all::<Point>(&this.display_snapshot(cx))
20387 .into_iter()
20388 .map(|selection| {
20389 let uuid = match version {
20390 UuidVersion::V4 => uuid::Uuid::new_v4(),
20391 UuidVersion::V7 => uuid::Uuid::now_v7(),
20392 };
20393
20394 (selection.range(), uuid.to_string())
20395 });
20396 this.edit(edits, cx);
20397 this.refresh_edit_prediction(true, false, window, cx);
20398 });
20399 }
20400
20401 pub fn open_selections_in_multibuffer(
20402 &mut self,
20403 _: &OpenSelectionsInMultibuffer,
20404 window: &mut Window,
20405 cx: &mut Context<Self>,
20406 ) {
20407 let multibuffer = self.buffer.read(cx);
20408
20409 let Some(buffer) = multibuffer.as_singleton() else {
20410 return;
20411 };
20412
20413 let Some(workspace) = self.workspace() else {
20414 return;
20415 };
20416
20417 let title = multibuffer.title(cx).to_string();
20418
20419 let locations = self
20420 .selections
20421 .all_anchors(&self.display_snapshot(cx))
20422 .iter()
20423 .map(|selection| {
20424 (
20425 buffer.clone(),
20426 (selection.start.text_anchor..selection.end.text_anchor)
20427 .to_point(buffer.read(cx)),
20428 )
20429 })
20430 .into_group_map();
20431
20432 cx.spawn_in(window, async move |_, cx| {
20433 workspace.update_in(cx, |workspace, window, cx| {
20434 Self::open_locations_in_multibuffer(
20435 workspace,
20436 locations,
20437 format!("Selections for '{title}'"),
20438 false,
20439 MultibufferSelectionMode::All,
20440 window,
20441 cx,
20442 );
20443 })
20444 })
20445 .detach();
20446 }
20447
20448 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20449 /// last highlight added will be used.
20450 ///
20451 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20452 pub fn highlight_rows<T: 'static>(
20453 &mut self,
20454 range: Range<Anchor>,
20455 color: Hsla,
20456 options: RowHighlightOptions,
20457 cx: &mut Context<Self>,
20458 ) {
20459 let snapshot = self.buffer().read(cx).snapshot(cx);
20460 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20461 let ix = row_highlights.binary_search_by(|highlight| {
20462 Ordering::Equal
20463 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20464 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20465 });
20466
20467 if let Err(mut ix) = ix {
20468 let index = post_inc(&mut self.highlight_order);
20469
20470 // If this range intersects with the preceding highlight, then merge it with
20471 // the preceding highlight. Otherwise insert a new highlight.
20472 let mut merged = false;
20473 if ix > 0 {
20474 let prev_highlight = &mut row_highlights[ix - 1];
20475 if prev_highlight
20476 .range
20477 .end
20478 .cmp(&range.start, &snapshot)
20479 .is_ge()
20480 {
20481 ix -= 1;
20482 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20483 prev_highlight.range.end = range.end;
20484 }
20485 merged = true;
20486 prev_highlight.index = index;
20487 prev_highlight.color = color;
20488 prev_highlight.options = options;
20489 }
20490 }
20491
20492 if !merged {
20493 row_highlights.insert(
20494 ix,
20495 RowHighlight {
20496 range,
20497 index,
20498 color,
20499 options,
20500 type_id: TypeId::of::<T>(),
20501 },
20502 );
20503 }
20504
20505 // If any of the following highlights intersect with this one, merge them.
20506 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20507 let highlight = &row_highlights[ix];
20508 if next_highlight
20509 .range
20510 .start
20511 .cmp(&highlight.range.end, &snapshot)
20512 .is_le()
20513 {
20514 if next_highlight
20515 .range
20516 .end
20517 .cmp(&highlight.range.end, &snapshot)
20518 .is_gt()
20519 {
20520 row_highlights[ix].range.end = next_highlight.range.end;
20521 }
20522 row_highlights.remove(ix + 1);
20523 } else {
20524 break;
20525 }
20526 }
20527 }
20528 }
20529
20530 /// Remove any highlighted row ranges of the given type that intersect the
20531 /// given ranges.
20532 pub fn remove_highlighted_rows<T: 'static>(
20533 &mut self,
20534 ranges_to_remove: Vec<Range<Anchor>>,
20535 cx: &mut Context<Self>,
20536 ) {
20537 let snapshot = self.buffer().read(cx).snapshot(cx);
20538 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20539 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20540 row_highlights.retain(|highlight| {
20541 while let Some(range_to_remove) = ranges_to_remove.peek() {
20542 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20543 Ordering::Less | Ordering::Equal => {
20544 ranges_to_remove.next();
20545 }
20546 Ordering::Greater => {
20547 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20548 Ordering::Less | Ordering::Equal => {
20549 return false;
20550 }
20551 Ordering::Greater => break,
20552 }
20553 }
20554 }
20555 }
20556
20557 true
20558 })
20559 }
20560
20561 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20562 pub fn clear_row_highlights<T: 'static>(&mut self) {
20563 self.highlighted_rows.remove(&TypeId::of::<T>());
20564 }
20565
20566 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20567 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20568 self.highlighted_rows
20569 .get(&TypeId::of::<T>())
20570 .map_or(&[] as &[_], |vec| vec.as_slice())
20571 .iter()
20572 .map(|highlight| (highlight.range.clone(), highlight.color))
20573 }
20574
20575 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20576 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20577 /// Allows to ignore certain kinds of highlights.
20578 pub fn highlighted_display_rows(
20579 &self,
20580 window: &mut Window,
20581 cx: &mut App,
20582 ) -> BTreeMap<DisplayRow, LineHighlight> {
20583 let snapshot = self.snapshot(window, cx);
20584 let mut used_highlight_orders = HashMap::default();
20585 self.highlighted_rows
20586 .iter()
20587 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20588 .fold(
20589 BTreeMap::<DisplayRow, LineHighlight>::new(),
20590 |mut unique_rows, highlight| {
20591 let start = highlight.range.start.to_display_point(&snapshot);
20592 let end = highlight.range.end.to_display_point(&snapshot);
20593 let start_row = start.row().0;
20594 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20595 && end.column() == 0
20596 {
20597 end.row().0.saturating_sub(1)
20598 } else {
20599 end.row().0
20600 };
20601 for row in start_row..=end_row {
20602 let used_index =
20603 used_highlight_orders.entry(row).or_insert(highlight.index);
20604 if highlight.index >= *used_index {
20605 *used_index = highlight.index;
20606 unique_rows.insert(
20607 DisplayRow(row),
20608 LineHighlight {
20609 include_gutter: highlight.options.include_gutter,
20610 border: None,
20611 background: highlight.color.into(),
20612 type_id: Some(highlight.type_id),
20613 },
20614 );
20615 }
20616 }
20617 unique_rows
20618 },
20619 )
20620 }
20621
20622 pub fn highlighted_display_row_for_autoscroll(
20623 &self,
20624 snapshot: &DisplaySnapshot,
20625 ) -> Option<DisplayRow> {
20626 self.highlighted_rows
20627 .values()
20628 .flat_map(|highlighted_rows| highlighted_rows.iter())
20629 .filter_map(|highlight| {
20630 if highlight.options.autoscroll {
20631 Some(highlight.range.start.to_display_point(snapshot).row())
20632 } else {
20633 None
20634 }
20635 })
20636 .min()
20637 }
20638
20639 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20640 self.highlight_background::<SearchWithinRange>(
20641 ranges,
20642 |colors| colors.colors().editor_document_highlight_read_background,
20643 cx,
20644 )
20645 }
20646
20647 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20648 self.breadcrumb_header = Some(new_header);
20649 }
20650
20651 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20652 self.clear_background_highlights::<SearchWithinRange>(cx);
20653 }
20654
20655 pub fn highlight_background<T: 'static>(
20656 &mut self,
20657 ranges: &[Range<Anchor>],
20658 color_fetcher: fn(&Theme) -> Hsla,
20659 cx: &mut Context<Self>,
20660 ) {
20661 self.background_highlights.insert(
20662 HighlightKey::Type(TypeId::of::<T>()),
20663 (color_fetcher, Arc::from(ranges)),
20664 );
20665 self.scrollbar_marker_state.dirty = true;
20666 cx.notify();
20667 }
20668
20669 pub fn highlight_background_key<T: 'static>(
20670 &mut self,
20671 key: usize,
20672 ranges: &[Range<Anchor>],
20673 color_fetcher: fn(&Theme) -> Hsla,
20674 cx: &mut Context<Self>,
20675 ) {
20676 self.background_highlights.insert(
20677 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20678 (color_fetcher, Arc::from(ranges)),
20679 );
20680 self.scrollbar_marker_state.dirty = true;
20681 cx.notify();
20682 }
20683
20684 pub fn clear_background_highlights<T: 'static>(
20685 &mut self,
20686 cx: &mut Context<Self>,
20687 ) -> Option<BackgroundHighlight> {
20688 let text_highlights = self
20689 .background_highlights
20690 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20691 if !text_highlights.1.is_empty() {
20692 self.scrollbar_marker_state.dirty = true;
20693 cx.notify();
20694 }
20695 Some(text_highlights)
20696 }
20697
20698 pub fn highlight_gutter<T: 'static>(
20699 &mut self,
20700 ranges: impl Into<Vec<Range<Anchor>>>,
20701 color_fetcher: fn(&App) -> Hsla,
20702 cx: &mut Context<Self>,
20703 ) {
20704 self.gutter_highlights
20705 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20706 cx.notify();
20707 }
20708
20709 pub fn clear_gutter_highlights<T: 'static>(
20710 &mut self,
20711 cx: &mut Context<Self>,
20712 ) -> Option<GutterHighlight> {
20713 cx.notify();
20714 self.gutter_highlights.remove(&TypeId::of::<T>())
20715 }
20716
20717 pub fn insert_gutter_highlight<T: 'static>(
20718 &mut self,
20719 range: Range<Anchor>,
20720 color_fetcher: fn(&App) -> Hsla,
20721 cx: &mut Context<Self>,
20722 ) {
20723 let snapshot = self.buffer().read(cx).snapshot(cx);
20724 let mut highlights = self
20725 .gutter_highlights
20726 .remove(&TypeId::of::<T>())
20727 .map(|(_, highlights)| highlights)
20728 .unwrap_or_default();
20729 let ix = highlights.binary_search_by(|highlight| {
20730 Ordering::Equal
20731 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20732 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20733 });
20734 if let Err(ix) = ix {
20735 highlights.insert(ix, range);
20736 }
20737 self.gutter_highlights
20738 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20739 }
20740
20741 pub fn remove_gutter_highlights<T: 'static>(
20742 &mut self,
20743 ranges_to_remove: Vec<Range<Anchor>>,
20744 cx: &mut Context<Self>,
20745 ) {
20746 let snapshot = self.buffer().read(cx).snapshot(cx);
20747 let Some((color_fetcher, mut gutter_highlights)) =
20748 self.gutter_highlights.remove(&TypeId::of::<T>())
20749 else {
20750 return;
20751 };
20752 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20753 gutter_highlights.retain(|highlight| {
20754 while let Some(range_to_remove) = ranges_to_remove.peek() {
20755 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20756 Ordering::Less | Ordering::Equal => {
20757 ranges_to_remove.next();
20758 }
20759 Ordering::Greater => {
20760 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20761 Ordering::Less | Ordering::Equal => {
20762 return false;
20763 }
20764 Ordering::Greater => break,
20765 }
20766 }
20767 }
20768 }
20769
20770 true
20771 });
20772 self.gutter_highlights
20773 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20774 }
20775
20776 #[cfg(feature = "test-support")]
20777 pub fn all_text_highlights(
20778 &self,
20779 window: &mut Window,
20780 cx: &mut Context<Self>,
20781 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20782 let snapshot = self.snapshot(window, cx);
20783 self.display_map.update(cx, |display_map, _| {
20784 display_map
20785 .all_text_highlights()
20786 .map(|highlight| {
20787 let (style, ranges) = highlight.as_ref();
20788 (
20789 *style,
20790 ranges
20791 .iter()
20792 .map(|range| range.clone().to_display_points(&snapshot))
20793 .collect(),
20794 )
20795 })
20796 .collect()
20797 })
20798 }
20799
20800 #[cfg(feature = "test-support")]
20801 pub fn all_text_background_highlights(
20802 &self,
20803 window: &mut Window,
20804 cx: &mut Context<Self>,
20805 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20806 let snapshot = self.snapshot(window, cx);
20807 let buffer = &snapshot.buffer_snapshot();
20808 let start = buffer.anchor_before(0);
20809 let end = buffer.anchor_after(buffer.len());
20810 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20811 }
20812
20813 #[cfg(any(test, feature = "test-support"))]
20814 pub fn sorted_background_highlights_in_range(
20815 &self,
20816 search_range: Range<Anchor>,
20817 display_snapshot: &DisplaySnapshot,
20818 theme: &Theme,
20819 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20820 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20821 res.sort_by(|a, b| {
20822 a.0.start
20823 .cmp(&b.0.start)
20824 .then_with(|| a.0.end.cmp(&b.0.end))
20825 .then_with(|| a.1.cmp(&b.1))
20826 });
20827 res
20828 }
20829
20830 #[cfg(feature = "test-support")]
20831 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20832 let snapshot = self.buffer().read(cx).snapshot(cx);
20833
20834 let highlights = self
20835 .background_highlights
20836 .get(&HighlightKey::Type(TypeId::of::<
20837 items::BufferSearchHighlights,
20838 >()));
20839
20840 if let Some((_color, ranges)) = highlights {
20841 ranges
20842 .iter()
20843 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20844 .collect_vec()
20845 } else {
20846 vec![]
20847 }
20848 }
20849
20850 fn document_highlights_for_position<'a>(
20851 &'a self,
20852 position: Anchor,
20853 buffer: &'a MultiBufferSnapshot,
20854 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20855 let read_highlights = self
20856 .background_highlights
20857 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20858 .map(|h| &h.1);
20859 let write_highlights = self
20860 .background_highlights
20861 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20862 .map(|h| &h.1);
20863 let left_position = position.bias_left(buffer);
20864 let right_position = position.bias_right(buffer);
20865 read_highlights
20866 .into_iter()
20867 .chain(write_highlights)
20868 .flat_map(move |ranges| {
20869 let start_ix = match ranges.binary_search_by(|probe| {
20870 let cmp = probe.end.cmp(&left_position, buffer);
20871 if cmp.is_ge() {
20872 Ordering::Greater
20873 } else {
20874 Ordering::Less
20875 }
20876 }) {
20877 Ok(i) | Err(i) => i,
20878 };
20879
20880 ranges[start_ix..]
20881 .iter()
20882 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20883 })
20884 }
20885
20886 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20887 self.background_highlights
20888 .get(&HighlightKey::Type(TypeId::of::<T>()))
20889 .is_some_and(|(_, highlights)| !highlights.is_empty())
20890 }
20891
20892 /// Returns all background highlights for a given range.
20893 ///
20894 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20895 pub fn background_highlights_in_range(
20896 &self,
20897 search_range: Range<Anchor>,
20898 display_snapshot: &DisplaySnapshot,
20899 theme: &Theme,
20900 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20901 let mut results = Vec::new();
20902 for (color_fetcher, ranges) in self.background_highlights.values() {
20903 let color = color_fetcher(theme);
20904 let start_ix = match ranges.binary_search_by(|probe| {
20905 let cmp = probe
20906 .end
20907 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20908 if cmp.is_gt() {
20909 Ordering::Greater
20910 } else {
20911 Ordering::Less
20912 }
20913 }) {
20914 Ok(i) | Err(i) => i,
20915 };
20916 for range in &ranges[start_ix..] {
20917 if range
20918 .start
20919 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20920 .is_ge()
20921 {
20922 break;
20923 }
20924
20925 let start = range.start.to_display_point(display_snapshot);
20926 let end = range.end.to_display_point(display_snapshot);
20927 results.push((start..end, color))
20928 }
20929 }
20930 results
20931 }
20932
20933 pub fn gutter_highlights_in_range(
20934 &self,
20935 search_range: Range<Anchor>,
20936 display_snapshot: &DisplaySnapshot,
20937 cx: &App,
20938 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20939 let mut results = Vec::new();
20940 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20941 let color = color_fetcher(cx);
20942 let start_ix = match ranges.binary_search_by(|probe| {
20943 let cmp = probe
20944 .end
20945 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20946 if cmp.is_gt() {
20947 Ordering::Greater
20948 } else {
20949 Ordering::Less
20950 }
20951 }) {
20952 Ok(i) | Err(i) => i,
20953 };
20954 for range in &ranges[start_ix..] {
20955 if range
20956 .start
20957 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20958 .is_ge()
20959 {
20960 break;
20961 }
20962
20963 let start = range.start.to_display_point(display_snapshot);
20964 let end = range.end.to_display_point(display_snapshot);
20965 results.push((start..end, color))
20966 }
20967 }
20968 results
20969 }
20970
20971 /// Get the text ranges corresponding to the redaction query
20972 pub fn redacted_ranges(
20973 &self,
20974 search_range: Range<Anchor>,
20975 display_snapshot: &DisplaySnapshot,
20976 cx: &App,
20977 ) -> Vec<Range<DisplayPoint>> {
20978 display_snapshot
20979 .buffer_snapshot()
20980 .redacted_ranges(search_range, |file| {
20981 if let Some(file) = file {
20982 file.is_private()
20983 && EditorSettings::get(
20984 Some(SettingsLocation {
20985 worktree_id: file.worktree_id(cx),
20986 path: file.path().as_ref(),
20987 }),
20988 cx,
20989 )
20990 .redact_private_values
20991 } else {
20992 false
20993 }
20994 })
20995 .map(|range| {
20996 range.start.to_display_point(display_snapshot)
20997 ..range.end.to_display_point(display_snapshot)
20998 })
20999 .collect()
21000 }
21001
21002 pub fn highlight_text_key<T: 'static>(
21003 &mut self,
21004 key: usize,
21005 ranges: Vec<Range<Anchor>>,
21006 style: HighlightStyle,
21007 cx: &mut Context<Self>,
21008 ) {
21009 self.display_map.update(cx, |map, _| {
21010 map.highlight_text(
21011 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21012 ranges,
21013 style,
21014 );
21015 });
21016 cx.notify();
21017 }
21018
21019 pub fn highlight_text<T: 'static>(
21020 &mut self,
21021 ranges: Vec<Range<Anchor>>,
21022 style: HighlightStyle,
21023 cx: &mut Context<Self>,
21024 ) {
21025 self.display_map.update(cx, |map, _| {
21026 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
21027 });
21028 cx.notify();
21029 }
21030
21031 pub fn text_highlights<'a, T: 'static>(
21032 &'a self,
21033 cx: &'a App,
21034 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21035 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21036 }
21037
21038 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21039 let cleared = self
21040 .display_map
21041 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21042 if cleared {
21043 cx.notify();
21044 }
21045 }
21046
21047 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21048 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21049 && self.focus_handle.is_focused(window)
21050 }
21051
21052 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21053 self.show_cursor_when_unfocused = is_enabled;
21054 cx.notify();
21055 }
21056
21057 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21058 cx.notify();
21059 }
21060
21061 fn on_debug_session_event(
21062 &mut self,
21063 _session: Entity<Session>,
21064 event: &SessionEvent,
21065 cx: &mut Context<Self>,
21066 ) {
21067 if let SessionEvent::InvalidateInlineValue = event {
21068 self.refresh_inline_values(cx);
21069 }
21070 }
21071
21072 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21073 let Some(project) = self.project.clone() else {
21074 return;
21075 };
21076
21077 if !self.inline_value_cache.enabled {
21078 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21079 self.splice_inlays(&inlays, Vec::new(), cx);
21080 return;
21081 }
21082
21083 let current_execution_position = self
21084 .highlighted_rows
21085 .get(&TypeId::of::<ActiveDebugLine>())
21086 .and_then(|lines| lines.last().map(|line| line.range.end));
21087
21088 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21089 let inline_values = editor
21090 .update(cx, |editor, cx| {
21091 let Some(current_execution_position) = current_execution_position else {
21092 return Some(Task::ready(Ok(Vec::new())));
21093 };
21094
21095 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21096 let snapshot = buffer.snapshot(cx);
21097
21098 let excerpt = snapshot.excerpt_containing(
21099 current_execution_position..current_execution_position,
21100 )?;
21101
21102 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21103 })?;
21104
21105 let range =
21106 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21107
21108 project.inline_values(buffer, range, cx)
21109 })
21110 .ok()
21111 .flatten()?
21112 .await
21113 .context("refreshing debugger inlays")
21114 .log_err()?;
21115
21116 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21117
21118 for (buffer_id, inline_value) in inline_values
21119 .into_iter()
21120 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21121 {
21122 buffer_inline_values
21123 .entry(buffer_id)
21124 .or_default()
21125 .push(inline_value);
21126 }
21127
21128 editor
21129 .update(cx, |editor, cx| {
21130 let snapshot = editor.buffer.read(cx).snapshot(cx);
21131 let mut new_inlays = Vec::default();
21132
21133 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21134 let buffer_id = buffer_snapshot.remote_id();
21135 buffer_inline_values
21136 .get(&buffer_id)
21137 .into_iter()
21138 .flatten()
21139 .for_each(|hint| {
21140 let inlay = Inlay::debugger(
21141 post_inc(&mut editor.next_inlay_id),
21142 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
21143 hint.text(),
21144 );
21145 if !inlay.text().chars().contains(&'\n') {
21146 new_inlays.push(inlay);
21147 }
21148 });
21149 }
21150
21151 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21152 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21153
21154 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21155 })
21156 .ok()?;
21157 Some(())
21158 });
21159 }
21160
21161 fn on_buffer_event(
21162 &mut self,
21163 multibuffer: &Entity<MultiBuffer>,
21164 event: &multi_buffer::Event,
21165 window: &mut Window,
21166 cx: &mut Context<Self>,
21167 ) {
21168 match event {
21169 multi_buffer::Event::Edited { edited_buffer } => {
21170 self.scrollbar_marker_state.dirty = true;
21171 self.active_indent_guides_state.dirty = true;
21172 self.refresh_active_diagnostics(cx);
21173 self.refresh_code_actions(window, cx);
21174 self.refresh_selected_text_highlights(true, window, cx);
21175 self.refresh_single_line_folds(window, cx);
21176 self.refresh_matching_bracket_highlights(window, cx);
21177 if self.has_active_edit_prediction() {
21178 self.update_visible_edit_prediction(window, cx);
21179 }
21180
21181 if let Some(buffer) = edited_buffer {
21182 if buffer.read(cx).file().is_none() {
21183 cx.emit(EditorEvent::TitleChanged);
21184 }
21185
21186 if self.project.is_some() {
21187 let buffer_id = buffer.read(cx).remote_id();
21188 self.register_buffer(buffer_id, cx);
21189 self.update_lsp_data(Some(buffer_id), window, cx);
21190 self.refresh_inlay_hints(
21191 InlayHintRefreshReason::BufferEdited(buffer_id),
21192 cx,
21193 );
21194 }
21195 }
21196
21197 cx.emit(EditorEvent::BufferEdited);
21198 cx.emit(SearchEvent::MatchesInvalidated);
21199
21200 let Some(project) = &self.project else { return };
21201 let (telemetry, is_via_ssh) = {
21202 let project = project.read(cx);
21203 let telemetry = project.client().telemetry().clone();
21204 let is_via_ssh = project.is_via_remote_server();
21205 (telemetry, is_via_ssh)
21206 };
21207 telemetry.log_edit_event("editor", is_via_ssh);
21208 }
21209 multi_buffer::Event::ExcerptsAdded {
21210 buffer,
21211 predecessor,
21212 excerpts,
21213 } => {
21214 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21215 let buffer_id = buffer.read(cx).remote_id();
21216 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21217 && let Some(project) = &self.project
21218 {
21219 update_uncommitted_diff_for_buffer(
21220 cx.entity(),
21221 project,
21222 [buffer.clone()],
21223 self.buffer.clone(),
21224 cx,
21225 )
21226 .detach();
21227 }
21228 self.update_lsp_data(Some(buffer_id), window, cx);
21229 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21230 cx.emit(EditorEvent::ExcerptsAdded {
21231 buffer: buffer.clone(),
21232 predecessor: *predecessor,
21233 excerpts: excerpts.clone(),
21234 });
21235 }
21236 multi_buffer::Event::ExcerptsRemoved {
21237 ids,
21238 removed_buffer_ids,
21239 } => {
21240 if let Some(inlay_hints) = &mut self.inlay_hints {
21241 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21242 }
21243 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21244 for buffer_id in removed_buffer_ids {
21245 self.registered_buffers.remove(buffer_id);
21246 }
21247 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21248 cx.emit(EditorEvent::ExcerptsRemoved {
21249 ids: ids.clone(),
21250 removed_buffer_ids: removed_buffer_ids.clone(),
21251 });
21252 }
21253 multi_buffer::Event::ExcerptsEdited {
21254 excerpt_ids,
21255 buffer_ids,
21256 } => {
21257 self.display_map.update(cx, |map, cx| {
21258 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21259 });
21260 cx.emit(EditorEvent::ExcerptsEdited {
21261 ids: excerpt_ids.clone(),
21262 });
21263 }
21264 multi_buffer::Event::ExcerptsExpanded { ids } => {
21265 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21266 self.refresh_document_highlights(cx);
21267 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21268 }
21269 multi_buffer::Event::Reparsed(buffer_id) => {
21270 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21271 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21272
21273 cx.emit(EditorEvent::Reparsed(*buffer_id));
21274 }
21275 multi_buffer::Event::DiffHunksToggled => {
21276 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21277 }
21278 multi_buffer::Event::LanguageChanged(buffer_id) => {
21279 self.registered_buffers.remove(&buffer_id);
21280 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21281 cx.emit(EditorEvent::Reparsed(*buffer_id));
21282 cx.notify();
21283 }
21284 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21285 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21286 multi_buffer::Event::FileHandleChanged
21287 | multi_buffer::Event::Reloaded
21288 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21289 multi_buffer::Event::DiagnosticsUpdated => {
21290 self.update_diagnostics_state(window, cx);
21291 }
21292 _ => {}
21293 };
21294 }
21295
21296 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21297 if !self.diagnostics_enabled() {
21298 return;
21299 }
21300 self.refresh_active_diagnostics(cx);
21301 self.refresh_inline_diagnostics(true, window, cx);
21302 self.scrollbar_marker_state.dirty = true;
21303 cx.notify();
21304 }
21305
21306 pub fn start_temporary_diff_override(&mut self) {
21307 self.load_diff_task.take();
21308 self.temporary_diff_override = true;
21309 }
21310
21311 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21312 self.temporary_diff_override = false;
21313 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21314 self.buffer.update(cx, |buffer, cx| {
21315 buffer.set_all_diff_hunks_collapsed(cx);
21316 });
21317
21318 if let Some(project) = self.project.clone() {
21319 self.load_diff_task = Some(
21320 update_uncommitted_diff_for_buffer(
21321 cx.entity(),
21322 &project,
21323 self.buffer.read(cx).all_buffers(),
21324 self.buffer.clone(),
21325 cx,
21326 )
21327 .shared(),
21328 );
21329 }
21330 }
21331
21332 fn on_display_map_changed(
21333 &mut self,
21334 _: Entity<DisplayMap>,
21335 _: &mut Window,
21336 cx: &mut Context<Self>,
21337 ) {
21338 cx.notify();
21339 }
21340
21341 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21342 if self.diagnostics_enabled() {
21343 let new_severity = EditorSettings::get_global(cx)
21344 .diagnostics_max_severity
21345 .unwrap_or(DiagnosticSeverity::Hint);
21346 self.set_max_diagnostics_severity(new_severity, cx);
21347 }
21348 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21349 self.update_edit_prediction_settings(cx);
21350 self.refresh_edit_prediction(true, false, window, cx);
21351 self.refresh_inline_values(cx);
21352 self.refresh_inlay_hints(
21353 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21354 self.selections.newest_anchor().head(),
21355 &self.buffer.read(cx).snapshot(cx),
21356 cx,
21357 )),
21358 cx,
21359 );
21360
21361 let old_cursor_shape = self.cursor_shape;
21362 let old_show_breadcrumbs = self.show_breadcrumbs;
21363
21364 {
21365 let editor_settings = EditorSettings::get_global(cx);
21366 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21367 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21368 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21369 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21370 }
21371
21372 if old_cursor_shape != self.cursor_shape {
21373 cx.emit(EditorEvent::CursorShapeChanged);
21374 }
21375
21376 if old_show_breadcrumbs != self.show_breadcrumbs {
21377 cx.emit(EditorEvent::BreadcrumbsChanged);
21378 }
21379
21380 let project_settings = ProjectSettings::get_global(cx);
21381 self.buffer_serialization = self
21382 .should_serialize_buffer()
21383 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21384
21385 if self.mode.is_full() {
21386 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21387 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21388 if self.show_inline_diagnostics != show_inline_diagnostics {
21389 self.show_inline_diagnostics = show_inline_diagnostics;
21390 self.refresh_inline_diagnostics(false, window, cx);
21391 }
21392
21393 if self.git_blame_inline_enabled != inline_blame_enabled {
21394 self.toggle_git_blame_inline_internal(false, window, cx);
21395 }
21396
21397 let minimap_settings = EditorSettings::get_global(cx).minimap;
21398 if self.minimap_visibility != MinimapVisibility::Disabled {
21399 if self.minimap_visibility.settings_visibility()
21400 != minimap_settings.minimap_enabled()
21401 {
21402 self.set_minimap_visibility(
21403 MinimapVisibility::for_mode(self.mode(), cx),
21404 window,
21405 cx,
21406 );
21407 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21408 minimap_entity.update(cx, |minimap_editor, cx| {
21409 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21410 })
21411 }
21412 }
21413 }
21414
21415 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21416 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21417 }) {
21418 if !inlay_splice.is_empty() {
21419 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21420 }
21421 self.refresh_colors_for_visible_range(None, window, cx);
21422 }
21423
21424 cx.notify();
21425 }
21426
21427 pub fn set_searchable(&mut self, searchable: bool) {
21428 self.searchable = searchable;
21429 }
21430
21431 pub fn searchable(&self) -> bool {
21432 self.searchable
21433 }
21434
21435 pub fn open_excerpts_in_split(
21436 &mut self,
21437 _: &OpenExcerptsSplit,
21438 window: &mut Window,
21439 cx: &mut Context<Self>,
21440 ) {
21441 self.open_excerpts_common(None, true, window, cx)
21442 }
21443
21444 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21445 self.open_excerpts_common(None, false, window, cx)
21446 }
21447
21448 fn open_excerpts_common(
21449 &mut self,
21450 jump_data: Option<JumpData>,
21451 split: bool,
21452 window: &mut Window,
21453 cx: &mut Context<Self>,
21454 ) {
21455 let Some(workspace) = self.workspace() else {
21456 cx.propagate();
21457 return;
21458 };
21459
21460 if self.buffer.read(cx).is_singleton() {
21461 cx.propagate();
21462 return;
21463 }
21464
21465 let mut new_selections_by_buffer = HashMap::default();
21466 match &jump_data {
21467 Some(JumpData::MultiBufferPoint {
21468 excerpt_id,
21469 position,
21470 anchor,
21471 line_offset_from_top,
21472 }) => {
21473 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21474 if let Some(buffer) = multi_buffer_snapshot
21475 .buffer_id_for_excerpt(*excerpt_id)
21476 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21477 {
21478 let buffer_snapshot = buffer.read(cx).snapshot();
21479 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21480 language::ToPoint::to_point(anchor, &buffer_snapshot)
21481 } else {
21482 buffer_snapshot.clip_point(*position, Bias::Left)
21483 };
21484 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21485 new_selections_by_buffer.insert(
21486 buffer,
21487 (
21488 vec![jump_to_offset..jump_to_offset],
21489 Some(*line_offset_from_top),
21490 ),
21491 );
21492 }
21493 }
21494 Some(JumpData::MultiBufferRow {
21495 row,
21496 line_offset_from_top,
21497 }) => {
21498 let point = MultiBufferPoint::new(row.0, 0);
21499 if let Some((buffer, buffer_point, _)) =
21500 self.buffer.read(cx).point_to_buffer_point(point, cx)
21501 {
21502 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21503 new_selections_by_buffer
21504 .entry(buffer)
21505 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21506 .0
21507 .push(buffer_offset..buffer_offset)
21508 }
21509 }
21510 None => {
21511 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21512 let multi_buffer = self.buffer.read(cx);
21513 for selection in selections {
21514 for (snapshot, range, _, anchor) in multi_buffer
21515 .snapshot(cx)
21516 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21517 {
21518 if let Some(anchor) = anchor {
21519 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21520 else {
21521 continue;
21522 };
21523 let offset = text::ToOffset::to_offset(
21524 &anchor.text_anchor,
21525 &buffer_handle.read(cx).snapshot(),
21526 );
21527 let range = offset..offset;
21528 new_selections_by_buffer
21529 .entry(buffer_handle)
21530 .or_insert((Vec::new(), None))
21531 .0
21532 .push(range)
21533 } else {
21534 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21535 else {
21536 continue;
21537 };
21538 new_selections_by_buffer
21539 .entry(buffer_handle)
21540 .or_insert((Vec::new(), None))
21541 .0
21542 .push(range)
21543 }
21544 }
21545 }
21546 }
21547 }
21548
21549 new_selections_by_buffer
21550 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21551
21552 if new_selections_by_buffer.is_empty() {
21553 return;
21554 }
21555
21556 // We defer the pane interaction because we ourselves are a workspace item
21557 // and activating a new item causes the pane to call a method on us reentrantly,
21558 // which panics if we're on the stack.
21559 window.defer(cx, move |window, cx| {
21560 workspace.update(cx, |workspace, cx| {
21561 let pane = if split {
21562 workspace.adjacent_pane(window, cx)
21563 } else {
21564 workspace.active_pane().clone()
21565 };
21566
21567 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21568 let editor = buffer
21569 .read(cx)
21570 .file()
21571 .is_none()
21572 .then(|| {
21573 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21574 // so `workspace.open_project_item` will never find them, always opening a new editor.
21575 // Instead, we try to activate the existing editor in the pane first.
21576 let (editor, pane_item_index) =
21577 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21578 let editor = item.downcast::<Editor>()?;
21579 let singleton_buffer =
21580 editor.read(cx).buffer().read(cx).as_singleton()?;
21581 if singleton_buffer == buffer {
21582 Some((editor, i))
21583 } else {
21584 None
21585 }
21586 })?;
21587 pane.update(cx, |pane, cx| {
21588 pane.activate_item(pane_item_index, true, true, window, cx)
21589 });
21590 Some(editor)
21591 })
21592 .flatten()
21593 .unwrap_or_else(|| {
21594 workspace.open_project_item::<Self>(
21595 pane.clone(),
21596 buffer,
21597 true,
21598 true,
21599 window,
21600 cx,
21601 )
21602 });
21603
21604 editor.update(cx, |editor, cx| {
21605 let autoscroll = match scroll_offset {
21606 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21607 None => Autoscroll::newest(),
21608 };
21609 let nav_history = editor.nav_history.take();
21610 editor.change_selections(
21611 SelectionEffects::scroll(autoscroll),
21612 window,
21613 cx,
21614 |s| {
21615 s.select_ranges(ranges);
21616 },
21617 );
21618 editor.nav_history = nav_history;
21619 });
21620 }
21621 })
21622 });
21623 }
21624
21625 // For now, don't allow opening excerpts in buffers that aren't backed by
21626 // regular project files.
21627 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21628 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21629 }
21630
21631 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21632 let snapshot = self.buffer.read(cx).read(cx);
21633 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21634 Some(
21635 ranges
21636 .iter()
21637 .map(move |range| {
21638 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21639 })
21640 .collect(),
21641 )
21642 }
21643
21644 fn selection_replacement_ranges(
21645 &self,
21646 range: Range<OffsetUtf16>,
21647 cx: &mut App,
21648 ) -> Vec<Range<OffsetUtf16>> {
21649 let selections = self
21650 .selections
21651 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21652 let newest_selection = selections
21653 .iter()
21654 .max_by_key(|selection| selection.id)
21655 .unwrap();
21656 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21657 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21658 let snapshot = self.buffer.read(cx).read(cx);
21659 selections
21660 .into_iter()
21661 .map(|mut selection| {
21662 selection.start.0 =
21663 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21664 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21665 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21666 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21667 })
21668 .collect()
21669 }
21670
21671 fn report_editor_event(
21672 &self,
21673 reported_event: ReportEditorEvent,
21674 file_extension: Option<String>,
21675 cx: &App,
21676 ) {
21677 if cfg!(any(test, feature = "test-support")) {
21678 return;
21679 }
21680
21681 let Some(project) = &self.project else { return };
21682
21683 // If None, we are in a file without an extension
21684 let file = self
21685 .buffer
21686 .read(cx)
21687 .as_singleton()
21688 .and_then(|b| b.read(cx).file());
21689 let file_extension = file_extension.or(file
21690 .as_ref()
21691 .and_then(|file| Path::new(file.file_name(cx)).extension())
21692 .and_then(|e| e.to_str())
21693 .map(|a| a.to_string()));
21694
21695 let vim_mode = vim_flavor(cx).is_some();
21696
21697 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21698 let copilot_enabled = edit_predictions_provider
21699 == language::language_settings::EditPredictionProvider::Copilot;
21700 let copilot_enabled_for_language = self
21701 .buffer
21702 .read(cx)
21703 .language_settings(cx)
21704 .show_edit_predictions;
21705
21706 let project = project.read(cx);
21707 let event_type = reported_event.event_type();
21708
21709 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21710 telemetry::event!(
21711 event_type,
21712 type = if auto_saved {"autosave"} else {"manual"},
21713 file_extension,
21714 vim_mode,
21715 copilot_enabled,
21716 copilot_enabled_for_language,
21717 edit_predictions_provider,
21718 is_via_ssh = project.is_via_remote_server(),
21719 );
21720 } else {
21721 telemetry::event!(
21722 event_type,
21723 file_extension,
21724 vim_mode,
21725 copilot_enabled,
21726 copilot_enabled_for_language,
21727 edit_predictions_provider,
21728 is_via_ssh = project.is_via_remote_server(),
21729 );
21730 };
21731 }
21732
21733 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21734 /// with each line being an array of {text, highlight} objects.
21735 fn copy_highlight_json(
21736 &mut self,
21737 _: &CopyHighlightJson,
21738 window: &mut Window,
21739 cx: &mut Context<Self>,
21740 ) {
21741 #[derive(Serialize)]
21742 struct Chunk<'a> {
21743 text: String,
21744 highlight: Option<&'a str>,
21745 }
21746
21747 let snapshot = self.buffer.read(cx).snapshot(cx);
21748 let range = self
21749 .selected_text_range(false, window, cx)
21750 .and_then(|selection| {
21751 if selection.range.is_empty() {
21752 None
21753 } else {
21754 Some(
21755 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21756 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21757 )
21758 }
21759 })
21760 .unwrap_or_else(|| 0..snapshot.len());
21761
21762 let chunks = snapshot.chunks(range, true);
21763 let mut lines = Vec::new();
21764 let mut line: VecDeque<Chunk> = VecDeque::new();
21765
21766 let Some(style) = self.style.as_ref() else {
21767 return;
21768 };
21769
21770 for chunk in chunks {
21771 let highlight = chunk
21772 .syntax_highlight_id
21773 .and_then(|id| id.name(&style.syntax));
21774 let mut chunk_lines = chunk.text.split('\n').peekable();
21775 while let Some(text) = chunk_lines.next() {
21776 let mut merged_with_last_token = false;
21777 if let Some(last_token) = line.back_mut()
21778 && last_token.highlight == highlight
21779 {
21780 last_token.text.push_str(text);
21781 merged_with_last_token = true;
21782 }
21783
21784 if !merged_with_last_token {
21785 line.push_back(Chunk {
21786 text: text.into(),
21787 highlight,
21788 });
21789 }
21790
21791 if chunk_lines.peek().is_some() {
21792 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21793 line.pop_front();
21794 }
21795 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21796 line.pop_back();
21797 }
21798
21799 lines.push(mem::take(&mut line));
21800 }
21801 }
21802 }
21803
21804 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21805 return;
21806 };
21807 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21808 }
21809
21810 pub fn open_context_menu(
21811 &mut self,
21812 _: &OpenContextMenu,
21813 window: &mut Window,
21814 cx: &mut Context<Self>,
21815 ) {
21816 self.request_autoscroll(Autoscroll::newest(), cx);
21817 let position = self
21818 .selections
21819 .newest_display(&self.display_snapshot(cx))
21820 .start;
21821 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21822 }
21823
21824 pub fn replay_insert_event(
21825 &mut self,
21826 text: &str,
21827 relative_utf16_range: Option<Range<isize>>,
21828 window: &mut Window,
21829 cx: &mut Context<Self>,
21830 ) {
21831 if !self.input_enabled {
21832 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21833 return;
21834 }
21835 if let Some(relative_utf16_range) = relative_utf16_range {
21836 let selections = self
21837 .selections
21838 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21839 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21840 let new_ranges = selections.into_iter().map(|range| {
21841 let start = OffsetUtf16(
21842 range
21843 .head()
21844 .0
21845 .saturating_add_signed(relative_utf16_range.start),
21846 );
21847 let end = OffsetUtf16(
21848 range
21849 .head()
21850 .0
21851 .saturating_add_signed(relative_utf16_range.end),
21852 );
21853 start..end
21854 });
21855 s.select_ranges(new_ranges);
21856 });
21857 }
21858
21859 self.handle_input(text, window, cx);
21860 }
21861
21862 pub fn is_focused(&self, window: &Window) -> bool {
21863 self.focus_handle.is_focused(window)
21864 }
21865
21866 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21867 cx.emit(EditorEvent::Focused);
21868
21869 if let Some(descendant) = self
21870 .last_focused_descendant
21871 .take()
21872 .and_then(|descendant| descendant.upgrade())
21873 {
21874 window.focus(&descendant);
21875 } else {
21876 if let Some(blame) = self.blame.as_ref() {
21877 blame.update(cx, GitBlame::focus)
21878 }
21879
21880 self.blink_manager.update(cx, BlinkManager::enable);
21881 self.show_cursor_names(window, cx);
21882 self.buffer.update(cx, |buffer, cx| {
21883 buffer.finalize_last_transaction(cx);
21884 if self.leader_id.is_none() {
21885 buffer.set_active_selections(
21886 &self.selections.disjoint_anchors_arc(),
21887 self.selections.line_mode(),
21888 self.cursor_shape,
21889 cx,
21890 );
21891 }
21892 });
21893 }
21894 }
21895
21896 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21897 cx.emit(EditorEvent::FocusedIn)
21898 }
21899
21900 fn handle_focus_out(
21901 &mut self,
21902 event: FocusOutEvent,
21903 _window: &mut Window,
21904 cx: &mut Context<Self>,
21905 ) {
21906 if event.blurred != self.focus_handle {
21907 self.last_focused_descendant = Some(event.blurred);
21908 }
21909 self.selection_drag_state = SelectionDragState::None;
21910 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21911 }
21912
21913 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21914 self.blink_manager.update(cx, BlinkManager::disable);
21915 self.buffer
21916 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21917
21918 if let Some(blame) = self.blame.as_ref() {
21919 blame.update(cx, GitBlame::blur)
21920 }
21921 if !self.hover_state.focused(window, cx) {
21922 hide_hover(self, cx);
21923 }
21924 if !self
21925 .context_menu
21926 .borrow()
21927 .as_ref()
21928 .is_some_and(|context_menu| context_menu.focused(window, cx))
21929 {
21930 self.hide_context_menu(window, cx);
21931 }
21932 self.take_active_edit_prediction(cx);
21933 cx.emit(EditorEvent::Blurred);
21934 cx.notify();
21935 }
21936
21937 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21938 let mut pending: String = window
21939 .pending_input_keystrokes()
21940 .into_iter()
21941 .flatten()
21942 .filter_map(|keystroke| {
21943 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21944 keystroke.key_char.clone()
21945 } else {
21946 None
21947 }
21948 })
21949 .collect();
21950
21951 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21952 pending = "".to_string();
21953 }
21954
21955 let existing_pending = self
21956 .text_highlights::<PendingInput>(cx)
21957 .map(|(_, ranges)| ranges.to_vec());
21958 if existing_pending.is_none() && pending.is_empty() {
21959 return;
21960 }
21961 let transaction =
21962 self.transact(window, cx, |this, window, cx| {
21963 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21964 let edits = selections
21965 .iter()
21966 .map(|selection| (selection.end..selection.end, pending.clone()));
21967 this.edit(edits, cx);
21968 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21969 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21970 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21971 }));
21972 });
21973 if let Some(existing_ranges) = existing_pending {
21974 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21975 this.edit(edits, cx);
21976 }
21977 });
21978
21979 let snapshot = self.snapshot(window, cx);
21980 let ranges = self
21981 .selections
21982 .all::<usize>(&snapshot.display_snapshot)
21983 .into_iter()
21984 .map(|selection| {
21985 snapshot.buffer_snapshot().anchor_after(selection.end)
21986 ..snapshot
21987 .buffer_snapshot()
21988 .anchor_before(selection.end + pending.len())
21989 })
21990 .collect();
21991
21992 if pending.is_empty() {
21993 self.clear_highlights::<PendingInput>(cx);
21994 } else {
21995 self.highlight_text::<PendingInput>(
21996 ranges,
21997 HighlightStyle {
21998 underline: Some(UnderlineStyle {
21999 thickness: px(1.),
22000 color: None,
22001 wavy: false,
22002 }),
22003 ..Default::default()
22004 },
22005 cx,
22006 );
22007 }
22008
22009 self.ime_transaction = self.ime_transaction.or(transaction);
22010 if let Some(transaction) = self.ime_transaction {
22011 self.buffer.update(cx, |buffer, cx| {
22012 buffer.group_until_transaction(transaction, cx);
22013 });
22014 }
22015
22016 if self.text_highlights::<PendingInput>(cx).is_none() {
22017 self.ime_transaction.take();
22018 }
22019 }
22020
22021 pub fn register_action_renderer(
22022 &mut self,
22023 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22024 ) -> Subscription {
22025 let id = self.next_editor_action_id.post_inc();
22026 self.editor_actions
22027 .borrow_mut()
22028 .insert(id, Box::new(listener));
22029
22030 let editor_actions = self.editor_actions.clone();
22031 Subscription::new(move || {
22032 editor_actions.borrow_mut().remove(&id);
22033 })
22034 }
22035
22036 pub fn register_action<A: Action>(
22037 &mut self,
22038 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22039 ) -> Subscription {
22040 let id = self.next_editor_action_id.post_inc();
22041 let listener = Arc::new(listener);
22042 self.editor_actions.borrow_mut().insert(
22043 id,
22044 Box::new(move |_, window, _| {
22045 let listener = listener.clone();
22046 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22047 let action = action.downcast_ref().unwrap();
22048 if phase == DispatchPhase::Bubble {
22049 listener(action, window, cx)
22050 }
22051 })
22052 }),
22053 );
22054
22055 let editor_actions = self.editor_actions.clone();
22056 Subscription::new(move || {
22057 editor_actions.borrow_mut().remove(&id);
22058 })
22059 }
22060
22061 pub fn file_header_size(&self) -> u32 {
22062 FILE_HEADER_HEIGHT
22063 }
22064
22065 pub fn restore(
22066 &mut self,
22067 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22068 window: &mut Window,
22069 cx: &mut Context<Self>,
22070 ) {
22071 let workspace = self.workspace();
22072 let project = self.project();
22073 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22074 let mut tasks = Vec::new();
22075 for (buffer_id, changes) in revert_changes {
22076 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22077 buffer.update(cx, |buffer, cx| {
22078 buffer.edit(
22079 changes
22080 .into_iter()
22081 .map(|(range, text)| (range, text.to_string())),
22082 None,
22083 cx,
22084 );
22085 });
22086
22087 if let Some(project) =
22088 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22089 {
22090 project.update(cx, |project, cx| {
22091 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22092 })
22093 }
22094 }
22095 }
22096 tasks
22097 });
22098 cx.spawn_in(window, async move |_, cx| {
22099 for (buffer, task) in save_tasks {
22100 let result = task.await;
22101 if result.is_err() {
22102 let Some(path) = buffer
22103 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22104 .ok()
22105 else {
22106 continue;
22107 };
22108 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22109 let Some(task) = cx
22110 .update_window_entity(workspace, |workspace, window, cx| {
22111 workspace
22112 .open_path_preview(path, None, false, false, false, window, cx)
22113 })
22114 .ok()
22115 else {
22116 continue;
22117 };
22118 task.await.log_err();
22119 }
22120 }
22121 }
22122 })
22123 .detach();
22124 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22125 selections.refresh()
22126 });
22127 }
22128
22129 pub fn to_pixel_point(
22130 &self,
22131 source: multi_buffer::Anchor,
22132 editor_snapshot: &EditorSnapshot,
22133 window: &mut Window,
22134 ) -> Option<gpui::Point<Pixels>> {
22135 let source_point = source.to_display_point(editor_snapshot);
22136 self.display_to_pixel_point(source_point, editor_snapshot, window)
22137 }
22138
22139 pub fn display_to_pixel_point(
22140 &self,
22141 source: DisplayPoint,
22142 editor_snapshot: &EditorSnapshot,
22143 window: &mut Window,
22144 ) -> Option<gpui::Point<Pixels>> {
22145 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22146 let text_layout_details = self.text_layout_details(window);
22147 let scroll_top = text_layout_details
22148 .scroll_anchor
22149 .scroll_position(editor_snapshot)
22150 .y;
22151
22152 if source.row().as_f64() < scroll_top.floor() {
22153 return None;
22154 }
22155 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22156 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22157 Some(gpui::Point::new(source_x, source_y))
22158 }
22159
22160 pub fn has_visible_completions_menu(&self) -> bool {
22161 !self.edit_prediction_preview_is_active()
22162 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22163 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22164 })
22165 }
22166
22167 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22168 if self.mode.is_minimap() {
22169 return;
22170 }
22171 self.addons
22172 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22173 }
22174
22175 pub fn unregister_addon<T: Addon>(&mut self) {
22176 self.addons.remove(&std::any::TypeId::of::<T>());
22177 }
22178
22179 pub fn addon<T: Addon>(&self) -> Option<&T> {
22180 let type_id = std::any::TypeId::of::<T>();
22181 self.addons
22182 .get(&type_id)
22183 .and_then(|item| item.to_any().downcast_ref::<T>())
22184 }
22185
22186 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22187 let type_id = std::any::TypeId::of::<T>();
22188 self.addons
22189 .get_mut(&type_id)
22190 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22191 }
22192
22193 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22194 let text_layout_details = self.text_layout_details(window);
22195 let style = &text_layout_details.editor_style;
22196 let font_id = window.text_system().resolve_font(&style.text.font());
22197 let font_size = style.text.font_size.to_pixels(window.rem_size());
22198 let line_height = style.text.line_height_in_pixels(window.rem_size());
22199 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22200 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22201
22202 CharacterDimensions {
22203 em_width,
22204 em_advance,
22205 line_height,
22206 }
22207 }
22208
22209 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22210 self.load_diff_task.clone()
22211 }
22212
22213 fn read_metadata_from_db(
22214 &mut self,
22215 item_id: u64,
22216 workspace_id: WorkspaceId,
22217 window: &mut Window,
22218 cx: &mut Context<Editor>,
22219 ) {
22220 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22221 && !self.mode.is_minimap()
22222 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22223 {
22224 let buffer_snapshot = OnceCell::new();
22225
22226 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22227 && !folds.is_empty()
22228 {
22229 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22230 self.fold_ranges(
22231 folds
22232 .into_iter()
22233 .map(|(start, end)| {
22234 snapshot.clip_offset(start, Bias::Left)
22235 ..snapshot.clip_offset(end, Bias::Right)
22236 })
22237 .collect(),
22238 false,
22239 window,
22240 cx,
22241 );
22242 }
22243
22244 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22245 && !selections.is_empty()
22246 {
22247 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22248 // skip adding the initial selection to selection history
22249 self.selection_history.mode = SelectionHistoryMode::Skipping;
22250 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22251 s.select_ranges(selections.into_iter().map(|(start, end)| {
22252 snapshot.clip_offset(start, Bias::Left)
22253 ..snapshot.clip_offset(end, Bias::Right)
22254 }));
22255 });
22256 self.selection_history.mode = SelectionHistoryMode::Normal;
22257 };
22258 }
22259
22260 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22261 }
22262
22263 fn update_lsp_data(
22264 &mut self,
22265 for_buffer: Option<BufferId>,
22266 window: &mut Window,
22267 cx: &mut Context<'_, Self>,
22268 ) {
22269 self.pull_diagnostics(for_buffer, window, cx);
22270 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22271 }
22272
22273 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22274 if self.ignore_lsp_data() {
22275 return;
22276 }
22277 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22278 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22279 }
22280 }
22281
22282 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22283 if self.ignore_lsp_data() {
22284 return;
22285 }
22286
22287 if !self.registered_buffers.contains_key(&buffer_id)
22288 && let Some(project) = self.project.as_ref()
22289 {
22290 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22291 project.update(cx, |project, cx| {
22292 self.registered_buffers.insert(
22293 buffer_id,
22294 project.register_buffer_with_language_servers(&buffer, cx),
22295 );
22296 });
22297 } else {
22298 self.registered_buffers.remove(&buffer_id);
22299 }
22300 }
22301 }
22302
22303 fn ignore_lsp_data(&self) -> bool {
22304 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22305 // skip any LSP updates for it.
22306 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22307 }
22308}
22309
22310fn edit_for_markdown_paste<'a>(
22311 buffer: &MultiBufferSnapshot,
22312 range: Range<usize>,
22313 to_insert: &'a str,
22314 url: Option<url::Url>,
22315) -> (Range<usize>, Cow<'a, str>) {
22316 if url.is_none() {
22317 return (range, Cow::Borrowed(to_insert));
22318 };
22319
22320 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22321
22322 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22323 Cow::Borrowed(to_insert)
22324 } else {
22325 Cow::Owned(format!("[{old_text}]({to_insert})"))
22326 };
22327 (range, new_text)
22328}
22329
22330#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
22331pub enum VimFlavor {
22332 Vim,
22333 Helix,
22334}
22335
22336pub fn vim_flavor(cx: &App) -> Option<VimFlavor> {
22337 if vim_mode_setting::HelixModeSetting::try_get(cx)
22338 .map(|helix_mode| helix_mode.0)
22339 .unwrap_or(false)
22340 {
22341 Some(VimFlavor::Helix)
22342 } else if vim_mode_setting::VimModeSetting::try_get(cx)
22343 .map(|vim_mode| vim_mode.0)
22344 .unwrap_or(false)
22345 {
22346 Some(VimFlavor::Vim)
22347 } else {
22348 None // neither vim nor helix mode
22349 }
22350}
22351
22352fn process_completion_for_edit(
22353 completion: &Completion,
22354 intent: CompletionIntent,
22355 buffer: &Entity<Buffer>,
22356 cursor_position: &text::Anchor,
22357 cx: &mut Context<Editor>,
22358) -> CompletionEdit {
22359 let buffer = buffer.read(cx);
22360 let buffer_snapshot = buffer.snapshot();
22361 let (snippet, new_text) = if completion.is_snippet() {
22362 let mut snippet_source = completion.new_text.clone();
22363 // Workaround for typescript language server issues so that methods don't expand within
22364 // strings and functions with type expressions. The previous point is used because the query
22365 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22366 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22367 let previous_point = if previous_point.column > 0 {
22368 cursor_position.to_previous_offset(&buffer_snapshot)
22369 } else {
22370 cursor_position.to_offset(&buffer_snapshot)
22371 };
22372 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22373 && scope.prefers_label_for_snippet_in_completion()
22374 && let Some(label) = completion.label()
22375 && matches!(
22376 completion.kind(),
22377 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22378 )
22379 {
22380 snippet_source = label;
22381 }
22382 match Snippet::parse(&snippet_source).log_err() {
22383 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22384 None => (None, completion.new_text.clone()),
22385 }
22386 } else {
22387 (None, completion.new_text.clone())
22388 };
22389
22390 let mut range_to_replace = {
22391 let replace_range = &completion.replace_range;
22392 if let CompletionSource::Lsp {
22393 insert_range: Some(insert_range),
22394 ..
22395 } = &completion.source
22396 {
22397 debug_assert_eq!(
22398 insert_range.start, replace_range.start,
22399 "insert_range and replace_range should start at the same position"
22400 );
22401 debug_assert!(
22402 insert_range
22403 .start
22404 .cmp(cursor_position, &buffer_snapshot)
22405 .is_le(),
22406 "insert_range should start before or at cursor position"
22407 );
22408 debug_assert!(
22409 replace_range
22410 .start
22411 .cmp(cursor_position, &buffer_snapshot)
22412 .is_le(),
22413 "replace_range should start before or at cursor position"
22414 );
22415
22416 let should_replace = match intent {
22417 CompletionIntent::CompleteWithInsert => false,
22418 CompletionIntent::CompleteWithReplace => true,
22419 CompletionIntent::Complete | CompletionIntent::Compose => {
22420 let insert_mode =
22421 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22422 .completions
22423 .lsp_insert_mode;
22424 match insert_mode {
22425 LspInsertMode::Insert => false,
22426 LspInsertMode::Replace => true,
22427 LspInsertMode::ReplaceSubsequence => {
22428 let mut text_to_replace = buffer.chars_for_range(
22429 buffer.anchor_before(replace_range.start)
22430 ..buffer.anchor_after(replace_range.end),
22431 );
22432 let mut current_needle = text_to_replace.next();
22433 for haystack_ch in completion.label.text.chars() {
22434 if let Some(needle_ch) = current_needle
22435 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22436 {
22437 current_needle = text_to_replace.next();
22438 }
22439 }
22440 current_needle.is_none()
22441 }
22442 LspInsertMode::ReplaceSuffix => {
22443 if replace_range
22444 .end
22445 .cmp(cursor_position, &buffer_snapshot)
22446 .is_gt()
22447 {
22448 let range_after_cursor = *cursor_position..replace_range.end;
22449 let text_after_cursor = buffer
22450 .text_for_range(
22451 buffer.anchor_before(range_after_cursor.start)
22452 ..buffer.anchor_after(range_after_cursor.end),
22453 )
22454 .collect::<String>()
22455 .to_ascii_lowercase();
22456 completion
22457 .label
22458 .text
22459 .to_ascii_lowercase()
22460 .ends_with(&text_after_cursor)
22461 } else {
22462 true
22463 }
22464 }
22465 }
22466 }
22467 };
22468
22469 if should_replace {
22470 replace_range.clone()
22471 } else {
22472 insert_range.clone()
22473 }
22474 } else {
22475 replace_range.clone()
22476 }
22477 };
22478
22479 if range_to_replace
22480 .end
22481 .cmp(cursor_position, &buffer_snapshot)
22482 .is_lt()
22483 {
22484 range_to_replace.end = *cursor_position;
22485 }
22486
22487 CompletionEdit {
22488 new_text,
22489 replace_range: range_to_replace.to_offset(buffer),
22490 snippet,
22491 }
22492}
22493
22494struct CompletionEdit {
22495 new_text: String,
22496 replace_range: Range<usize>,
22497 snippet: Option<Snippet>,
22498}
22499
22500fn insert_extra_newline_brackets(
22501 buffer: &MultiBufferSnapshot,
22502 range: Range<usize>,
22503 language: &language::LanguageScope,
22504) -> bool {
22505 let leading_whitespace_len = buffer
22506 .reversed_chars_at(range.start)
22507 .take_while(|c| c.is_whitespace() && *c != '\n')
22508 .map(|c| c.len_utf8())
22509 .sum::<usize>();
22510 let trailing_whitespace_len = buffer
22511 .chars_at(range.end)
22512 .take_while(|c| c.is_whitespace() && *c != '\n')
22513 .map(|c| c.len_utf8())
22514 .sum::<usize>();
22515 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22516
22517 language.brackets().any(|(pair, enabled)| {
22518 let pair_start = pair.start.trim_end();
22519 let pair_end = pair.end.trim_start();
22520
22521 enabled
22522 && pair.newline
22523 && buffer.contains_str_at(range.end, pair_end)
22524 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22525 })
22526}
22527
22528fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22529 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22530 [(buffer, range, _)] => (*buffer, range.clone()),
22531 _ => return false,
22532 };
22533 let pair = {
22534 let mut result: Option<BracketMatch> = None;
22535
22536 for pair in buffer
22537 .all_bracket_ranges(range.clone())
22538 .filter(move |pair| {
22539 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22540 })
22541 {
22542 let len = pair.close_range.end - pair.open_range.start;
22543
22544 if let Some(existing) = &result {
22545 let existing_len = existing.close_range.end - existing.open_range.start;
22546 if len > existing_len {
22547 continue;
22548 }
22549 }
22550
22551 result = Some(pair);
22552 }
22553
22554 result
22555 };
22556 let Some(pair) = pair else {
22557 return false;
22558 };
22559 pair.newline_only
22560 && buffer
22561 .chars_for_range(pair.open_range.end..range.start)
22562 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22563 .all(|c| c.is_whitespace() && c != '\n')
22564}
22565
22566fn update_uncommitted_diff_for_buffer(
22567 editor: Entity<Editor>,
22568 project: &Entity<Project>,
22569 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22570 buffer: Entity<MultiBuffer>,
22571 cx: &mut App,
22572) -> Task<()> {
22573 let mut tasks = Vec::new();
22574 project.update(cx, |project, cx| {
22575 for buffer in buffers {
22576 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22577 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22578 }
22579 }
22580 });
22581 cx.spawn(async move |cx| {
22582 let diffs = future::join_all(tasks).await;
22583 if editor
22584 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22585 .unwrap_or(false)
22586 {
22587 return;
22588 }
22589
22590 buffer
22591 .update(cx, |buffer, cx| {
22592 for diff in diffs.into_iter().flatten() {
22593 buffer.add_diff(diff, cx);
22594 }
22595 })
22596 .ok();
22597 })
22598}
22599
22600fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22601 let tab_size = tab_size.get() as usize;
22602 let mut width = offset;
22603
22604 for ch in text.chars() {
22605 width += if ch == '\t' {
22606 tab_size - (width % tab_size)
22607 } else {
22608 1
22609 };
22610 }
22611
22612 width - offset
22613}
22614
22615#[cfg(test)]
22616mod tests {
22617 use super::*;
22618
22619 #[test]
22620 fn test_string_size_with_expanded_tabs() {
22621 let nz = |val| NonZeroU32::new(val).unwrap();
22622 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22623 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22624 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22625 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22626 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22627 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22628 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22629 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22630 }
22631}
22632
22633/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22634struct WordBreakingTokenizer<'a> {
22635 input: &'a str,
22636}
22637
22638impl<'a> WordBreakingTokenizer<'a> {
22639 fn new(input: &'a str) -> Self {
22640 Self { input }
22641 }
22642}
22643
22644fn is_char_ideographic(ch: char) -> bool {
22645 use unicode_script::Script::*;
22646 use unicode_script::UnicodeScript;
22647 matches!(ch.script(), Han | Tangut | Yi)
22648}
22649
22650fn is_grapheme_ideographic(text: &str) -> bool {
22651 text.chars().any(is_char_ideographic)
22652}
22653
22654fn is_grapheme_whitespace(text: &str) -> bool {
22655 text.chars().any(|x| x.is_whitespace())
22656}
22657
22658fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22659 text.chars()
22660 .next()
22661 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22662}
22663
22664#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22665enum WordBreakToken<'a> {
22666 Word { token: &'a str, grapheme_len: usize },
22667 InlineWhitespace { token: &'a str, grapheme_len: usize },
22668 Newline,
22669}
22670
22671impl<'a> Iterator for WordBreakingTokenizer<'a> {
22672 /// Yields a span, the count of graphemes in the token, and whether it was
22673 /// whitespace. Note that it also breaks at word boundaries.
22674 type Item = WordBreakToken<'a>;
22675
22676 fn next(&mut self) -> Option<Self::Item> {
22677 use unicode_segmentation::UnicodeSegmentation;
22678 if self.input.is_empty() {
22679 return None;
22680 }
22681
22682 let mut iter = self.input.graphemes(true).peekable();
22683 let mut offset = 0;
22684 let mut grapheme_len = 0;
22685 if let Some(first_grapheme) = iter.next() {
22686 let is_newline = first_grapheme == "\n";
22687 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22688 offset += first_grapheme.len();
22689 grapheme_len += 1;
22690 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22691 if let Some(grapheme) = iter.peek().copied()
22692 && should_stay_with_preceding_ideograph(grapheme)
22693 {
22694 offset += grapheme.len();
22695 grapheme_len += 1;
22696 }
22697 } else {
22698 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22699 let mut next_word_bound = words.peek().copied();
22700 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22701 next_word_bound = words.next();
22702 }
22703 while let Some(grapheme) = iter.peek().copied() {
22704 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22705 break;
22706 };
22707 if is_grapheme_whitespace(grapheme) != is_whitespace
22708 || (grapheme == "\n") != is_newline
22709 {
22710 break;
22711 };
22712 offset += grapheme.len();
22713 grapheme_len += 1;
22714 iter.next();
22715 }
22716 }
22717 let token = &self.input[..offset];
22718 self.input = &self.input[offset..];
22719 if token == "\n" {
22720 Some(WordBreakToken::Newline)
22721 } else if is_whitespace {
22722 Some(WordBreakToken::InlineWhitespace {
22723 token,
22724 grapheme_len,
22725 })
22726 } else {
22727 Some(WordBreakToken::Word {
22728 token,
22729 grapheme_len,
22730 })
22731 }
22732 } else {
22733 None
22734 }
22735 }
22736}
22737
22738#[test]
22739fn test_word_breaking_tokenizer() {
22740 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22741 ("", &[]),
22742 (" ", &[whitespace(" ", 2)]),
22743 ("Ʒ", &[word("Ʒ", 1)]),
22744 ("Ǽ", &[word("Ǽ", 1)]),
22745 ("⋑", &[word("⋑", 1)]),
22746 ("⋑⋑", &[word("⋑⋑", 2)]),
22747 (
22748 "原理,进而",
22749 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22750 ),
22751 (
22752 "hello world",
22753 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22754 ),
22755 (
22756 "hello, world",
22757 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22758 ),
22759 (
22760 " hello world",
22761 &[
22762 whitespace(" ", 2),
22763 word("hello", 5),
22764 whitespace(" ", 1),
22765 word("world", 5),
22766 ],
22767 ),
22768 (
22769 "这是什么 \n 钢笔",
22770 &[
22771 word("这", 1),
22772 word("是", 1),
22773 word("什", 1),
22774 word("么", 1),
22775 whitespace(" ", 1),
22776 newline(),
22777 whitespace(" ", 1),
22778 word("钢", 1),
22779 word("笔", 1),
22780 ],
22781 ),
22782 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22783 ];
22784
22785 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22786 WordBreakToken::Word {
22787 token,
22788 grapheme_len,
22789 }
22790 }
22791
22792 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22793 WordBreakToken::InlineWhitespace {
22794 token,
22795 grapheme_len,
22796 }
22797 }
22798
22799 fn newline() -> WordBreakToken<'static> {
22800 WordBreakToken::Newline
22801 }
22802
22803 for (input, result) in tests {
22804 assert_eq!(
22805 WordBreakingTokenizer::new(input)
22806 .collect::<Vec<_>>()
22807 .as_slice(),
22808 *result,
22809 );
22810 }
22811}
22812
22813fn wrap_with_prefix(
22814 first_line_prefix: String,
22815 subsequent_lines_prefix: String,
22816 unwrapped_text: String,
22817 wrap_column: usize,
22818 tab_size: NonZeroU32,
22819 preserve_existing_whitespace: bool,
22820) -> String {
22821 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22822 let subsequent_lines_prefix_len =
22823 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22824 let mut wrapped_text = String::new();
22825 let mut current_line = first_line_prefix;
22826 let mut is_first_line = true;
22827
22828 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22829 let mut current_line_len = first_line_prefix_len;
22830 let mut in_whitespace = false;
22831 for token in tokenizer {
22832 let have_preceding_whitespace = in_whitespace;
22833 match token {
22834 WordBreakToken::Word {
22835 token,
22836 grapheme_len,
22837 } => {
22838 in_whitespace = false;
22839 let current_prefix_len = if is_first_line {
22840 first_line_prefix_len
22841 } else {
22842 subsequent_lines_prefix_len
22843 };
22844 if current_line_len + grapheme_len > wrap_column
22845 && current_line_len != current_prefix_len
22846 {
22847 wrapped_text.push_str(current_line.trim_end());
22848 wrapped_text.push('\n');
22849 is_first_line = false;
22850 current_line = subsequent_lines_prefix.clone();
22851 current_line_len = subsequent_lines_prefix_len;
22852 }
22853 current_line.push_str(token);
22854 current_line_len += grapheme_len;
22855 }
22856 WordBreakToken::InlineWhitespace {
22857 mut token,
22858 mut grapheme_len,
22859 } => {
22860 in_whitespace = true;
22861 if have_preceding_whitespace && !preserve_existing_whitespace {
22862 continue;
22863 }
22864 if !preserve_existing_whitespace {
22865 // Keep a single whitespace grapheme as-is
22866 if let Some(first) =
22867 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22868 {
22869 token = first;
22870 } else {
22871 token = " ";
22872 }
22873 grapheme_len = 1;
22874 }
22875 let current_prefix_len = if is_first_line {
22876 first_line_prefix_len
22877 } else {
22878 subsequent_lines_prefix_len
22879 };
22880 if current_line_len + grapheme_len > wrap_column {
22881 wrapped_text.push_str(current_line.trim_end());
22882 wrapped_text.push('\n');
22883 is_first_line = false;
22884 current_line = subsequent_lines_prefix.clone();
22885 current_line_len = subsequent_lines_prefix_len;
22886 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22887 current_line.push_str(token);
22888 current_line_len += grapheme_len;
22889 }
22890 }
22891 WordBreakToken::Newline => {
22892 in_whitespace = true;
22893 let current_prefix_len = if is_first_line {
22894 first_line_prefix_len
22895 } else {
22896 subsequent_lines_prefix_len
22897 };
22898 if preserve_existing_whitespace {
22899 wrapped_text.push_str(current_line.trim_end());
22900 wrapped_text.push('\n');
22901 is_first_line = false;
22902 current_line = subsequent_lines_prefix.clone();
22903 current_line_len = subsequent_lines_prefix_len;
22904 } else if have_preceding_whitespace {
22905 continue;
22906 } else if current_line_len + 1 > wrap_column
22907 && current_line_len != current_prefix_len
22908 {
22909 wrapped_text.push_str(current_line.trim_end());
22910 wrapped_text.push('\n');
22911 is_first_line = false;
22912 current_line = subsequent_lines_prefix.clone();
22913 current_line_len = subsequent_lines_prefix_len;
22914 } else if current_line_len != current_prefix_len {
22915 current_line.push(' ');
22916 current_line_len += 1;
22917 }
22918 }
22919 }
22920 }
22921
22922 if !current_line.is_empty() {
22923 wrapped_text.push_str(¤t_line);
22924 }
22925 wrapped_text
22926}
22927
22928#[test]
22929fn test_wrap_with_prefix() {
22930 assert_eq!(
22931 wrap_with_prefix(
22932 "# ".to_string(),
22933 "# ".to_string(),
22934 "abcdefg".to_string(),
22935 4,
22936 NonZeroU32::new(4).unwrap(),
22937 false,
22938 ),
22939 "# abcdefg"
22940 );
22941 assert_eq!(
22942 wrap_with_prefix(
22943 "".to_string(),
22944 "".to_string(),
22945 "\thello world".to_string(),
22946 8,
22947 NonZeroU32::new(4).unwrap(),
22948 false,
22949 ),
22950 "hello\nworld"
22951 );
22952 assert_eq!(
22953 wrap_with_prefix(
22954 "// ".to_string(),
22955 "// ".to_string(),
22956 "xx \nyy zz aa bb cc".to_string(),
22957 12,
22958 NonZeroU32::new(4).unwrap(),
22959 false,
22960 ),
22961 "// xx yy zz\n// aa bb cc"
22962 );
22963 assert_eq!(
22964 wrap_with_prefix(
22965 String::new(),
22966 String::new(),
22967 "这是什么 \n 钢笔".to_string(),
22968 3,
22969 NonZeroU32::new(4).unwrap(),
22970 false,
22971 ),
22972 "这是什\n么 钢\n笔"
22973 );
22974 assert_eq!(
22975 wrap_with_prefix(
22976 String::new(),
22977 String::new(),
22978 format!("foo{}bar", '\u{2009}'), // thin space
22979 80,
22980 NonZeroU32::new(4).unwrap(),
22981 false,
22982 ),
22983 format!("foo{}bar", '\u{2009}')
22984 );
22985}
22986
22987pub trait CollaborationHub {
22988 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22989 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22990 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22991}
22992
22993impl CollaborationHub for Entity<Project> {
22994 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22995 self.read(cx).collaborators()
22996 }
22997
22998 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22999 self.read(cx).user_store().read(cx).participant_indices()
23000 }
23001
23002 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23003 let this = self.read(cx);
23004 let user_ids = this.collaborators().values().map(|c| c.user_id);
23005 this.user_store().read(cx).participant_names(user_ids, cx)
23006 }
23007}
23008
23009pub trait SemanticsProvider {
23010 fn hover(
23011 &self,
23012 buffer: &Entity<Buffer>,
23013 position: text::Anchor,
23014 cx: &mut App,
23015 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23016
23017 fn inline_values(
23018 &self,
23019 buffer_handle: Entity<Buffer>,
23020 range: Range<text::Anchor>,
23021 cx: &mut App,
23022 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23023
23024 fn applicable_inlay_chunks(
23025 &self,
23026 buffer: &Entity<Buffer>,
23027 ranges: &[Range<text::Anchor>],
23028 cx: &mut App,
23029 ) -> Vec<Range<BufferRow>>;
23030
23031 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23032
23033 fn inlay_hints(
23034 &self,
23035 invalidate: InvalidationStrategy,
23036 buffer: Entity<Buffer>,
23037 ranges: Vec<Range<text::Anchor>>,
23038 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23039 cx: &mut App,
23040 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23041
23042 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23043
23044 fn document_highlights(
23045 &self,
23046 buffer: &Entity<Buffer>,
23047 position: text::Anchor,
23048 cx: &mut App,
23049 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23050
23051 fn definitions(
23052 &self,
23053 buffer: &Entity<Buffer>,
23054 position: text::Anchor,
23055 kind: GotoDefinitionKind,
23056 cx: &mut App,
23057 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23058
23059 fn range_for_rename(
23060 &self,
23061 buffer: &Entity<Buffer>,
23062 position: text::Anchor,
23063 cx: &mut App,
23064 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23065
23066 fn perform_rename(
23067 &self,
23068 buffer: &Entity<Buffer>,
23069 position: text::Anchor,
23070 new_name: String,
23071 cx: &mut App,
23072 ) -> Option<Task<Result<ProjectTransaction>>>;
23073}
23074
23075pub trait CompletionProvider {
23076 fn completions(
23077 &self,
23078 excerpt_id: ExcerptId,
23079 buffer: &Entity<Buffer>,
23080 buffer_position: text::Anchor,
23081 trigger: CompletionContext,
23082 window: &mut Window,
23083 cx: &mut Context<Editor>,
23084 ) -> Task<Result<Vec<CompletionResponse>>>;
23085
23086 fn resolve_completions(
23087 &self,
23088 _buffer: Entity<Buffer>,
23089 _completion_indices: Vec<usize>,
23090 _completions: Rc<RefCell<Box<[Completion]>>>,
23091 _cx: &mut Context<Editor>,
23092 ) -> Task<Result<bool>> {
23093 Task::ready(Ok(false))
23094 }
23095
23096 fn apply_additional_edits_for_completion(
23097 &self,
23098 _buffer: Entity<Buffer>,
23099 _completions: Rc<RefCell<Box<[Completion]>>>,
23100 _completion_index: usize,
23101 _push_to_history: bool,
23102 _cx: &mut Context<Editor>,
23103 ) -> Task<Result<Option<language::Transaction>>> {
23104 Task::ready(Ok(None))
23105 }
23106
23107 fn is_completion_trigger(
23108 &self,
23109 buffer: &Entity<Buffer>,
23110 position: language::Anchor,
23111 text: &str,
23112 trigger_in_words: bool,
23113 menu_is_open: bool,
23114 cx: &mut Context<Editor>,
23115 ) -> bool;
23116
23117 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23118
23119 fn sort_completions(&self) -> bool {
23120 true
23121 }
23122
23123 fn filter_completions(&self) -> bool {
23124 true
23125 }
23126
23127 fn show_snippets(&self) -> bool {
23128 false
23129 }
23130}
23131
23132pub trait CodeActionProvider {
23133 fn id(&self) -> Arc<str>;
23134
23135 fn code_actions(
23136 &self,
23137 buffer: &Entity<Buffer>,
23138 range: Range<text::Anchor>,
23139 window: &mut Window,
23140 cx: &mut App,
23141 ) -> Task<Result<Vec<CodeAction>>>;
23142
23143 fn apply_code_action(
23144 &self,
23145 buffer_handle: Entity<Buffer>,
23146 action: CodeAction,
23147 excerpt_id: ExcerptId,
23148 push_to_history: bool,
23149 window: &mut Window,
23150 cx: &mut App,
23151 ) -> Task<Result<ProjectTransaction>>;
23152}
23153
23154impl CodeActionProvider for Entity<Project> {
23155 fn id(&self) -> Arc<str> {
23156 "project".into()
23157 }
23158
23159 fn code_actions(
23160 &self,
23161 buffer: &Entity<Buffer>,
23162 range: Range<text::Anchor>,
23163 _window: &mut Window,
23164 cx: &mut App,
23165 ) -> Task<Result<Vec<CodeAction>>> {
23166 self.update(cx, |project, cx| {
23167 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23168 let code_actions = project.code_actions(buffer, range, None, cx);
23169 cx.background_spawn(async move {
23170 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23171 Ok(code_lens_actions
23172 .context("code lens fetch")?
23173 .into_iter()
23174 .flatten()
23175 .chain(
23176 code_actions
23177 .context("code action fetch")?
23178 .into_iter()
23179 .flatten(),
23180 )
23181 .collect())
23182 })
23183 })
23184 }
23185
23186 fn apply_code_action(
23187 &self,
23188 buffer_handle: Entity<Buffer>,
23189 action: CodeAction,
23190 _excerpt_id: ExcerptId,
23191 push_to_history: bool,
23192 _window: &mut Window,
23193 cx: &mut App,
23194 ) -> Task<Result<ProjectTransaction>> {
23195 self.update(cx, |project, cx| {
23196 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23197 })
23198 }
23199}
23200
23201fn snippet_completions(
23202 project: &Project,
23203 buffer: &Entity<Buffer>,
23204 buffer_position: text::Anchor,
23205 cx: &mut App,
23206) -> Task<Result<CompletionResponse>> {
23207 let languages = buffer.read(cx).languages_at(buffer_position);
23208 let snippet_store = project.snippets().read(cx);
23209
23210 let scopes: Vec<_> = languages
23211 .iter()
23212 .filter_map(|language| {
23213 let language_name = language.lsp_id();
23214 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23215
23216 if snippets.is_empty() {
23217 None
23218 } else {
23219 Some((language.default_scope(), snippets))
23220 }
23221 })
23222 .collect();
23223
23224 if scopes.is_empty() {
23225 return Task::ready(Ok(CompletionResponse {
23226 completions: vec![],
23227 display_options: CompletionDisplayOptions::default(),
23228 is_incomplete: false,
23229 }));
23230 }
23231
23232 let snapshot = buffer.read(cx).text_snapshot();
23233 let executor = cx.background_executor().clone();
23234
23235 cx.background_spawn(async move {
23236 let mut is_incomplete = false;
23237 let mut completions: Vec<Completion> = Vec::new();
23238 for (scope, snippets) in scopes.into_iter() {
23239 let classifier =
23240 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
23241
23242 const MAX_WORD_PREFIX_LEN: usize = 128;
23243 let last_word: String = snapshot
23244 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
23245 .take(MAX_WORD_PREFIX_LEN)
23246 .take_while(|c| classifier.is_word(*c))
23247 .collect::<String>()
23248 .chars()
23249 .rev()
23250 .collect();
23251
23252 if last_word.is_empty() {
23253 return Ok(CompletionResponse {
23254 completions: vec![],
23255 display_options: CompletionDisplayOptions::default(),
23256 is_incomplete: true,
23257 });
23258 }
23259
23260 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
23261 let to_lsp = |point: &text::Anchor| {
23262 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23263 point_to_lsp(end)
23264 };
23265 let lsp_end = to_lsp(&buffer_position);
23266
23267 let candidates = snippets
23268 .iter()
23269 .enumerate()
23270 .flat_map(|(ix, snippet)| {
23271 snippet
23272 .prefix
23273 .iter()
23274 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23275 })
23276 .collect::<Vec<StringMatchCandidate>>();
23277
23278 const MAX_RESULTS: usize = 100;
23279 let mut matches = fuzzy::match_strings(
23280 &candidates,
23281 &last_word,
23282 last_word.chars().any(|c| c.is_uppercase()),
23283 true,
23284 MAX_RESULTS,
23285 &Default::default(),
23286 executor.clone(),
23287 )
23288 .await;
23289
23290 if matches.len() >= MAX_RESULTS {
23291 is_incomplete = true;
23292 }
23293
23294 // Remove all candidates where the query's start does not match the start of any word in the candidate
23295 if let Some(query_start) = last_word.chars().next() {
23296 matches.retain(|string_match| {
23297 split_words(&string_match.string).any(|word| {
23298 // Check that the first codepoint of the word as lowercase matches the first
23299 // codepoint of the query as lowercase
23300 word.chars()
23301 .flat_map(|codepoint| codepoint.to_lowercase())
23302 .zip(query_start.to_lowercase())
23303 .all(|(word_cp, query_cp)| word_cp == query_cp)
23304 })
23305 });
23306 }
23307
23308 let matched_strings = matches
23309 .into_iter()
23310 .map(|m| m.string)
23311 .collect::<HashSet<_>>();
23312
23313 completions.extend(snippets.iter().filter_map(|snippet| {
23314 let matching_prefix = snippet
23315 .prefix
23316 .iter()
23317 .find(|prefix| matched_strings.contains(*prefix))?;
23318 let start = as_offset - last_word.len();
23319 let start = snapshot.anchor_before(start);
23320 let range = start..buffer_position;
23321 let lsp_start = to_lsp(&start);
23322 let lsp_range = lsp::Range {
23323 start: lsp_start,
23324 end: lsp_end,
23325 };
23326 Some(Completion {
23327 replace_range: range,
23328 new_text: snippet.body.clone(),
23329 source: CompletionSource::Lsp {
23330 insert_range: None,
23331 server_id: LanguageServerId(usize::MAX),
23332 resolved: true,
23333 lsp_completion: Box::new(lsp::CompletionItem {
23334 label: snippet.prefix.first().unwrap().clone(),
23335 kind: Some(CompletionItemKind::SNIPPET),
23336 label_details: snippet.description.as_ref().map(|description| {
23337 lsp::CompletionItemLabelDetails {
23338 detail: Some(description.clone()),
23339 description: None,
23340 }
23341 }),
23342 insert_text_format: Some(InsertTextFormat::SNIPPET),
23343 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23344 lsp::InsertReplaceEdit {
23345 new_text: snippet.body.clone(),
23346 insert: lsp_range,
23347 replace: lsp_range,
23348 },
23349 )),
23350 filter_text: Some(snippet.body.clone()),
23351 sort_text: Some(char::MAX.to_string()),
23352 ..lsp::CompletionItem::default()
23353 }),
23354 lsp_defaults: None,
23355 },
23356 label: CodeLabel::plain(matching_prefix.clone(), None),
23357 icon_path: None,
23358 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23359 single_line: snippet.name.clone().into(),
23360 plain_text: snippet
23361 .description
23362 .clone()
23363 .map(|description| description.into()),
23364 }),
23365 insert_text_mode: None,
23366 confirm: None,
23367 })
23368 }))
23369 }
23370
23371 Ok(CompletionResponse {
23372 completions,
23373 display_options: CompletionDisplayOptions::default(),
23374 is_incomplete,
23375 })
23376 })
23377}
23378
23379impl CompletionProvider for Entity<Project> {
23380 fn completions(
23381 &self,
23382 _excerpt_id: ExcerptId,
23383 buffer: &Entity<Buffer>,
23384 buffer_position: text::Anchor,
23385 options: CompletionContext,
23386 _window: &mut Window,
23387 cx: &mut Context<Editor>,
23388 ) -> Task<Result<Vec<CompletionResponse>>> {
23389 self.update(cx, |project, cx| {
23390 let task = project.completions(buffer, buffer_position, options, cx);
23391 cx.background_spawn(task)
23392 })
23393 }
23394
23395 fn resolve_completions(
23396 &self,
23397 buffer: Entity<Buffer>,
23398 completion_indices: Vec<usize>,
23399 completions: Rc<RefCell<Box<[Completion]>>>,
23400 cx: &mut Context<Editor>,
23401 ) -> Task<Result<bool>> {
23402 self.update(cx, |project, cx| {
23403 project.lsp_store().update(cx, |lsp_store, cx| {
23404 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23405 })
23406 })
23407 }
23408
23409 fn apply_additional_edits_for_completion(
23410 &self,
23411 buffer: Entity<Buffer>,
23412 completions: Rc<RefCell<Box<[Completion]>>>,
23413 completion_index: usize,
23414 push_to_history: bool,
23415 cx: &mut Context<Editor>,
23416 ) -> Task<Result<Option<language::Transaction>>> {
23417 self.update(cx, |project, cx| {
23418 project.lsp_store().update(cx, |lsp_store, cx| {
23419 lsp_store.apply_additional_edits_for_completion(
23420 buffer,
23421 completions,
23422 completion_index,
23423 push_to_history,
23424 cx,
23425 )
23426 })
23427 })
23428 }
23429
23430 fn is_completion_trigger(
23431 &self,
23432 buffer: &Entity<Buffer>,
23433 position: language::Anchor,
23434 text: &str,
23435 trigger_in_words: bool,
23436 menu_is_open: bool,
23437 cx: &mut Context<Editor>,
23438 ) -> bool {
23439 let mut chars = text.chars();
23440 let char = if let Some(char) = chars.next() {
23441 char
23442 } else {
23443 return false;
23444 };
23445 if chars.next().is_some() {
23446 return false;
23447 }
23448
23449 let buffer = buffer.read(cx);
23450 let snapshot = buffer.snapshot();
23451 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23452 return false;
23453 }
23454 let classifier = snapshot
23455 .char_classifier_at(position)
23456 .scope_context(Some(CharScopeContext::Completion));
23457 if trigger_in_words && classifier.is_word(char) {
23458 return true;
23459 }
23460
23461 buffer.completion_triggers().contains(text)
23462 }
23463
23464 fn show_snippets(&self) -> bool {
23465 true
23466 }
23467}
23468
23469impl SemanticsProvider for Entity<Project> {
23470 fn hover(
23471 &self,
23472 buffer: &Entity<Buffer>,
23473 position: text::Anchor,
23474 cx: &mut App,
23475 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23476 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23477 }
23478
23479 fn document_highlights(
23480 &self,
23481 buffer: &Entity<Buffer>,
23482 position: text::Anchor,
23483 cx: &mut App,
23484 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23485 Some(self.update(cx, |project, cx| {
23486 project.document_highlights(buffer, position, cx)
23487 }))
23488 }
23489
23490 fn definitions(
23491 &self,
23492 buffer: &Entity<Buffer>,
23493 position: text::Anchor,
23494 kind: GotoDefinitionKind,
23495 cx: &mut App,
23496 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23497 Some(self.update(cx, |project, cx| match kind {
23498 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23499 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23500 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23501 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23502 }))
23503 }
23504
23505 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23506 self.update(cx, |project, cx| {
23507 if project
23508 .active_debug_session(cx)
23509 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23510 {
23511 return true;
23512 }
23513
23514 buffer.update(cx, |buffer, cx| {
23515 project.any_language_server_supports_inlay_hints(buffer, cx)
23516 })
23517 })
23518 }
23519
23520 fn inline_values(
23521 &self,
23522 buffer_handle: Entity<Buffer>,
23523 range: Range<text::Anchor>,
23524 cx: &mut App,
23525 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23526 self.update(cx, |project, cx| {
23527 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23528
23529 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23530 })
23531 }
23532
23533 fn applicable_inlay_chunks(
23534 &self,
23535 buffer: &Entity<Buffer>,
23536 ranges: &[Range<text::Anchor>],
23537 cx: &mut App,
23538 ) -> Vec<Range<BufferRow>> {
23539 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23540 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23541 })
23542 }
23543
23544 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23545 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23546 lsp_store.invalidate_inlay_hints(for_buffers)
23547 });
23548 }
23549
23550 fn inlay_hints(
23551 &self,
23552 invalidate: InvalidationStrategy,
23553 buffer: Entity<Buffer>,
23554 ranges: Vec<Range<text::Anchor>>,
23555 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23556 cx: &mut App,
23557 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23558 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23559 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23560 }))
23561 }
23562
23563 fn range_for_rename(
23564 &self,
23565 buffer: &Entity<Buffer>,
23566 position: text::Anchor,
23567 cx: &mut App,
23568 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23569 Some(self.update(cx, |project, cx| {
23570 let buffer = buffer.clone();
23571 let task = project.prepare_rename(buffer.clone(), position, cx);
23572 cx.spawn(async move |_, cx| {
23573 Ok(match task.await? {
23574 PrepareRenameResponse::Success(range) => Some(range),
23575 PrepareRenameResponse::InvalidPosition => None,
23576 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23577 // Fallback on using TreeSitter info to determine identifier range
23578 buffer.read_with(cx, |buffer, _| {
23579 let snapshot = buffer.snapshot();
23580 let (range, kind) = snapshot.surrounding_word(position, None);
23581 if kind != Some(CharKind::Word) {
23582 return None;
23583 }
23584 Some(
23585 snapshot.anchor_before(range.start)
23586 ..snapshot.anchor_after(range.end),
23587 )
23588 })?
23589 }
23590 })
23591 })
23592 }))
23593 }
23594
23595 fn perform_rename(
23596 &self,
23597 buffer: &Entity<Buffer>,
23598 position: text::Anchor,
23599 new_name: String,
23600 cx: &mut App,
23601 ) -> Option<Task<Result<ProjectTransaction>>> {
23602 Some(self.update(cx, |project, cx| {
23603 project.perform_rename(buffer.clone(), position, new_name, cx)
23604 }))
23605 }
23606}
23607
23608fn consume_contiguous_rows(
23609 contiguous_row_selections: &mut Vec<Selection<Point>>,
23610 selection: &Selection<Point>,
23611 display_map: &DisplaySnapshot,
23612 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23613) -> (MultiBufferRow, MultiBufferRow) {
23614 contiguous_row_selections.push(selection.clone());
23615 let start_row = starting_row(selection, display_map);
23616 let mut end_row = ending_row(selection, display_map);
23617
23618 while let Some(next_selection) = selections.peek() {
23619 if next_selection.start.row <= end_row.0 {
23620 end_row = ending_row(next_selection, display_map);
23621 contiguous_row_selections.push(selections.next().unwrap().clone());
23622 } else {
23623 break;
23624 }
23625 }
23626 (start_row, end_row)
23627}
23628
23629fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23630 if selection.start.column > 0 {
23631 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23632 } else {
23633 MultiBufferRow(selection.start.row)
23634 }
23635}
23636
23637fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23638 if next_selection.end.column > 0 || next_selection.is_empty() {
23639 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23640 } else {
23641 MultiBufferRow(next_selection.end.row)
23642 }
23643}
23644
23645impl EditorSnapshot {
23646 pub fn remote_selections_in_range<'a>(
23647 &'a self,
23648 range: &'a Range<Anchor>,
23649 collaboration_hub: &dyn CollaborationHub,
23650 cx: &'a App,
23651 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23652 let participant_names = collaboration_hub.user_names(cx);
23653 let participant_indices = collaboration_hub.user_participant_indices(cx);
23654 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23655 let collaborators_by_replica_id = collaborators_by_peer_id
23656 .values()
23657 .map(|collaborator| (collaborator.replica_id, collaborator))
23658 .collect::<HashMap<_, _>>();
23659 self.buffer_snapshot()
23660 .selections_in_range(range, false)
23661 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23662 if replica_id == ReplicaId::AGENT {
23663 Some(RemoteSelection {
23664 replica_id,
23665 selection,
23666 cursor_shape,
23667 line_mode,
23668 collaborator_id: CollaboratorId::Agent,
23669 user_name: Some("Agent".into()),
23670 color: cx.theme().players().agent(),
23671 })
23672 } else {
23673 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23674 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23675 let user_name = participant_names.get(&collaborator.user_id).cloned();
23676 Some(RemoteSelection {
23677 replica_id,
23678 selection,
23679 cursor_shape,
23680 line_mode,
23681 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23682 user_name,
23683 color: if let Some(index) = participant_index {
23684 cx.theme().players().color_for_participant(index.0)
23685 } else {
23686 cx.theme().players().absent()
23687 },
23688 })
23689 }
23690 })
23691 }
23692
23693 pub fn hunks_for_ranges(
23694 &self,
23695 ranges: impl IntoIterator<Item = Range<Point>>,
23696 ) -> Vec<MultiBufferDiffHunk> {
23697 let mut hunks = Vec::new();
23698 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23699 HashMap::default();
23700 for query_range in ranges {
23701 let query_rows =
23702 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23703 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23704 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23705 ) {
23706 // Include deleted hunks that are adjacent to the query range, because
23707 // otherwise they would be missed.
23708 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23709 if hunk.status().is_deleted() {
23710 intersects_range |= hunk.row_range.start == query_rows.end;
23711 intersects_range |= hunk.row_range.end == query_rows.start;
23712 }
23713 if intersects_range {
23714 if !processed_buffer_rows
23715 .entry(hunk.buffer_id)
23716 .or_default()
23717 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23718 {
23719 continue;
23720 }
23721 hunks.push(hunk);
23722 }
23723 }
23724 }
23725
23726 hunks
23727 }
23728
23729 fn display_diff_hunks_for_rows<'a>(
23730 &'a self,
23731 display_rows: Range<DisplayRow>,
23732 folded_buffers: &'a HashSet<BufferId>,
23733 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23734 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23735 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23736
23737 self.buffer_snapshot()
23738 .diff_hunks_in_range(buffer_start..buffer_end)
23739 .filter_map(|hunk| {
23740 if folded_buffers.contains(&hunk.buffer_id) {
23741 return None;
23742 }
23743
23744 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23745 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23746
23747 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23748 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23749
23750 let display_hunk = if hunk_display_start.column() != 0 {
23751 DisplayDiffHunk::Folded {
23752 display_row: hunk_display_start.row(),
23753 }
23754 } else {
23755 let mut end_row = hunk_display_end.row();
23756 if hunk_display_end.column() > 0 {
23757 end_row.0 += 1;
23758 }
23759 let is_created_file = hunk.is_created_file();
23760 DisplayDiffHunk::Unfolded {
23761 status: hunk.status(),
23762 diff_base_byte_range: hunk.diff_base_byte_range,
23763 display_row_range: hunk_display_start.row()..end_row,
23764 multi_buffer_range: Anchor::range_in_buffer(
23765 hunk.excerpt_id,
23766 hunk.buffer_id,
23767 hunk.buffer_range,
23768 ),
23769 is_created_file,
23770 }
23771 };
23772
23773 Some(display_hunk)
23774 })
23775 }
23776
23777 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23778 self.display_snapshot
23779 .buffer_snapshot()
23780 .language_at(position)
23781 }
23782
23783 pub fn is_focused(&self) -> bool {
23784 self.is_focused
23785 }
23786
23787 pub fn placeholder_text(&self) -> Option<String> {
23788 self.placeholder_display_snapshot
23789 .as_ref()
23790 .map(|display_map| display_map.text())
23791 }
23792
23793 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23794 self.scroll_anchor.scroll_position(&self.display_snapshot)
23795 }
23796
23797 fn gutter_dimensions(
23798 &self,
23799 font_id: FontId,
23800 font_size: Pixels,
23801 max_line_number_width: Pixels,
23802 cx: &App,
23803 ) -> Option<GutterDimensions> {
23804 if !self.show_gutter {
23805 return None;
23806 }
23807
23808 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23809 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23810
23811 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23812 matches!(
23813 ProjectSettings::get_global(cx).git.git_gutter,
23814 GitGutterSetting::TrackedFiles
23815 )
23816 });
23817 let gutter_settings = EditorSettings::get_global(cx).gutter;
23818 let show_line_numbers = self
23819 .show_line_numbers
23820 .unwrap_or(gutter_settings.line_numbers);
23821 let line_gutter_width = if show_line_numbers {
23822 // Avoid flicker-like gutter resizes when the line number gains another digit by
23823 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23824 let min_width_for_number_on_gutter =
23825 ch_advance * gutter_settings.min_line_number_digits as f32;
23826 max_line_number_width.max(min_width_for_number_on_gutter)
23827 } else {
23828 0.0.into()
23829 };
23830
23831 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23832 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23833
23834 let git_blame_entries_width =
23835 self.git_blame_gutter_max_author_length
23836 .map(|max_author_length| {
23837 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23838 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23839
23840 /// The number of characters to dedicate to gaps and margins.
23841 const SPACING_WIDTH: usize = 4;
23842
23843 let max_char_count = max_author_length.min(renderer.max_author_length())
23844 + ::git::SHORT_SHA_LENGTH
23845 + MAX_RELATIVE_TIMESTAMP.len()
23846 + SPACING_WIDTH;
23847
23848 ch_advance * max_char_count
23849 });
23850
23851 let is_singleton = self.buffer_snapshot().is_singleton();
23852
23853 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23854 left_padding += if !is_singleton {
23855 ch_width * 4.0
23856 } else if show_runnables || show_breakpoints {
23857 ch_width * 3.0
23858 } else if show_git_gutter && show_line_numbers {
23859 ch_width * 2.0
23860 } else if show_git_gutter || show_line_numbers {
23861 ch_width
23862 } else {
23863 px(0.)
23864 };
23865
23866 let shows_folds = is_singleton && gutter_settings.folds;
23867
23868 let right_padding = if shows_folds && show_line_numbers {
23869 ch_width * 4.0
23870 } else if shows_folds || (!is_singleton && show_line_numbers) {
23871 ch_width * 3.0
23872 } else if show_line_numbers {
23873 ch_width
23874 } else {
23875 px(0.)
23876 };
23877
23878 Some(GutterDimensions {
23879 left_padding,
23880 right_padding,
23881 width: line_gutter_width + left_padding + right_padding,
23882 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23883 git_blame_entries_width,
23884 })
23885 }
23886
23887 pub fn render_crease_toggle(
23888 &self,
23889 buffer_row: MultiBufferRow,
23890 row_contains_cursor: bool,
23891 editor: Entity<Editor>,
23892 window: &mut Window,
23893 cx: &mut App,
23894 ) -> Option<AnyElement> {
23895 let folded = self.is_line_folded(buffer_row);
23896 let mut is_foldable = false;
23897
23898 if let Some(crease) = self
23899 .crease_snapshot
23900 .query_row(buffer_row, self.buffer_snapshot())
23901 {
23902 is_foldable = true;
23903 match crease {
23904 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23905 if let Some(render_toggle) = render_toggle {
23906 let toggle_callback =
23907 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23908 if folded {
23909 editor.update(cx, |editor, cx| {
23910 editor.fold_at(buffer_row, window, cx)
23911 });
23912 } else {
23913 editor.update(cx, |editor, cx| {
23914 editor.unfold_at(buffer_row, window, cx)
23915 });
23916 }
23917 });
23918 return Some((render_toggle)(
23919 buffer_row,
23920 folded,
23921 toggle_callback,
23922 window,
23923 cx,
23924 ));
23925 }
23926 }
23927 }
23928 }
23929
23930 is_foldable |= self.starts_indent(buffer_row);
23931
23932 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23933 Some(
23934 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23935 .toggle_state(folded)
23936 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23937 if folded {
23938 this.unfold_at(buffer_row, window, cx);
23939 } else {
23940 this.fold_at(buffer_row, window, cx);
23941 }
23942 }))
23943 .into_any_element(),
23944 )
23945 } else {
23946 None
23947 }
23948 }
23949
23950 pub fn render_crease_trailer(
23951 &self,
23952 buffer_row: MultiBufferRow,
23953 window: &mut Window,
23954 cx: &mut App,
23955 ) -> Option<AnyElement> {
23956 let folded = self.is_line_folded(buffer_row);
23957 if let Crease::Inline { render_trailer, .. } = self
23958 .crease_snapshot
23959 .query_row(buffer_row, self.buffer_snapshot())?
23960 {
23961 let render_trailer = render_trailer.as_ref()?;
23962 Some(render_trailer(buffer_row, folded, window, cx))
23963 } else {
23964 None
23965 }
23966 }
23967}
23968
23969impl Deref for EditorSnapshot {
23970 type Target = DisplaySnapshot;
23971
23972 fn deref(&self) -> &Self::Target {
23973 &self.display_snapshot
23974 }
23975}
23976
23977#[derive(Clone, Debug, PartialEq, Eq)]
23978pub enum EditorEvent {
23979 InputIgnored {
23980 text: Arc<str>,
23981 },
23982 InputHandled {
23983 utf16_range_to_replace: Option<Range<isize>>,
23984 text: Arc<str>,
23985 },
23986 ExcerptsAdded {
23987 buffer: Entity<Buffer>,
23988 predecessor: ExcerptId,
23989 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23990 },
23991 ExcerptsRemoved {
23992 ids: Vec<ExcerptId>,
23993 removed_buffer_ids: Vec<BufferId>,
23994 },
23995 BufferFoldToggled {
23996 ids: Vec<ExcerptId>,
23997 folded: bool,
23998 },
23999 ExcerptsEdited {
24000 ids: Vec<ExcerptId>,
24001 },
24002 ExcerptsExpanded {
24003 ids: Vec<ExcerptId>,
24004 },
24005 BufferEdited,
24006 Edited {
24007 transaction_id: clock::Lamport,
24008 },
24009 Reparsed(BufferId),
24010 Focused,
24011 FocusedIn,
24012 Blurred,
24013 DirtyChanged,
24014 Saved,
24015 TitleChanged,
24016 SelectionsChanged {
24017 local: bool,
24018 },
24019 ScrollPositionChanged {
24020 local: bool,
24021 autoscroll: bool,
24022 },
24023 TransactionUndone {
24024 transaction_id: clock::Lamport,
24025 },
24026 TransactionBegun {
24027 transaction_id: clock::Lamport,
24028 },
24029 CursorShapeChanged,
24030 BreadcrumbsChanged,
24031 PushedToNavHistory {
24032 anchor: Anchor,
24033 is_deactivate: bool,
24034 },
24035}
24036
24037impl EventEmitter<EditorEvent> for Editor {}
24038
24039impl Focusable for Editor {
24040 fn focus_handle(&self, _cx: &App) -> FocusHandle {
24041 self.focus_handle.clone()
24042 }
24043}
24044
24045impl Render for Editor {
24046 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24047 let settings = ThemeSettings::get_global(cx);
24048
24049 let mut text_style = match self.mode {
24050 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24051 color: cx.theme().colors().editor_foreground,
24052 font_family: settings.ui_font.family.clone(),
24053 font_features: settings.ui_font.features.clone(),
24054 font_fallbacks: settings.ui_font.fallbacks.clone(),
24055 font_size: rems(0.875).into(),
24056 font_weight: settings.ui_font.weight,
24057 line_height: relative(settings.buffer_line_height.value()),
24058 ..Default::default()
24059 },
24060 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24061 color: cx.theme().colors().editor_foreground,
24062 font_family: settings.buffer_font.family.clone(),
24063 font_features: settings.buffer_font.features.clone(),
24064 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24065 font_size: settings.buffer_font_size(cx).into(),
24066 font_weight: settings.buffer_font.weight,
24067 line_height: relative(settings.buffer_line_height.value()),
24068 ..Default::default()
24069 },
24070 };
24071 if let Some(text_style_refinement) = &self.text_style_refinement {
24072 text_style.refine(text_style_refinement)
24073 }
24074
24075 let background = match self.mode {
24076 EditorMode::SingleLine => cx.theme().system().transparent,
24077 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24078 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24079 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24080 };
24081
24082 EditorElement::new(
24083 &cx.entity(),
24084 EditorStyle {
24085 background,
24086 border: cx.theme().colors().border,
24087 local_player: cx.theme().players().local(),
24088 text: text_style,
24089 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24090 syntax: cx.theme().syntax().clone(),
24091 status: cx.theme().status().clone(),
24092 inlay_hints_style: make_inlay_hints_style(cx),
24093 edit_prediction_styles: make_suggestion_styles(cx),
24094 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
24095 show_underlines: self.diagnostics_enabled(),
24096 },
24097 )
24098 }
24099}
24100
24101impl EntityInputHandler for Editor {
24102 fn text_for_range(
24103 &mut self,
24104 range_utf16: Range<usize>,
24105 adjusted_range: &mut Option<Range<usize>>,
24106 _: &mut Window,
24107 cx: &mut Context<Self>,
24108 ) -> Option<String> {
24109 let snapshot = self.buffer.read(cx).read(cx);
24110 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
24111 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
24112 if (start.0..end.0) != range_utf16 {
24113 adjusted_range.replace(start.0..end.0);
24114 }
24115 Some(snapshot.text_for_range(start..end).collect())
24116 }
24117
24118 fn selected_text_range(
24119 &mut self,
24120 ignore_disabled_input: bool,
24121 _: &mut Window,
24122 cx: &mut Context<Self>,
24123 ) -> Option<UTF16Selection> {
24124 // Prevent the IME menu from appearing when holding down an alphabetic key
24125 // while input is disabled.
24126 if !ignore_disabled_input && !self.input_enabled {
24127 return None;
24128 }
24129
24130 let selection = self
24131 .selections
24132 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
24133 let range = selection.range();
24134
24135 Some(UTF16Selection {
24136 range: range.start.0..range.end.0,
24137 reversed: selection.reversed,
24138 })
24139 }
24140
24141 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24142 let snapshot = self.buffer.read(cx).read(cx);
24143 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24144 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
24145 }
24146
24147 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24148 self.clear_highlights::<InputComposition>(cx);
24149 self.ime_transaction.take();
24150 }
24151
24152 fn replace_text_in_range(
24153 &mut self,
24154 range_utf16: Option<Range<usize>>,
24155 text: &str,
24156 window: &mut Window,
24157 cx: &mut Context<Self>,
24158 ) {
24159 if !self.input_enabled {
24160 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24161 return;
24162 }
24163
24164 self.transact(window, cx, |this, window, cx| {
24165 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24166 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24167 Some(this.selection_replacement_ranges(range_utf16, cx))
24168 } else {
24169 this.marked_text_ranges(cx)
24170 };
24171
24172 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24173 let newest_selection_id = this.selections.newest_anchor().id;
24174 this.selections
24175 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24176 .iter()
24177 .zip(ranges_to_replace.iter())
24178 .find_map(|(selection, range)| {
24179 if selection.id == newest_selection_id {
24180 Some(
24181 (range.start.0 as isize - selection.head().0 as isize)
24182 ..(range.end.0 as isize - selection.head().0 as isize),
24183 )
24184 } else {
24185 None
24186 }
24187 })
24188 });
24189
24190 cx.emit(EditorEvent::InputHandled {
24191 utf16_range_to_replace: range_to_replace,
24192 text: text.into(),
24193 });
24194
24195 if let Some(new_selected_ranges) = new_selected_ranges {
24196 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24197 selections.select_ranges(new_selected_ranges)
24198 });
24199 this.backspace(&Default::default(), window, cx);
24200 }
24201
24202 this.handle_input(text, window, cx);
24203 });
24204
24205 if let Some(transaction) = self.ime_transaction {
24206 self.buffer.update(cx, |buffer, cx| {
24207 buffer.group_until_transaction(transaction, cx);
24208 });
24209 }
24210
24211 self.unmark_text(window, cx);
24212 }
24213
24214 fn replace_and_mark_text_in_range(
24215 &mut self,
24216 range_utf16: Option<Range<usize>>,
24217 text: &str,
24218 new_selected_range_utf16: Option<Range<usize>>,
24219 window: &mut Window,
24220 cx: &mut Context<Self>,
24221 ) {
24222 if !self.input_enabled {
24223 return;
24224 }
24225
24226 let transaction = self.transact(window, cx, |this, window, cx| {
24227 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24228 let snapshot = this.buffer.read(cx).read(cx);
24229 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24230 for marked_range in &mut marked_ranges {
24231 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24232 marked_range.start.0 += relative_range_utf16.start;
24233 marked_range.start =
24234 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24235 marked_range.end =
24236 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24237 }
24238 }
24239 Some(marked_ranges)
24240 } else if let Some(range_utf16) = range_utf16 {
24241 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24242 Some(this.selection_replacement_ranges(range_utf16, cx))
24243 } else {
24244 None
24245 };
24246
24247 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24248 let newest_selection_id = this.selections.newest_anchor().id;
24249 this.selections
24250 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24251 .iter()
24252 .zip(ranges_to_replace.iter())
24253 .find_map(|(selection, range)| {
24254 if selection.id == newest_selection_id {
24255 Some(
24256 (range.start.0 as isize - selection.head().0 as isize)
24257 ..(range.end.0 as isize - selection.head().0 as isize),
24258 )
24259 } else {
24260 None
24261 }
24262 })
24263 });
24264
24265 cx.emit(EditorEvent::InputHandled {
24266 utf16_range_to_replace: range_to_replace,
24267 text: text.into(),
24268 });
24269
24270 if let Some(ranges) = ranges_to_replace {
24271 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24272 s.select_ranges(ranges)
24273 });
24274 }
24275
24276 let marked_ranges = {
24277 let snapshot = this.buffer.read(cx).read(cx);
24278 this.selections
24279 .disjoint_anchors_arc()
24280 .iter()
24281 .map(|selection| {
24282 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24283 })
24284 .collect::<Vec<_>>()
24285 };
24286
24287 if text.is_empty() {
24288 this.unmark_text(window, cx);
24289 } else {
24290 this.highlight_text::<InputComposition>(
24291 marked_ranges.clone(),
24292 HighlightStyle {
24293 underline: Some(UnderlineStyle {
24294 thickness: px(1.),
24295 color: None,
24296 wavy: false,
24297 }),
24298 ..Default::default()
24299 },
24300 cx,
24301 );
24302 }
24303
24304 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24305 let use_autoclose = this.use_autoclose;
24306 let use_auto_surround = this.use_auto_surround;
24307 this.set_use_autoclose(false);
24308 this.set_use_auto_surround(false);
24309 this.handle_input(text, window, cx);
24310 this.set_use_autoclose(use_autoclose);
24311 this.set_use_auto_surround(use_auto_surround);
24312
24313 if let Some(new_selected_range) = new_selected_range_utf16 {
24314 let snapshot = this.buffer.read(cx).read(cx);
24315 let new_selected_ranges = marked_ranges
24316 .into_iter()
24317 .map(|marked_range| {
24318 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24319 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24320 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24321 snapshot.clip_offset_utf16(new_start, Bias::Left)
24322 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24323 })
24324 .collect::<Vec<_>>();
24325
24326 drop(snapshot);
24327 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24328 selections.select_ranges(new_selected_ranges)
24329 });
24330 }
24331 });
24332
24333 self.ime_transaction = self.ime_transaction.or(transaction);
24334 if let Some(transaction) = self.ime_transaction {
24335 self.buffer.update(cx, |buffer, cx| {
24336 buffer.group_until_transaction(transaction, cx);
24337 });
24338 }
24339
24340 if self.text_highlights::<InputComposition>(cx).is_none() {
24341 self.ime_transaction.take();
24342 }
24343 }
24344
24345 fn bounds_for_range(
24346 &mut self,
24347 range_utf16: Range<usize>,
24348 element_bounds: gpui::Bounds<Pixels>,
24349 window: &mut Window,
24350 cx: &mut Context<Self>,
24351 ) -> Option<gpui::Bounds<Pixels>> {
24352 let text_layout_details = self.text_layout_details(window);
24353 let CharacterDimensions {
24354 em_width,
24355 em_advance,
24356 line_height,
24357 } = self.character_dimensions(window);
24358
24359 let snapshot = self.snapshot(window, cx);
24360 let scroll_position = snapshot.scroll_position();
24361 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24362
24363 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24364 let x = Pixels::from(
24365 ScrollOffset::from(
24366 snapshot.x_for_display_point(start, &text_layout_details)
24367 + self.gutter_dimensions.full_width(),
24368 ) - scroll_left,
24369 );
24370 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24371
24372 Some(Bounds {
24373 origin: element_bounds.origin + point(x, y),
24374 size: size(em_width, line_height),
24375 })
24376 }
24377
24378 fn character_index_for_point(
24379 &mut self,
24380 point: gpui::Point<Pixels>,
24381 _window: &mut Window,
24382 _cx: &mut Context<Self>,
24383 ) -> Option<usize> {
24384 let position_map = self.last_position_map.as_ref()?;
24385 if !position_map.text_hitbox.contains(&point) {
24386 return None;
24387 }
24388 let display_point = position_map.point_for_position(point).previous_valid;
24389 let anchor = position_map
24390 .snapshot
24391 .display_point_to_anchor(display_point, Bias::Left);
24392 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24393 Some(utf16_offset.0)
24394 }
24395
24396 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24397 self.input_enabled
24398 }
24399}
24400
24401trait SelectionExt {
24402 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24403 fn spanned_rows(
24404 &self,
24405 include_end_if_at_line_start: bool,
24406 map: &DisplaySnapshot,
24407 ) -> Range<MultiBufferRow>;
24408}
24409
24410impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24411 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24412 let start = self
24413 .start
24414 .to_point(map.buffer_snapshot())
24415 .to_display_point(map);
24416 let end = self
24417 .end
24418 .to_point(map.buffer_snapshot())
24419 .to_display_point(map);
24420 if self.reversed {
24421 end..start
24422 } else {
24423 start..end
24424 }
24425 }
24426
24427 fn spanned_rows(
24428 &self,
24429 include_end_if_at_line_start: bool,
24430 map: &DisplaySnapshot,
24431 ) -> Range<MultiBufferRow> {
24432 let start = self.start.to_point(map.buffer_snapshot());
24433 let mut end = self.end.to_point(map.buffer_snapshot());
24434 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24435 end.row -= 1;
24436 }
24437
24438 let buffer_start = map.prev_line_boundary(start).0;
24439 let buffer_end = map.next_line_boundary(end).0;
24440 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24441 }
24442}
24443
24444impl<T: InvalidationRegion> InvalidationStack<T> {
24445 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24446 where
24447 S: Clone + ToOffset,
24448 {
24449 while let Some(region) = self.last() {
24450 let all_selections_inside_invalidation_ranges =
24451 if selections.len() == region.ranges().len() {
24452 selections
24453 .iter()
24454 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24455 .all(|(selection, invalidation_range)| {
24456 let head = selection.head().to_offset(buffer);
24457 invalidation_range.start <= head && invalidation_range.end >= head
24458 })
24459 } else {
24460 false
24461 };
24462
24463 if all_selections_inside_invalidation_ranges {
24464 break;
24465 } else {
24466 self.pop();
24467 }
24468 }
24469 }
24470}
24471
24472impl<T> Default for InvalidationStack<T> {
24473 fn default() -> Self {
24474 Self(Default::default())
24475 }
24476}
24477
24478impl<T> Deref for InvalidationStack<T> {
24479 type Target = Vec<T>;
24480
24481 fn deref(&self) -> &Self::Target {
24482 &self.0
24483 }
24484}
24485
24486impl<T> DerefMut for InvalidationStack<T> {
24487 fn deref_mut(&mut self) -> &mut Self::Target {
24488 &mut self.0
24489 }
24490}
24491
24492impl InvalidationRegion for SnippetState {
24493 fn ranges(&self) -> &[Range<Anchor>] {
24494 &self.ranges[self.active_index]
24495 }
24496}
24497
24498fn edit_prediction_edit_text(
24499 current_snapshot: &BufferSnapshot,
24500 edits: &[(Range<Anchor>, impl AsRef<str>)],
24501 edit_preview: &EditPreview,
24502 include_deletions: bool,
24503 cx: &App,
24504) -> HighlightedText {
24505 let edits = edits
24506 .iter()
24507 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24508 .collect::<Vec<_>>();
24509
24510 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24511}
24512
24513fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24514 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24515 // Just show the raw edit text with basic styling
24516 let mut text = String::new();
24517 let mut highlights = Vec::new();
24518
24519 let insertion_highlight_style = HighlightStyle {
24520 color: Some(cx.theme().colors().text),
24521 ..Default::default()
24522 };
24523
24524 for (_, edit_text) in edits {
24525 let start_offset = text.len();
24526 text.push_str(edit_text);
24527 let end_offset = text.len();
24528
24529 if start_offset < end_offset {
24530 highlights.push((start_offset..end_offset, insertion_highlight_style));
24531 }
24532 }
24533
24534 HighlightedText {
24535 text: text.into(),
24536 highlights,
24537 }
24538}
24539
24540pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24541 match severity {
24542 lsp::DiagnosticSeverity::ERROR => colors.error,
24543 lsp::DiagnosticSeverity::WARNING => colors.warning,
24544 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24545 lsp::DiagnosticSeverity::HINT => colors.info,
24546 _ => colors.ignored,
24547 }
24548}
24549
24550pub fn styled_runs_for_code_label<'a>(
24551 label: &'a CodeLabel,
24552 syntax_theme: &'a theme::SyntaxTheme,
24553) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24554 let fade_out = HighlightStyle {
24555 fade_out: Some(0.35),
24556 ..Default::default()
24557 };
24558
24559 let mut prev_end = label.filter_range.end;
24560 label
24561 .runs
24562 .iter()
24563 .enumerate()
24564 .flat_map(move |(ix, (range, highlight_id))| {
24565 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24566 style
24567 } else {
24568 return Default::default();
24569 };
24570 let muted_style = style.highlight(fade_out);
24571
24572 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24573 if range.start >= label.filter_range.end {
24574 if range.start > prev_end {
24575 runs.push((prev_end..range.start, fade_out));
24576 }
24577 runs.push((range.clone(), muted_style));
24578 } else if range.end <= label.filter_range.end {
24579 runs.push((range.clone(), style));
24580 } else {
24581 runs.push((range.start..label.filter_range.end, style));
24582 runs.push((label.filter_range.end..range.end, muted_style));
24583 }
24584 prev_end = cmp::max(prev_end, range.end);
24585
24586 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24587 runs.push((prev_end..label.text.len(), fade_out));
24588 }
24589
24590 runs
24591 })
24592}
24593
24594pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24595 let mut prev_index = 0;
24596 let mut prev_codepoint: Option<char> = None;
24597 text.char_indices()
24598 .chain([(text.len(), '\0')])
24599 .filter_map(move |(index, codepoint)| {
24600 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24601 let is_boundary = index == text.len()
24602 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24603 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24604 if is_boundary {
24605 let chunk = &text[prev_index..index];
24606 prev_index = index;
24607 Some(chunk)
24608 } else {
24609 None
24610 }
24611 })
24612}
24613
24614pub trait RangeToAnchorExt: Sized {
24615 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24616
24617 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24618 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24619 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24620 }
24621}
24622
24623impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24624 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24625 let start_offset = self.start.to_offset(snapshot);
24626 let end_offset = self.end.to_offset(snapshot);
24627 if start_offset == end_offset {
24628 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24629 } else {
24630 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24631 }
24632 }
24633}
24634
24635pub trait RowExt {
24636 fn as_f64(&self) -> f64;
24637
24638 fn next_row(&self) -> Self;
24639
24640 fn previous_row(&self) -> Self;
24641
24642 fn minus(&self, other: Self) -> u32;
24643}
24644
24645impl RowExt for DisplayRow {
24646 fn as_f64(&self) -> f64 {
24647 self.0 as _
24648 }
24649
24650 fn next_row(&self) -> Self {
24651 Self(self.0 + 1)
24652 }
24653
24654 fn previous_row(&self) -> Self {
24655 Self(self.0.saturating_sub(1))
24656 }
24657
24658 fn minus(&self, other: Self) -> u32 {
24659 self.0 - other.0
24660 }
24661}
24662
24663impl RowExt for MultiBufferRow {
24664 fn as_f64(&self) -> f64 {
24665 self.0 as _
24666 }
24667
24668 fn next_row(&self) -> Self {
24669 Self(self.0 + 1)
24670 }
24671
24672 fn previous_row(&self) -> Self {
24673 Self(self.0.saturating_sub(1))
24674 }
24675
24676 fn minus(&self, other: Self) -> u32 {
24677 self.0 - other.0
24678 }
24679}
24680
24681trait RowRangeExt {
24682 type Row;
24683
24684 fn len(&self) -> usize;
24685
24686 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24687}
24688
24689impl RowRangeExt for Range<MultiBufferRow> {
24690 type Row = MultiBufferRow;
24691
24692 fn len(&self) -> usize {
24693 (self.end.0 - self.start.0) as usize
24694 }
24695
24696 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24697 (self.start.0..self.end.0).map(MultiBufferRow)
24698 }
24699}
24700
24701impl RowRangeExt for Range<DisplayRow> {
24702 type Row = DisplayRow;
24703
24704 fn len(&self) -> usize {
24705 (self.end.0 - self.start.0) as usize
24706 }
24707
24708 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24709 (self.start.0..self.end.0).map(DisplayRow)
24710 }
24711}
24712
24713/// If select range has more than one line, we
24714/// just point the cursor to range.start.
24715fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24716 if range.start.row == range.end.row {
24717 range
24718 } else {
24719 range.start..range.start
24720 }
24721}
24722pub struct KillRing(ClipboardItem);
24723impl Global for KillRing {}
24724
24725const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24726
24727enum BreakpointPromptEditAction {
24728 Log,
24729 Condition,
24730 HitCondition,
24731}
24732
24733struct BreakpointPromptEditor {
24734 pub(crate) prompt: Entity<Editor>,
24735 editor: WeakEntity<Editor>,
24736 breakpoint_anchor: Anchor,
24737 breakpoint: Breakpoint,
24738 edit_action: BreakpointPromptEditAction,
24739 block_ids: HashSet<CustomBlockId>,
24740 editor_margins: Arc<Mutex<EditorMargins>>,
24741 _subscriptions: Vec<Subscription>,
24742}
24743
24744impl BreakpointPromptEditor {
24745 const MAX_LINES: u8 = 4;
24746
24747 fn new(
24748 editor: WeakEntity<Editor>,
24749 breakpoint_anchor: Anchor,
24750 breakpoint: Breakpoint,
24751 edit_action: BreakpointPromptEditAction,
24752 window: &mut Window,
24753 cx: &mut Context<Self>,
24754 ) -> Self {
24755 let base_text = match edit_action {
24756 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24757 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24758 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24759 }
24760 .map(|msg| msg.to_string())
24761 .unwrap_or_default();
24762
24763 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24764 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24765
24766 let prompt = cx.new(|cx| {
24767 let mut prompt = Editor::new(
24768 EditorMode::AutoHeight {
24769 min_lines: 1,
24770 max_lines: Some(Self::MAX_LINES as usize),
24771 },
24772 buffer,
24773 None,
24774 window,
24775 cx,
24776 );
24777 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24778 prompt.set_show_cursor_when_unfocused(false, cx);
24779 prompt.set_placeholder_text(
24780 match edit_action {
24781 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24782 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24783 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24784 },
24785 window,
24786 cx,
24787 );
24788
24789 prompt
24790 });
24791
24792 Self {
24793 prompt,
24794 editor,
24795 breakpoint_anchor,
24796 breakpoint,
24797 edit_action,
24798 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24799 block_ids: Default::default(),
24800 _subscriptions: vec![],
24801 }
24802 }
24803
24804 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24805 self.block_ids.extend(block_ids)
24806 }
24807
24808 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24809 if let Some(editor) = self.editor.upgrade() {
24810 let message = self
24811 .prompt
24812 .read(cx)
24813 .buffer
24814 .read(cx)
24815 .as_singleton()
24816 .expect("A multi buffer in breakpoint prompt isn't possible")
24817 .read(cx)
24818 .as_rope()
24819 .to_string();
24820
24821 editor.update(cx, |editor, cx| {
24822 editor.edit_breakpoint_at_anchor(
24823 self.breakpoint_anchor,
24824 self.breakpoint.clone(),
24825 match self.edit_action {
24826 BreakpointPromptEditAction::Log => {
24827 BreakpointEditAction::EditLogMessage(message.into())
24828 }
24829 BreakpointPromptEditAction::Condition => {
24830 BreakpointEditAction::EditCondition(message.into())
24831 }
24832 BreakpointPromptEditAction::HitCondition => {
24833 BreakpointEditAction::EditHitCondition(message.into())
24834 }
24835 },
24836 cx,
24837 );
24838
24839 editor.remove_blocks(self.block_ids.clone(), None, cx);
24840 cx.focus_self(window);
24841 });
24842 }
24843 }
24844
24845 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24846 self.editor
24847 .update(cx, |editor, cx| {
24848 editor.remove_blocks(self.block_ids.clone(), None, cx);
24849 window.focus(&editor.focus_handle);
24850 })
24851 .log_err();
24852 }
24853
24854 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24855 let settings = ThemeSettings::get_global(cx);
24856 let text_style = TextStyle {
24857 color: if self.prompt.read(cx).read_only(cx) {
24858 cx.theme().colors().text_disabled
24859 } else {
24860 cx.theme().colors().text
24861 },
24862 font_family: settings.buffer_font.family.clone(),
24863 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24864 font_size: settings.buffer_font_size(cx).into(),
24865 font_weight: settings.buffer_font.weight,
24866 line_height: relative(settings.buffer_line_height.value()),
24867 ..Default::default()
24868 };
24869 EditorElement::new(
24870 &self.prompt,
24871 EditorStyle {
24872 background: cx.theme().colors().editor_background,
24873 local_player: cx.theme().players().local(),
24874 text: text_style,
24875 ..Default::default()
24876 },
24877 )
24878 }
24879}
24880
24881impl Render for BreakpointPromptEditor {
24882 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24883 let editor_margins = *self.editor_margins.lock();
24884 let gutter_dimensions = editor_margins.gutter;
24885 h_flex()
24886 .key_context("Editor")
24887 .bg(cx.theme().colors().editor_background)
24888 .border_y_1()
24889 .border_color(cx.theme().status().info_border)
24890 .size_full()
24891 .py(window.line_height() / 2.5)
24892 .on_action(cx.listener(Self::confirm))
24893 .on_action(cx.listener(Self::cancel))
24894 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24895 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24896 }
24897}
24898
24899impl Focusable for BreakpointPromptEditor {
24900 fn focus_handle(&self, cx: &App) -> FocusHandle {
24901 self.prompt.focus_handle(cx)
24902 }
24903}
24904
24905fn all_edits_insertions_or_deletions(
24906 edits: &Vec<(Range<Anchor>, Arc<str>)>,
24907 snapshot: &MultiBufferSnapshot,
24908) -> bool {
24909 let mut all_insertions = true;
24910 let mut all_deletions = true;
24911
24912 for (range, new_text) in edits.iter() {
24913 let range_is_empty = range.to_offset(snapshot).is_empty();
24914 let text_is_empty = new_text.is_empty();
24915
24916 if range_is_empty != text_is_empty {
24917 if range_is_empty {
24918 all_deletions = false;
24919 } else {
24920 all_insertions = false;
24921 }
24922 } else {
24923 return false;
24924 }
24925
24926 if !all_insertions && !all_deletions {
24927 return false;
24928 }
24929 }
24930 all_insertions || all_deletions
24931}
24932
24933struct MissingEditPredictionKeybindingTooltip;
24934
24935impl Render for MissingEditPredictionKeybindingTooltip {
24936 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24937 ui::tooltip_container(cx, |container, cx| {
24938 container
24939 .flex_shrink_0()
24940 .max_w_80()
24941 .min_h(rems_from_px(124.))
24942 .justify_between()
24943 .child(
24944 v_flex()
24945 .flex_1()
24946 .text_ui_sm(cx)
24947 .child(Label::new("Conflict with Accept Keybinding"))
24948 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24949 )
24950 .child(
24951 h_flex()
24952 .pb_1()
24953 .gap_1()
24954 .items_end()
24955 .w_full()
24956 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24957 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24958 }))
24959 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24960 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24961 })),
24962 )
24963 })
24964 }
24965}
24966
24967#[derive(Debug, Clone, Copy, PartialEq)]
24968pub struct LineHighlight {
24969 pub background: Background,
24970 pub border: Option<gpui::Hsla>,
24971 pub include_gutter: bool,
24972 pub type_id: Option<TypeId>,
24973}
24974
24975struct LineManipulationResult {
24976 pub new_text: String,
24977 pub line_count_before: usize,
24978 pub line_count_after: usize,
24979}
24980
24981fn render_diff_hunk_controls(
24982 row: u32,
24983 status: &DiffHunkStatus,
24984 hunk_range: Range<Anchor>,
24985 is_created_file: bool,
24986 line_height: Pixels,
24987 editor: &Entity<Editor>,
24988 _window: &mut Window,
24989 cx: &mut App,
24990) -> AnyElement {
24991 h_flex()
24992 .h(line_height)
24993 .mr_1()
24994 .gap_1()
24995 .px_0p5()
24996 .pb_1()
24997 .border_x_1()
24998 .border_b_1()
24999 .border_color(cx.theme().colors().border_variant)
25000 .rounded_b_lg()
25001 .bg(cx.theme().colors().editor_background)
25002 .gap_1()
25003 .block_mouse_except_scroll()
25004 .shadow_md()
25005 .child(if status.has_secondary_hunk() {
25006 Button::new(("stage", row as u64), "Stage")
25007 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25008 .tooltip({
25009 let focus_handle = editor.focus_handle(cx);
25010 move |_window, cx| {
25011 Tooltip::for_action_in(
25012 "Stage Hunk",
25013 &::git::ToggleStaged,
25014 &focus_handle,
25015 cx,
25016 )
25017 }
25018 })
25019 .on_click({
25020 let editor = editor.clone();
25021 move |_event, _window, cx| {
25022 editor.update(cx, |editor, cx| {
25023 editor.stage_or_unstage_diff_hunks(
25024 true,
25025 vec![hunk_range.start..hunk_range.start],
25026 cx,
25027 );
25028 });
25029 }
25030 })
25031 } else {
25032 Button::new(("unstage", row as u64), "Unstage")
25033 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25034 .tooltip({
25035 let focus_handle = editor.focus_handle(cx);
25036 move |_window, cx| {
25037 Tooltip::for_action_in(
25038 "Unstage Hunk",
25039 &::git::ToggleStaged,
25040 &focus_handle,
25041 cx,
25042 )
25043 }
25044 })
25045 .on_click({
25046 let editor = editor.clone();
25047 move |_event, _window, cx| {
25048 editor.update(cx, |editor, cx| {
25049 editor.stage_or_unstage_diff_hunks(
25050 false,
25051 vec![hunk_range.start..hunk_range.start],
25052 cx,
25053 );
25054 });
25055 }
25056 })
25057 })
25058 .child(
25059 Button::new(("restore", row as u64), "Restore")
25060 .tooltip({
25061 let focus_handle = editor.focus_handle(cx);
25062 move |_window, cx| {
25063 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
25064 }
25065 })
25066 .on_click({
25067 let editor = editor.clone();
25068 move |_event, window, cx| {
25069 editor.update(cx, |editor, cx| {
25070 let snapshot = editor.snapshot(window, cx);
25071 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
25072 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
25073 });
25074 }
25075 })
25076 .disabled(is_created_file),
25077 )
25078 .when(
25079 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
25080 |el| {
25081 el.child(
25082 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
25083 .shape(IconButtonShape::Square)
25084 .icon_size(IconSize::Small)
25085 // .disabled(!has_multiple_hunks)
25086 .tooltip({
25087 let focus_handle = editor.focus_handle(cx);
25088 move |_window, cx| {
25089 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
25090 }
25091 })
25092 .on_click({
25093 let editor = editor.clone();
25094 move |_event, window, cx| {
25095 editor.update(cx, |editor, cx| {
25096 let snapshot = editor.snapshot(window, cx);
25097 let position =
25098 hunk_range.end.to_point(&snapshot.buffer_snapshot());
25099 editor.go_to_hunk_before_or_after_position(
25100 &snapshot,
25101 position,
25102 Direction::Next,
25103 window,
25104 cx,
25105 );
25106 editor.expand_selected_diff_hunks(cx);
25107 });
25108 }
25109 }),
25110 )
25111 .child(
25112 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25113 .shape(IconButtonShape::Square)
25114 .icon_size(IconSize::Small)
25115 // .disabled(!has_multiple_hunks)
25116 .tooltip({
25117 let focus_handle = editor.focus_handle(cx);
25118 move |_window, cx| {
25119 Tooltip::for_action_in(
25120 "Previous Hunk",
25121 &GoToPreviousHunk,
25122 &focus_handle,
25123 cx,
25124 )
25125 }
25126 })
25127 .on_click({
25128 let editor = editor.clone();
25129 move |_event, window, cx| {
25130 editor.update(cx, |editor, cx| {
25131 let snapshot = editor.snapshot(window, cx);
25132 let point =
25133 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25134 editor.go_to_hunk_before_or_after_position(
25135 &snapshot,
25136 point,
25137 Direction::Prev,
25138 window,
25139 cx,
25140 );
25141 editor.expand_selected_diff_hunks(cx);
25142 });
25143 }
25144 }),
25145 )
25146 },
25147 )
25148 .into_any_element()
25149}
25150
25151pub fn multibuffer_context_lines(cx: &App) -> u32 {
25152 EditorSettings::try_get(cx)
25153 .map(|settings| settings.excerpt_context_lines)
25154 .unwrap_or(2)
25155 .min(32)
25156}