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;
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, Point, Runnable, RunnableRange, Selection, SelectionGoal,
121 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_settings(cx: &mut App) {
314 EditorSettings::register(cx);
315}
316
317pub fn init(cx: &mut App) {
318 init_settings(cx);
319
320 cx.set_global(GlobalBlameRenderer(Arc::new(())));
321
322 workspace::register_project_item::<Editor>(cx);
323 workspace::FollowableViewRegistry::register::<Editor>(cx);
324 workspace::register_serializable_item::<Editor>(cx);
325
326 cx.observe_new(
327 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
328 workspace.register_action(Editor::new_file);
329 workspace.register_action(Editor::new_file_split);
330 workspace.register_action(Editor::new_file_vertical);
331 workspace.register_action(Editor::new_file_horizontal);
332 workspace.register_action(Editor::cancel_language_server_work);
333 workspace.register_action(Editor::toggle_focus);
334 },
335 )
336 .detach();
337
338 cx.on_action(move |_: &workspace::NewFile, cx| {
339 let app_state = workspace::AppState::global(cx);
340 if let Some(app_state) = app_state.upgrade() {
341 workspace::open_new(
342 Default::default(),
343 app_state,
344 cx,
345 |workspace, window, cx| {
346 Editor::new_file(workspace, &Default::default(), window, cx)
347 },
348 )
349 .detach();
350 }
351 });
352 cx.on_action(move |_: &workspace::NewWindow, cx| {
353 let app_state = workspace::AppState::global(cx);
354 if let Some(app_state) = app_state.upgrade() {
355 workspace::open_new(
356 Default::default(),
357 app_state,
358 cx,
359 |workspace, window, cx| {
360 cx.activate(true);
361 Editor::new_file(workspace, &Default::default(), window, cx)
362 },
363 )
364 .detach();
365 }
366 });
367}
368
369pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
370 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
371}
372
373pub trait DiagnosticRenderer {
374 fn render_group(
375 &self,
376 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
377 buffer_id: BufferId,
378 snapshot: EditorSnapshot,
379 editor: WeakEntity<Editor>,
380 cx: &mut App,
381 ) -> Vec<BlockProperties<Anchor>>;
382
383 fn render_hover(
384 &self,
385 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
386 range: Range<Point>,
387 buffer_id: BufferId,
388 cx: &mut App,
389 ) -> Option<Entity<markdown::Markdown>>;
390
391 fn open_link(
392 &self,
393 editor: &mut Editor,
394 link: SharedString,
395 window: &mut Window,
396 cx: &mut Context<Editor>,
397 );
398}
399
400pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
401
402impl GlobalDiagnosticRenderer {
403 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
404 cx.try_global::<Self>().map(|g| g.0.clone())
405 }
406}
407
408impl gpui::Global for GlobalDiagnosticRenderer {}
409pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
410 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
411}
412
413pub struct SearchWithinRange;
414
415trait InvalidationRegion {
416 fn ranges(&self) -> &[Range<Anchor>];
417}
418
419#[derive(Clone, Debug, PartialEq)]
420pub enum SelectPhase {
421 Begin {
422 position: DisplayPoint,
423 add: bool,
424 click_count: usize,
425 },
426 BeginColumnar {
427 position: DisplayPoint,
428 reset: bool,
429 mode: ColumnarMode,
430 goal_column: u32,
431 },
432 Extend {
433 position: DisplayPoint,
434 click_count: usize,
435 },
436 Update {
437 position: DisplayPoint,
438 goal_column: u32,
439 scroll_delta: gpui::Point<f32>,
440 },
441 End,
442}
443
444#[derive(Clone, Debug, PartialEq)]
445pub enum ColumnarMode {
446 FromMouse,
447 FromSelection,
448}
449
450#[derive(Clone, Debug)]
451pub enum SelectMode {
452 Character,
453 Word(Range<Anchor>),
454 Line(Range<Anchor>),
455 All,
456}
457
458#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
459pub enum SizingBehavior {
460 /// The editor will layout itself using `size_full` and will include the vertical
461 /// scroll margin as requested by user settings.
462 #[default]
463 Default,
464 /// The editor will layout itself using `size_full`, but will not have any
465 /// vertical overscroll.
466 ExcludeOverscrollMargin,
467 /// The editor will request a vertical size according to its content and will be
468 /// layouted without a vertical scroll margin.
469 SizeByContent,
470}
471
472#[derive(Clone, PartialEq, Eq, Debug)]
473pub enum EditorMode {
474 SingleLine,
475 AutoHeight {
476 min_lines: usize,
477 max_lines: Option<usize>,
478 },
479 Full {
480 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
481 scale_ui_elements_with_buffer_font_size: bool,
482 /// When set to `true`, the editor will render a background for the active line.
483 show_active_line_background: bool,
484 /// Determines the sizing behavior for this editor
485 sizing_behavior: SizingBehavior,
486 },
487 Minimap {
488 parent: WeakEntity<Editor>,
489 },
490}
491
492impl EditorMode {
493 pub fn full() -> Self {
494 Self::Full {
495 scale_ui_elements_with_buffer_font_size: true,
496 show_active_line_background: true,
497 sizing_behavior: SizingBehavior::Default,
498 }
499 }
500
501 #[inline]
502 pub fn is_full(&self) -> bool {
503 matches!(self, Self::Full { .. })
504 }
505
506 #[inline]
507 pub fn is_single_line(&self) -> bool {
508 matches!(self, Self::SingleLine { .. })
509 }
510
511 #[inline]
512 fn is_minimap(&self) -> bool {
513 matches!(self, Self::Minimap { .. })
514 }
515}
516
517#[derive(Copy, Clone, Debug)]
518pub enum SoftWrap {
519 /// Prefer not to wrap at all.
520 ///
521 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
522 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
523 GitDiff,
524 /// Prefer a single line generally, unless an overly long line is encountered.
525 None,
526 /// Soft wrap lines that exceed the editor width.
527 EditorWidth,
528 /// Soft wrap lines at the preferred line length.
529 Column(u32),
530 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
531 Bounded(u32),
532}
533
534#[derive(Clone)]
535pub struct EditorStyle {
536 pub background: Hsla,
537 pub border: Hsla,
538 pub local_player: PlayerColor,
539 pub text: TextStyle,
540 pub scrollbar_width: Pixels,
541 pub syntax: Arc<SyntaxTheme>,
542 pub status: StatusColors,
543 pub inlay_hints_style: HighlightStyle,
544 pub edit_prediction_styles: EditPredictionStyles,
545 pub unnecessary_code_fade: f32,
546 pub show_underlines: bool,
547}
548
549impl Default for EditorStyle {
550 fn default() -> Self {
551 Self {
552 background: Hsla::default(),
553 border: Hsla::default(),
554 local_player: PlayerColor::default(),
555 text: TextStyle::default(),
556 scrollbar_width: Pixels::default(),
557 syntax: Default::default(),
558 // HACK: Status colors don't have a real default.
559 // We should look into removing the status colors from the editor
560 // style and retrieve them directly from the theme.
561 status: StatusColors::dark(),
562 inlay_hints_style: HighlightStyle::default(),
563 edit_prediction_styles: EditPredictionStyles {
564 insertion: HighlightStyle::default(),
565 whitespace: HighlightStyle::default(),
566 },
567 unnecessary_code_fade: Default::default(),
568 show_underlines: true,
569 }
570 }
571}
572
573pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
574 let show_background = language_settings::language_settings(None, None, cx)
575 .inlay_hints
576 .show_background;
577
578 let mut style = cx.theme().syntax().get("hint");
579
580 if style.color.is_none() {
581 style.color = Some(cx.theme().status().hint);
582 }
583
584 if !show_background {
585 style.background_color = None;
586 return style;
587 }
588
589 if style.background_color.is_none() {
590 style.background_color = Some(cx.theme().status().hint_background);
591 }
592
593 style
594}
595
596pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
597 EditPredictionStyles {
598 insertion: HighlightStyle {
599 color: Some(cx.theme().status().predictive),
600 ..HighlightStyle::default()
601 },
602 whitespace: HighlightStyle {
603 background_color: Some(cx.theme().status().created_background),
604 ..HighlightStyle::default()
605 },
606 }
607}
608
609type CompletionId = usize;
610
611pub(crate) enum EditDisplayMode {
612 TabAccept,
613 DiffPopover,
614 Inline,
615}
616
617enum EditPrediction {
618 Edit {
619 edits: Vec<(Range<Anchor>, Arc<str>)>,
620 edit_preview: Option<EditPreview>,
621 display_mode: EditDisplayMode,
622 snapshot: BufferSnapshot,
623 },
624 /// Move to a specific location in the active editor
625 MoveWithin {
626 target: Anchor,
627 snapshot: BufferSnapshot,
628 },
629 /// Move to a specific location in a different editor (not the active one)
630 MoveOutside {
631 target: language::Anchor,
632 snapshot: BufferSnapshot,
633 },
634}
635
636struct EditPredictionState {
637 inlay_ids: Vec<InlayId>,
638 completion: EditPrediction,
639 completion_id: Option<SharedString>,
640 invalidation_range: Option<Range<Anchor>>,
641}
642
643enum EditPredictionSettings {
644 Disabled,
645 Enabled {
646 show_in_menu: bool,
647 preview_requires_modifier: bool,
648 },
649}
650
651enum EditPredictionHighlight {}
652
653#[derive(Debug, Clone)]
654struct InlineDiagnostic {
655 message: SharedString,
656 group_id: usize,
657 is_primary: bool,
658 start: Point,
659 severity: lsp::DiagnosticSeverity,
660}
661
662pub enum MenuEditPredictionsPolicy {
663 Never,
664 ByProvider,
665}
666
667pub enum EditPredictionPreview {
668 /// Modifier is not pressed
669 Inactive { released_too_fast: bool },
670 /// Modifier pressed
671 Active {
672 since: Instant,
673 previous_scroll_position: Option<ScrollAnchor>,
674 },
675}
676
677impl EditPredictionPreview {
678 pub fn released_too_fast(&self) -> bool {
679 match self {
680 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
681 EditPredictionPreview::Active { .. } => false,
682 }
683 }
684
685 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
686 if let EditPredictionPreview::Active {
687 previous_scroll_position,
688 ..
689 } = self
690 {
691 *previous_scroll_position = scroll_position;
692 }
693 }
694}
695
696pub struct ContextMenuOptions {
697 pub min_entries_visible: usize,
698 pub max_entries_visible: usize,
699 pub placement: Option<ContextMenuPlacement>,
700}
701
702#[derive(Debug, Clone, PartialEq, Eq)]
703pub enum ContextMenuPlacement {
704 Above,
705 Below,
706}
707
708#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
709struct EditorActionId(usize);
710
711impl EditorActionId {
712 pub fn post_inc(&mut self) -> Self {
713 let answer = self.0;
714
715 *self = Self(answer + 1);
716
717 Self(answer)
718 }
719}
720
721// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
722// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
723
724type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
725type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
726
727#[derive(Default)]
728struct ScrollbarMarkerState {
729 scrollbar_size: Size<Pixels>,
730 dirty: bool,
731 markers: Arc<[PaintQuad]>,
732 pending_refresh: Option<Task<Result<()>>>,
733}
734
735impl ScrollbarMarkerState {
736 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
737 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
738 }
739}
740
741#[derive(Clone, Copy, PartialEq, Eq)]
742pub enum MinimapVisibility {
743 Disabled,
744 Enabled {
745 /// The configuration currently present in the users settings.
746 setting_configuration: bool,
747 /// Whether to override the currently set visibility from the users setting.
748 toggle_override: bool,
749 },
750}
751
752impl MinimapVisibility {
753 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
754 if mode.is_full() {
755 Self::Enabled {
756 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
757 toggle_override: false,
758 }
759 } else {
760 Self::Disabled
761 }
762 }
763
764 fn hidden(&self) -> Self {
765 match *self {
766 Self::Enabled {
767 setting_configuration,
768 ..
769 } => Self::Enabled {
770 setting_configuration,
771 toggle_override: setting_configuration,
772 },
773 Self::Disabled => Self::Disabled,
774 }
775 }
776
777 fn disabled(&self) -> bool {
778 matches!(*self, Self::Disabled)
779 }
780
781 fn settings_visibility(&self) -> bool {
782 match *self {
783 Self::Enabled {
784 setting_configuration,
785 ..
786 } => setting_configuration,
787 _ => false,
788 }
789 }
790
791 fn visible(&self) -> bool {
792 match *self {
793 Self::Enabled {
794 setting_configuration,
795 toggle_override,
796 } => setting_configuration ^ toggle_override,
797 _ => false,
798 }
799 }
800
801 fn toggle_visibility(&self) -> Self {
802 match *self {
803 Self::Enabled {
804 toggle_override,
805 setting_configuration,
806 } => Self::Enabled {
807 setting_configuration,
808 toggle_override: !toggle_override,
809 },
810 Self::Disabled => Self::Disabled,
811 }
812 }
813}
814
815#[derive(Debug, Clone, Copy, PartialEq, Eq)]
816pub enum BufferSerialization {
817 All,
818 NonDirtyBuffers,
819}
820
821impl BufferSerialization {
822 fn new(restore_unsaved_buffers: bool) -> Self {
823 if restore_unsaved_buffers {
824 Self::All
825 } else {
826 Self::NonDirtyBuffers
827 }
828 }
829}
830
831#[derive(Clone, Debug)]
832struct RunnableTasks {
833 templates: Vec<(TaskSourceKind, TaskTemplate)>,
834 offset: multi_buffer::Anchor,
835 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
836 column: u32,
837 // Values of all named captures, including those starting with '_'
838 extra_variables: HashMap<String, String>,
839 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
840 context_range: Range<BufferOffset>,
841}
842
843impl RunnableTasks {
844 fn resolve<'a>(
845 &'a self,
846 cx: &'a task::TaskContext,
847 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
848 self.templates.iter().filter_map(|(kind, template)| {
849 template
850 .resolve_task(&kind.to_id_base(), cx)
851 .map(|task| (kind.clone(), task))
852 })
853 }
854}
855
856#[derive(Clone)]
857pub struct ResolvedTasks {
858 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
859 position: Anchor,
860}
861
862#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
863struct BufferOffset(usize);
864
865/// Addons allow storing per-editor state in other crates (e.g. Vim)
866pub trait Addon: 'static {
867 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
868
869 fn render_buffer_header_controls(
870 &self,
871 _: &ExcerptInfo,
872 _: &Window,
873 _: &App,
874 ) -> Option<AnyElement> {
875 None
876 }
877
878 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
879 None
880 }
881
882 fn to_any(&self) -> &dyn std::any::Any;
883
884 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
885 None
886 }
887}
888
889struct ChangeLocation {
890 current: Option<Vec<Anchor>>,
891 original: Vec<Anchor>,
892}
893impl ChangeLocation {
894 fn locations(&self) -> &[Anchor] {
895 self.current.as_ref().unwrap_or(&self.original)
896 }
897}
898
899/// A set of caret positions, registered when the editor was edited.
900pub struct ChangeList {
901 changes: Vec<ChangeLocation>,
902 /// Currently "selected" change.
903 position: Option<usize>,
904}
905
906impl ChangeList {
907 pub fn new() -> Self {
908 Self {
909 changes: Vec::new(),
910 position: None,
911 }
912 }
913
914 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
915 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
916 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
917 if self.changes.is_empty() {
918 return None;
919 }
920
921 let prev = self.position.unwrap_or(self.changes.len());
922 let next = if direction == Direction::Prev {
923 prev.saturating_sub(count)
924 } else {
925 (prev + count).min(self.changes.len() - 1)
926 };
927 self.position = Some(next);
928 self.changes.get(next).map(|change| change.locations())
929 }
930
931 /// Adds a new change to the list, resetting the change list position.
932 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
933 self.position.take();
934 if let Some(last) = self.changes.last_mut()
935 && group
936 {
937 last.current = Some(new_positions)
938 } else {
939 self.changes.push(ChangeLocation {
940 original: new_positions,
941 current: None,
942 });
943 }
944 }
945
946 pub fn last(&self) -> Option<&[Anchor]> {
947 self.changes.last().map(|change| change.locations())
948 }
949
950 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
951 self.changes.last().map(|change| change.original.as_slice())
952 }
953
954 pub fn invert_last_group(&mut self) {
955 if let Some(last) = self.changes.last_mut()
956 && let Some(current) = last.current.as_mut()
957 {
958 mem::swap(&mut last.original, current);
959 }
960 }
961}
962
963#[derive(Clone)]
964struct InlineBlamePopoverState {
965 scroll_handle: ScrollHandle,
966 commit_message: Option<ParsedCommitMessage>,
967 markdown: Entity<Markdown>,
968}
969
970struct InlineBlamePopover {
971 position: gpui::Point<Pixels>,
972 hide_task: Option<Task<()>>,
973 popover_bounds: Option<Bounds<Pixels>>,
974 popover_state: InlineBlamePopoverState,
975 keyboard_grace: bool,
976}
977
978enum SelectionDragState {
979 /// State when no drag related activity is detected.
980 None,
981 /// State when the mouse is down on a selection that is about to be dragged.
982 ReadyToDrag {
983 selection: Selection<Anchor>,
984 click_position: gpui::Point<Pixels>,
985 mouse_down_time: Instant,
986 },
987 /// State when the mouse is dragging the selection in the editor.
988 Dragging {
989 selection: Selection<Anchor>,
990 drop_cursor: Selection<Anchor>,
991 hide_drop_cursor: bool,
992 },
993}
994
995enum ColumnarSelectionState {
996 FromMouse {
997 selection_tail: Anchor,
998 display_point: Option<DisplayPoint>,
999 },
1000 FromSelection {
1001 selection_tail: Anchor,
1002 },
1003}
1004
1005/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1006/// a breakpoint on them.
1007#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1008struct PhantomBreakpointIndicator {
1009 display_row: DisplayRow,
1010 /// There's a small debounce between hovering over the line and showing the indicator.
1011 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1012 is_active: bool,
1013 collides_with_existing_breakpoint: bool,
1014}
1015
1016/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1017///
1018/// See the [module level documentation](self) for more information.
1019pub struct Editor {
1020 focus_handle: FocusHandle,
1021 last_focused_descendant: Option<WeakFocusHandle>,
1022 /// The text buffer being edited
1023 buffer: Entity<MultiBuffer>,
1024 /// Map of how text in the buffer should be displayed.
1025 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1026 pub display_map: Entity<DisplayMap>,
1027 placeholder_display_map: Option<Entity<DisplayMap>>,
1028 pub selections: SelectionsCollection,
1029 pub scroll_manager: ScrollManager,
1030 /// When inline assist editors are linked, they all render cursors because
1031 /// typing enters text into each of them, even the ones that aren't focused.
1032 pub(crate) show_cursor_when_unfocused: bool,
1033 columnar_selection_state: Option<ColumnarSelectionState>,
1034 add_selections_state: Option<AddSelectionsState>,
1035 select_next_state: Option<SelectNextState>,
1036 select_prev_state: Option<SelectNextState>,
1037 selection_history: SelectionHistory,
1038 defer_selection_effects: bool,
1039 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1040 autoclose_regions: Vec<AutocloseRegion>,
1041 snippet_stack: InvalidationStack<SnippetState>,
1042 select_syntax_node_history: SelectSyntaxNodeHistory,
1043 ime_transaction: Option<TransactionId>,
1044 pub diagnostics_max_severity: DiagnosticSeverity,
1045 active_diagnostics: ActiveDiagnostic,
1046 show_inline_diagnostics: bool,
1047 inline_diagnostics_update: Task<()>,
1048 inline_diagnostics_enabled: bool,
1049 diagnostics_enabled: bool,
1050 word_completions_enabled: bool,
1051 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1052 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1053 hard_wrap: Option<usize>,
1054 project: Option<Entity<Project>>,
1055 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1056 completion_provider: Option<Rc<dyn CompletionProvider>>,
1057 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1058 blink_manager: Entity<BlinkManager>,
1059 show_cursor_names: bool,
1060 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1061 pub show_local_selections: bool,
1062 mode: EditorMode,
1063 show_breadcrumbs: bool,
1064 show_gutter: bool,
1065 show_scrollbars: ScrollbarAxes,
1066 minimap_visibility: MinimapVisibility,
1067 offset_content: bool,
1068 disable_expand_excerpt_buttons: bool,
1069 show_line_numbers: Option<bool>,
1070 use_relative_line_numbers: Option<bool>,
1071 show_git_diff_gutter: Option<bool>,
1072 show_code_actions: Option<bool>,
1073 show_runnables: Option<bool>,
1074 show_breakpoints: Option<bool>,
1075 show_wrap_guides: Option<bool>,
1076 show_indent_guides: Option<bool>,
1077 highlight_order: usize,
1078 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1079 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1080 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1081 scrollbar_marker_state: ScrollbarMarkerState,
1082 active_indent_guides_state: ActiveIndentGuidesState,
1083 nav_history: Option<ItemNavHistory>,
1084 context_menu: RefCell<Option<CodeContextMenu>>,
1085 context_menu_options: Option<ContextMenuOptions>,
1086 mouse_context_menu: Option<MouseContextMenu>,
1087 completion_tasks: Vec<(CompletionId, Task<()>)>,
1088 inline_blame_popover: Option<InlineBlamePopover>,
1089 inline_blame_popover_show_task: Option<Task<()>>,
1090 signature_help_state: SignatureHelpState,
1091 auto_signature_help: Option<bool>,
1092 find_all_references_task_sources: Vec<Anchor>,
1093 next_completion_id: CompletionId,
1094 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1095 code_actions_task: Option<Task<Result<()>>>,
1096 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1097 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1098 document_highlights_task: Option<Task<()>>,
1099 linked_editing_range_task: Option<Task<Option<()>>>,
1100 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1101 pending_rename: Option<RenameState>,
1102 searchable: bool,
1103 cursor_shape: CursorShape,
1104 current_line_highlight: Option<CurrentLineHighlight>,
1105 autoindent_mode: Option<AutoindentMode>,
1106 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1107 input_enabled: bool,
1108 use_modal_editing: bool,
1109 read_only: bool,
1110 leader_id: Option<CollaboratorId>,
1111 remote_id: Option<ViewId>,
1112 pub hover_state: HoverState,
1113 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1114 gutter_hovered: bool,
1115 hovered_link_state: Option<HoveredLinkState>,
1116 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1117 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1118 active_edit_prediction: Option<EditPredictionState>,
1119 /// Used to prevent flickering as the user types while the menu is open
1120 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1121 edit_prediction_settings: EditPredictionSettings,
1122 edit_predictions_hidden_for_vim_mode: bool,
1123 show_edit_predictions_override: Option<bool>,
1124 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1125 edit_prediction_preview: EditPredictionPreview,
1126 edit_prediction_indent_conflict: bool,
1127 edit_prediction_requires_modifier_in_indent_conflict: bool,
1128 next_inlay_id: usize,
1129 next_color_inlay_id: usize,
1130 _subscriptions: Vec<Subscription>,
1131 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1132 gutter_dimensions: GutterDimensions,
1133 style: Option<EditorStyle>,
1134 text_style_refinement: Option<TextStyleRefinement>,
1135 next_editor_action_id: EditorActionId,
1136 editor_actions: Rc<
1137 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1138 >,
1139 use_autoclose: bool,
1140 use_auto_surround: bool,
1141 auto_replace_emoji_shortcode: bool,
1142 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1143 show_git_blame_gutter: bool,
1144 show_git_blame_inline: bool,
1145 show_git_blame_inline_delay_task: Option<Task<()>>,
1146 git_blame_inline_enabled: bool,
1147 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1148 buffer_serialization: Option<BufferSerialization>,
1149 show_selection_menu: Option<bool>,
1150 blame: Option<Entity<GitBlame>>,
1151 blame_subscription: Option<Subscription>,
1152 custom_context_menu: Option<
1153 Box<
1154 dyn 'static
1155 + Fn(
1156 &mut Self,
1157 DisplayPoint,
1158 &mut Window,
1159 &mut Context<Self>,
1160 ) -> Option<Entity<ui::ContextMenu>>,
1161 >,
1162 >,
1163 last_bounds: Option<Bounds<Pixels>>,
1164 last_position_map: Option<Rc<PositionMap>>,
1165 expect_bounds_change: Option<Bounds<Pixels>>,
1166 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1167 tasks_update_task: Option<Task<()>>,
1168 breakpoint_store: Option<Entity<BreakpointStore>>,
1169 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1170 hovered_diff_hunk_row: Option<DisplayRow>,
1171 pull_diagnostics_task: Task<()>,
1172 in_project_search: bool,
1173 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1174 breadcrumb_header: Option<String>,
1175 focused_block: Option<FocusedBlock>,
1176 next_scroll_position: NextScrollCursorCenterTopBottom,
1177 addons: HashMap<TypeId, Box<dyn Addon>>,
1178 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1179 load_diff_task: Option<Shared<Task<()>>>,
1180 /// Whether we are temporarily displaying a diff other than git's
1181 temporary_diff_override: bool,
1182 selection_mark_mode: bool,
1183 toggle_fold_multiple_buffers: Task<()>,
1184 _scroll_cursor_center_top_bottom_task: Task<()>,
1185 serialize_selections: Task<()>,
1186 serialize_folds: Task<()>,
1187 mouse_cursor_hidden: bool,
1188 minimap: Option<Entity<Self>>,
1189 hide_mouse_mode: HideMouseMode,
1190 pub change_list: ChangeList,
1191 inline_value_cache: InlineValueCache,
1192 selection_drag_state: SelectionDragState,
1193 colors: Option<LspColorData>,
1194 post_scroll_update: Task<()>,
1195 refresh_colors_task: Task<()>,
1196 inlay_hints: Option<LspInlayHintData>,
1197 folding_newlines: Task<()>,
1198 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1199}
1200
1201fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1202 if debounce_ms > 0 {
1203 Some(Duration::from_millis(debounce_ms))
1204 } else {
1205 None
1206 }
1207}
1208
1209#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1210enum NextScrollCursorCenterTopBottom {
1211 #[default]
1212 Center,
1213 Top,
1214 Bottom,
1215}
1216
1217impl NextScrollCursorCenterTopBottom {
1218 fn next(&self) -> Self {
1219 match self {
1220 Self::Center => Self::Top,
1221 Self::Top => Self::Bottom,
1222 Self::Bottom => Self::Center,
1223 }
1224 }
1225}
1226
1227#[derive(Clone)]
1228pub struct EditorSnapshot {
1229 pub mode: EditorMode,
1230 show_gutter: bool,
1231 show_line_numbers: Option<bool>,
1232 show_git_diff_gutter: Option<bool>,
1233 show_code_actions: Option<bool>,
1234 show_runnables: Option<bool>,
1235 show_breakpoints: Option<bool>,
1236 git_blame_gutter_max_author_length: Option<usize>,
1237 pub display_snapshot: DisplaySnapshot,
1238 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1239 is_focused: bool,
1240 scroll_anchor: ScrollAnchor,
1241 ongoing_scroll: OngoingScroll,
1242 current_line_highlight: CurrentLineHighlight,
1243 gutter_hovered: bool,
1244}
1245
1246#[derive(Default, Debug, Clone, Copy)]
1247pub struct GutterDimensions {
1248 pub left_padding: Pixels,
1249 pub right_padding: Pixels,
1250 pub width: Pixels,
1251 pub margin: Pixels,
1252 pub git_blame_entries_width: Option<Pixels>,
1253}
1254
1255impl GutterDimensions {
1256 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1257 Self {
1258 margin: Self::default_gutter_margin(font_id, font_size, cx),
1259 ..Default::default()
1260 }
1261 }
1262
1263 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1264 -cx.text_system().descent(font_id, font_size)
1265 }
1266 /// The full width of the space taken up by the gutter.
1267 pub fn full_width(&self) -> Pixels {
1268 self.margin + self.width
1269 }
1270
1271 /// The width of the space reserved for the fold indicators,
1272 /// use alongside 'justify_end' and `gutter_width` to
1273 /// right align content with the line numbers
1274 pub fn fold_area_width(&self) -> Pixels {
1275 self.margin + self.right_padding
1276 }
1277}
1278
1279struct CharacterDimensions {
1280 em_width: Pixels,
1281 em_advance: Pixels,
1282 line_height: Pixels,
1283}
1284
1285#[derive(Debug)]
1286pub struct RemoteSelection {
1287 pub replica_id: ReplicaId,
1288 pub selection: Selection<Anchor>,
1289 pub cursor_shape: CursorShape,
1290 pub collaborator_id: CollaboratorId,
1291 pub line_mode: bool,
1292 pub user_name: Option<SharedString>,
1293 pub color: PlayerColor,
1294}
1295
1296#[derive(Clone, Debug)]
1297struct SelectionHistoryEntry {
1298 selections: Arc<[Selection<Anchor>]>,
1299 select_next_state: Option<SelectNextState>,
1300 select_prev_state: Option<SelectNextState>,
1301 add_selections_state: Option<AddSelectionsState>,
1302}
1303
1304#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1305enum SelectionHistoryMode {
1306 Normal,
1307 Undoing,
1308 Redoing,
1309 Skipping,
1310}
1311
1312#[derive(Clone, PartialEq, Eq, Hash)]
1313struct HoveredCursor {
1314 replica_id: ReplicaId,
1315 selection_id: usize,
1316}
1317
1318impl Default for SelectionHistoryMode {
1319 fn default() -> Self {
1320 Self::Normal
1321 }
1322}
1323
1324#[derive(Debug)]
1325/// SelectionEffects controls the side-effects of updating the selection.
1326///
1327/// The default behaviour does "what you mostly want":
1328/// - it pushes to the nav history if the cursor moved by >10 lines
1329/// - it re-triggers completion requests
1330/// - it scrolls to fit
1331///
1332/// You might want to modify these behaviours. For example when doing a "jump"
1333/// like go to definition, we always want to add to nav history; but when scrolling
1334/// in vim mode we never do.
1335///
1336/// Similarly, you might want to disable scrolling if you don't want the viewport to
1337/// move.
1338#[derive(Clone)]
1339pub struct SelectionEffects {
1340 nav_history: Option<bool>,
1341 completions: bool,
1342 scroll: Option<Autoscroll>,
1343}
1344
1345impl Default for SelectionEffects {
1346 fn default() -> Self {
1347 Self {
1348 nav_history: None,
1349 completions: true,
1350 scroll: Some(Autoscroll::fit()),
1351 }
1352 }
1353}
1354impl SelectionEffects {
1355 pub fn scroll(scroll: Autoscroll) -> Self {
1356 Self {
1357 scroll: Some(scroll),
1358 ..Default::default()
1359 }
1360 }
1361
1362 pub fn no_scroll() -> Self {
1363 Self {
1364 scroll: None,
1365 ..Default::default()
1366 }
1367 }
1368
1369 pub fn completions(self, completions: bool) -> Self {
1370 Self {
1371 completions,
1372 ..self
1373 }
1374 }
1375
1376 pub fn nav_history(self, nav_history: bool) -> Self {
1377 Self {
1378 nav_history: Some(nav_history),
1379 ..self
1380 }
1381 }
1382}
1383
1384struct DeferredSelectionEffectsState {
1385 changed: bool,
1386 effects: SelectionEffects,
1387 old_cursor_position: Anchor,
1388 history_entry: SelectionHistoryEntry,
1389}
1390
1391#[derive(Default)]
1392struct SelectionHistory {
1393 #[allow(clippy::type_complexity)]
1394 selections_by_transaction:
1395 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1396 mode: SelectionHistoryMode,
1397 undo_stack: VecDeque<SelectionHistoryEntry>,
1398 redo_stack: VecDeque<SelectionHistoryEntry>,
1399}
1400
1401impl SelectionHistory {
1402 #[track_caller]
1403 fn insert_transaction(
1404 &mut self,
1405 transaction_id: TransactionId,
1406 selections: Arc<[Selection<Anchor>]>,
1407 ) {
1408 if selections.is_empty() {
1409 log::error!(
1410 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1411 std::panic::Location::caller()
1412 );
1413 return;
1414 }
1415 self.selections_by_transaction
1416 .insert(transaction_id, (selections, None));
1417 }
1418
1419 #[allow(clippy::type_complexity)]
1420 fn transaction(
1421 &self,
1422 transaction_id: TransactionId,
1423 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1424 self.selections_by_transaction.get(&transaction_id)
1425 }
1426
1427 #[allow(clippy::type_complexity)]
1428 fn transaction_mut(
1429 &mut self,
1430 transaction_id: TransactionId,
1431 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1432 self.selections_by_transaction.get_mut(&transaction_id)
1433 }
1434
1435 fn push(&mut self, entry: SelectionHistoryEntry) {
1436 if !entry.selections.is_empty() {
1437 match self.mode {
1438 SelectionHistoryMode::Normal => {
1439 self.push_undo(entry);
1440 self.redo_stack.clear();
1441 }
1442 SelectionHistoryMode::Undoing => self.push_redo(entry),
1443 SelectionHistoryMode::Redoing => self.push_undo(entry),
1444 SelectionHistoryMode::Skipping => {}
1445 }
1446 }
1447 }
1448
1449 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1450 if self
1451 .undo_stack
1452 .back()
1453 .is_none_or(|e| e.selections != entry.selections)
1454 {
1455 self.undo_stack.push_back(entry);
1456 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1457 self.undo_stack.pop_front();
1458 }
1459 }
1460 }
1461
1462 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1463 if self
1464 .redo_stack
1465 .back()
1466 .is_none_or(|e| e.selections != entry.selections)
1467 {
1468 self.redo_stack.push_back(entry);
1469 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1470 self.redo_stack.pop_front();
1471 }
1472 }
1473 }
1474}
1475
1476#[derive(Clone, Copy)]
1477pub struct RowHighlightOptions {
1478 pub autoscroll: bool,
1479 pub include_gutter: bool,
1480}
1481
1482impl Default for RowHighlightOptions {
1483 fn default() -> Self {
1484 Self {
1485 autoscroll: Default::default(),
1486 include_gutter: true,
1487 }
1488 }
1489}
1490
1491struct RowHighlight {
1492 index: usize,
1493 range: Range<Anchor>,
1494 color: Hsla,
1495 options: RowHighlightOptions,
1496 type_id: TypeId,
1497}
1498
1499#[derive(Clone, Debug)]
1500struct AddSelectionsState {
1501 groups: Vec<AddSelectionsGroup>,
1502}
1503
1504#[derive(Clone, Debug)]
1505struct AddSelectionsGroup {
1506 above: bool,
1507 stack: Vec<usize>,
1508}
1509
1510#[derive(Clone)]
1511struct SelectNextState {
1512 query: AhoCorasick,
1513 wordwise: bool,
1514 done: bool,
1515}
1516
1517impl std::fmt::Debug for SelectNextState {
1518 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1519 f.debug_struct(std::any::type_name::<Self>())
1520 .field("wordwise", &self.wordwise)
1521 .field("done", &self.done)
1522 .finish()
1523 }
1524}
1525
1526#[derive(Debug)]
1527struct AutocloseRegion {
1528 selection_id: usize,
1529 range: Range<Anchor>,
1530 pair: BracketPair,
1531}
1532
1533#[derive(Debug)]
1534struct SnippetState {
1535 ranges: Vec<Vec<Range<Anchor>>>,
1536 active_index: usize,
1537 choices: Vec<Option<Vec<String>>>,
1538}
1539
1540#[doc(hidden)]
1541pub struct RenameState {
1542 pub range: Range<Anchor>,
1543 pub old_name: Arc<str>,
1544 pub editor: Entity<Editor>,
1545 block_id: CustomBlockId,
1546}
1547
1548struct InvalidationStack<T>(Vec<T>);
1549
1550struct RegisteredEditPredictionProvider {
1551 provider: Arc<dyn EditPredictionProviderHandle>,
1552 _subscription: Subscription,
1553}
1554
1555#[derive(Debug, PartialEq, Eq)]
1556pub struct ActiveDiagnosticGroup {
1557 pub active_range: Range<Anchor>,
1558 pub active_message: String,
1559 pub group_id: usize,
1560 pub blocks: HashSet<CustomBlockId>,
1561}
1562
1563#[derive(Debug, PartialEq, Eq)]
1564
1565pub(crate) enum ActiveDiagnostic {
1566 None,
1567 All,
1568 Group(ActiveDiagnosticGroup),
1569}
1570
1571#[derive(Serialize, Deserialize, Clone, Debug)]
1572pub struct ClipboardSelection {
1573 /// The number of bytes in this selection.
1574 pub len: usize,
1575 /// Whether this was a full-line selection.
1576 pub is_entire_line: bool,
1577 /// The indentation of the first line when this content was originally copied.
1578 pub first_line_indent: u32,
1579}
1580
1581// selections, scroll behavior, was newest selection reversed
1582type SelectSyntaxNodeHistoryState = (
1583 Box<[Selection<usize>]>,
1584 SelectSyntaxNodeScrollBehavior,
1585 bool,
1586);
1587
1588#[derive(Default)]
1589struct SelectSyntaxNodeHistory {
1590 stack: Vec<SelectSyntaxNodeHistoryState>,
1591 // disable temporarily to allow changing selections without losing the stack
1592 pub disable_clearing: bool,
1593}
1594
1595impl SelectSyntaxNodeHistory {
1596 pub fn try_clear(&mut self) {
1597 if !self.disable_clearing {
1598 self.stack.clear();
1599 }
1600 }
1601
1602 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1603 self.stack.push(selection);
1604 }
1605
1606 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1607 self.stack.pop()
1608 }
1609}
1610
1611enum SelectSyntaxNodeScrollBehavior {
1612 CursorTop,
1613 FitSelection,
1614 CursorBottom,
1615}
1616
1617#[derive(Debug)]
1618pub(crate) struct NavigationData {
1619 cursor_anchor: Anchor,
1620 cursor_position: Point,
1621 scroll_anchor: ScrollAnchor,
1622 scroll_top_row: u32,
1623}
1624
1625#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1626pub enum GotoDefinitionKind {
1627 Symbol,
1628 Declaration,
1629 Type,
1630 Implementation,
1631}
1632
1633pub enum FormatTarget {
1634 Buffers(HashSet<Entity<Buffer>>),
1635 Ranges(Vec<Range<MultiBufferPoint>>),
1636}
1637
1638pub(crate) struct FocusedBlock {
1639 id: BlockId,
1640 focus_handle: WeakFocusHandle,
1641}
1642
1643#[derive(Clone)]
1644enum JumpData {
1645 MultiBufferRow {
1646 row: MultiBufferRow,
1647 line_offset_from_top: u32,
1648 },
1649 MultiBufferPoint {
1650 excerpt_id: ExcerptId,
1651 position: Point,
1652 anchor: text::Anchor,
1653 line_offset_from_top: u32,
1654 },
1655}
1656
1657pub enum MultibufferSelectionMode {
1658 First,
1659 All,
1660}
1661
1662#[derive(Clone, Copy, Debug, Default)]
1663pub struct RewrapOptions {
1664 pub override_language_settings: bool,
1665 pub preserve_existing_whitespace: bool,
1666}
1667
1668impl Editor {
1669 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1670 let buffer = cx.new(|cx| Buffer::local("", cx));
1671 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1672 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1673 }
1674
1675 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1676 let buffer = cx.new(|cx| Buffer::local("", cx));
1677 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1678 Self::new(EditorMode::full(), buffer, None, window, cx)
1679 }
1680
1681 pub fn auto_height(
1682 min_lines: usize,
1683 max_lines: usize,
1684 window: &mut Window,
1685 cx: &mut Context<Self>,
1686 ) -> Self {
1687 let buffer = cx.new(|cx| Buffer::local("", cx));
1688 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1689 Self::new(
1690 EditorMode::AutoHeight {
1691 min_lines,
1692 max_lines: Some(max_lines),
1693 },
1694 buffer,
1695 None,
1696 window,
1697 cx,
1698 )
1699 }
1700
1701 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1702 /// The editor grows as tall as needed to fit its content.
1703 pub fn auto_height_unbounded(
1704 min_lines: usize,
1705 window: &mut Window,
1706 cx: &mut Context<Self>,
1707 ) -> Self {
1708 let buffer = cx.new(|cx| Buffer::local("", cx));
1709 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1710 Self::new(
1711 EditorMode::AutoHeight {
1712 min_lines,
1713 max_lines: None,
1714 },
1715 buffer,
1716 None,
1717 window,
1718 cx,
1719 )
1720 }
1721
1722 pub fn for_buffer(
1723 buffer: Entity<Buffer>,
1724 project: Option<Entity<Project>>,
1725 window: &mut Window,
1726 cx: &mut Context<Self>,
1727 ) -> Self {
1728 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1729 Self::new(EditorMode::full(), buffer, project, window, cx)
1730 }
1731
1732 pub fn for_multibuffer(
1733 buffer: Entity<MultiBuffer>,
1734 project: Option<Entity<Project>>,
1735 window: &mut Window,
1736 cx: &mut Context<Self>,
1737 ) -> Self {
1738 Self::new(EditorMode::full(), buffer, project, window, cx)
1739 }
1740
1741 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1742 let mut clone = Self::new(
1743 self.mode.clone(),
1744 self.buffer.clone(),
1745 self.project.clone(),
1746 window,
1747 cx,
1748 );
1749 self.display_map.update(cx, |display_map, cx| {
1750 let snapshot = display_map.snapshot(cx);
1751 clone.display_map.update(cx, |display_map, cx| {
1752 display_map.set_state(&snapshot, cx);
1753 });
1754 });
1755 clone.folds_did_change(cx);
1756 clone.selections.clone_state(&self.selections);
1757 clone.scroll_manager.clone_state(&self.scroll_manager);
1758 clone.searchable = self.searchable;
1759 clone.read_only = self.read_only;
1760 clone
1761 }
1762
1763 pub fn new(
1764 mode: EditorMode,
1765 buffer: Entity<MultiBuffer>,
1766 project: Option<Entity<Project>>,
1767 window: &mut Window,
1768 cx: &mut Context<Self>,
1769 ) -> Self {
1770 Editor::new_internal(mode, buffer, project, None, window, cx)
1771 }
1772
1773 fn new_internal(
1774 mode: EditorMode,
1775 multi_buffer: Entity<MultiBuffer>,
1776 project: Option<Entity<Project>>,
1777 display_map: Option<Entity<DisplayMap>>,
1778 window: &mut Window,
1779 cx: &mut Context<Self>,
1780 ) -> Self {
1781 debug_assert!(
1782 display_map.is_none() || mode.is_minimap(),
1783 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1784 );
1785
1786 let full_mode = mode.is_full();
1787 let is_minimap = mode.is_minimap();
1788 let diagnostics_max_severity = if full_mode {
1789 EditorSettings::get_global(cx)
1790 .diagnostics_max_severity
1791 .unwrap_or(DiagnosticSeverity::Hint)
1792 } else {
1793 DiagnosticSeverity::Off
1794 };
1795 let style = window.text_style();
1796 let font_size = style.font_size.to_pixels(window.rem_size());
1797 let editor = cx.entity().downgrade();
1798 let fold_placeholder = FoldPlaceholder {
1799 constrain_width: false,
1800 render: Arc::new(move |fold_id, fold_range, cx| {
1801 let editor = editor.clone();
1802 div()
1803 .id(fold_id)
1804 .bg(cx.theme().colors().ghost_element_background)
1805 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1806 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1807 .rounded_xs()
1808 .size_full()
1809 .cursor_pointer()
1810 .child("⋯")
1811 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1812 .on_click(move |_, _window, cx| {
1813 editor
1814 .update(cx, |editor, cx| {
1815 editor.unfold_ranges(
1816 &[fold_range.start..fold_range.end],
1817 true,
1818 false,
1819 cx,
1820 );
1821 cx.stop_propagation();
1822 })
1823 .ok();
1824 })
1825 .into_any()
1826 }),
1827 merge_adjacent: true,
1828 ..FoldPlaceholder::default()
1829 };
1830 let display_map = display_map.unwrap_or_else(|| {
1831 cx.new(|cx| {
1832 DisplayMap::new(
1833 multi_buffer.clone(),
1834 style.font(),
1835 font_size,
1836 None,
1837 FILE_HEADER_HEIGHT,
1838 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1839 fold_placeholder,
1840 diagnostics_max_severity,
1841 cx,
1842 )
1843 })
1844 });
1845
1846 let selections = SelectionsCollection::new();
1847
1848 let blink_manager = cx.new(|cx| {
1849 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1850 if is_minimap {
1851 blink_manager.disable(cx);
1852 }
1853 blink_manager
1854 });
1855
1856 let soft_wrap_mode_override =
1857 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1858
1859 let mut project_subscriptions = Vec::new();
1860 if full_mode && let Some(project) = project.as_ref() {
1861 project_subscriptions.push(cx.subscribe_in(
1862 project,
1863 window,
1864 |editor, _, event, window, cx| match event {
1865 project::Event::RefreshCodeLens => {
1866 // we always query lens with actions, without storing them, always refreshing them
1867 }
1868 project::Event::RefreshInlayHints {
1869 server_id,
1870 request_id,
1871 } => {
1872 editor.refresh_inlay_hints(
1873 InlayHintRefreshReason::RefreshRequested {
1874 server_id: *server_id,
1875 request_id: *request_id,
1876 },
1877 cx,
1878 );
1879 }
1880 project::Event::LanguageServerRemoved(..) => {
1881 if editor.tasks_update_task.is_none() {
1882 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1883 }
1884 editor.registered_buffers.clear();
1885 editor.register_visible_buffers(cx);
1886 }
1887 project::Event::LanguageServerAdded(..) => {
1888 if editor.tasks_update_task.is_none() {
1889 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1890 }
1891 }
1892 project::Event::SnippetEdit(id, snippet_edits) => {
1893 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1894 let focus_handle = editor.focus_handle(cx);
1895 if focus_handle.is_focused(window) {
1896 let snapshot = buffer.read(cx).snapshot();
1897 for (range, snippet) in snippet_edits {
1898 let editor_range =
1899 language::range_from_lsp(*range).to_offset(&snapshot);
1900 editor
1901 .insert_snippet(
1902 &[editor_range],
1903 snippet.clone(),
1904 window,
1905 cx,
1906 )
1907 .ok();
1908 }
1909 }
1910 }
1911 }
1912 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1913 let buffer_id = *buffer_id;
1914 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1915 editor.register_buffer(buffer_id, cx);
1916 editor.update_lsp_data(Some(buffer_id), window, cx);
1917 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1918 refresh_linked_ranges(editor, window, cx);
1919 editor.refresh_code_actions(window, cx);
1920 editor.refresh_document_highlights(cx);
1921 }
1922 }
1923
1924 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
1925 let Some(workspace) = editor.workspace() else {
1926 return;
1927 };
1928 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1929 else {
1930 return;
1931 };
1932
1933 if active_editor.entity_id() == cx.entity_id() {
1934 let entity_id = cx.entity_id();
1935 workspace.update(cx, |this, cx| {
1936 this.panes_mut()
1937 .iter_mut()
1938 .filter(|pane| pane.entity_id() != entity_id)
1939 .for_each(|p| {
1940 p.update(cx, |pane, _| {
1941 pane.nav_history_mut().rename_item(
1942 entity_id,
1943 project_path.clone(),
1944 abs_path.clone().into(),
1945 );
1946 })
1947 });
1948 });
1949 let edited_buffers_already_open = {
1950 let other_editors: Vec<Entity<Editor>> = workspace
1951 .read(cx)
1952 .panes()
1953 .iter()
1954 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1955 .filter(|editor| editor.entity_id() != cx.entity_id())
1956 .collect();
1957
1958 transaction.0.keys().all(|buffer| {
1959 other_editors.iter().any(|editor| {
1960 let multi_buffer = editor.read(cx).buffer();
1961 multi_buffer.read(cx).is_singleton()
1962 && multi_buffer.read(cx).as_singleton().map_or(
1963 false,
1964 |singleton| {
1965 singleton.entity_id() == buffer.entity_id()
1966 },
1967 )
1968 })
1969 })
1970 };
1971 if !edited_buffers_already_open {
1972 let workspace = workspace.downgrade();
1973 let transaction = transaction.clone();
1974 cx.defer_in(window, move |_, window, cx| {
1975 cx.spawn_in(window, async move |editor, cx| {
1976 Self::open_project_transaction(
1977 &editor,
1978 workspace,
1979 transaction,
1980 "Rename".to_string(),
1981 cx,
1982 )
1983 .await
1984 .ok()
1985 })
1986 .detach();
1987 });
1988 }
1989 }
1990 }
1991
1992 _ => {}
1993 },
1994 ));
1995 if let Some(task_inventory) = project
1996 .read(cx)
1997 .task_store()
1998 .read(cx)
1999 .task_inventory()
2000 .cloned()
2001 {
2002 project_subscriptions.push(cx.observe_in(
2003 &task_inventory,
2004 window,
2005 |editor, _, window, cx| {
2006 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2007 },
2008 ));
2009 };
2010
2011 project_subscriptions.push(cx.subscribe_in(
2012 &project.read(cx).breakpoint_store(),
2013 window,
2014 |editor, _, event, window, cx| match event {
2015 BreakpointStoreEvent::ClearDebugLines => {
2016 editor.clear_row_highlights::<ActiveDebugLine>();
2017 editor.refresh_inline_values(cx);
2018 }
2019 BreakpointStoreEvent::SetDebugLine => {
2020 if editor.go_to_active_debug_line(window, cx) {
2021 cx.stop_propagation();
2022 }
2023
2024 editor.refresh_inline_values(cx);
2025 }
2026 _ => {}
2027 },
2028 ));
2029 let git_store = project.read(cx).git_store().clone();
2030 let project = project.clone();
2031 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2032 if let GitStoreEvent::RepositoryAdded = event {
2033 this.load_diff_task = Some(
2034 update_uncommitted_diff_for_buffer(
2035 cx.entity(),
2036 &project,
2037 this.buffer.read(cx).all_buffers(),
2038 this.buffer.clone(),
2039 cx,
2040 )
2041 .shared(),
2042 );
2043 }
2044 }));
2045 }
2046
2047 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2048
2049 let inlay_hint_settings =
2050 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2051 let focus_handle = cx.focus_handle();
2052 if !is_minimap {
2053 cx.on_focus(&focus_handle, window, Self::handle_focus)
2054 .detach();
2055 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2056 .detach();
2057 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2058 .detach();
2059 cx.on_blur(&focus_handle, window, Self::handle_blur)
2060 .detach();
2061 cx.observe_pending_input(window, Self::observe_pending_input)
2062 .detach();
2063 }
2064
2065 let show_indent_guides =
2066 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2067 Some(false)
2068 } else {
2069 None
2070 };
2071
2072 let breakpoint_store = match (&mode, project.as_ref()) {
2073 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2074 _ => None,
2075 };
2076
2077 let mut code_action_providers = Vec::new();
2078 let mut load_uncommitted_diff = None;
2079 if let Some(project) = project.clone() {
2080 load_uncommitted_diff = Some(
2081 update_uncommitted_diff_for_buffer(
2082 cx.entity(),
2083 &project,
2084 multi_buffer.read(cx).all_buffers(),
2085 multi_buffer.clone(),
2086 cx,
2087 )
2088 .shared(),
2089 );
2090 code_action_providers.push(Rc::new(project) as Rc<_>);
2091 }
2092
2093 let mut editor = Self {
2094 focus_handle,
2095 show_cursor_when_unfocused: false,
2096 last_focused_descendant: None,
2097 buffer: multi_buffer.clone(),
2098 display_map: display_map.clone(),
2099 placeholder_display_map: None,
2100 selections,
2101 scroll_manager: ScrollManager::new(cx),
2102 columnar_selection_state: None,
2103 add_selections_state: None,
2104 select_next_state: None,
2105 select_prev_state: None,
2106 selection_history: SelectionHistory::default(),
2107 defer_selection_effects: false,
2108 deferred_selection_effects_state: None,
2109 autoclose_regions: Vec::new(),
2110 snippet_stack: InvalidationStack::default(),
2111 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2112 ime_transaction: None,
2113 active_diagnostics: ActiveDiagnostic::None,
2114 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2115 inline_diagnostics_update: Task::ready(()),
2116 inline_diagnostics: Vec::new(),
2117 soft_wrap_mode_override,
2118 diagnostics_max_severity,
2119 hard_wrap: None,
2120 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2121 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2122 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2123 project,
2124 blink_manager: blink_manager.clone(),
2125 show_local_selections: true,
2126 show_scrollbars: ScrollbarAxes {
2127 horizontal: full_mode,
2128 vertical: full_mode,
2129 },
2130 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2131 offset_content: !matches!(mode, EditorMode::SingleLine),
2132 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2133 show_gutter: full_mode,
2134 show_line_numbers: (!full_mode).then_some(false),
2135 use_relative_line_numbers: None,
2136 disable_expand_excerpt_buttons: !full_mode,
2137 show_git_diff_gutter: None,
2138 show_code_actions: None,
2139 show_runnables: None,
2140 show_breakpoints: None,
2141 show_wrap_guides: None,
2142 show_indent_guides,
2143 highlight_order: 0,
2144 highlighted_rows: HashMap::default(),
2145 background_highlights: HashMap::default(),
2146 gutter_highlights: HashMap::default(),
2147 scrollbar_marker_state: ScrollbarMarkerState::default(),
2148 active_indent_guides_state: ActiveIndentGuidesState::default(),
2149 nav_history: None,
2150 context_menu: RefCell::new(None),
2151 context_menu_options: None,
2152 mouse_context_menu: None,
2153 completion_tasks: Vec::new(),
2154 inline_blame_popover: None,
2155 inline_blame_popover_show_task: None,
2156 signature_help_state: SignatureHelpState::default(),
2157 auto_signature_help: None,
2158 find_all_references_task_sources: Vec::new(),
2159 next_completion_id: 0,
2160 next_inlay_id: 0,
2161 code_action_providers,
2162 available_code_actions: None,
2163 code_actions_task: None,
2164 quick_selection_highlight_task: None,
2165 debounced_selection_highlight_task: None,
2166 document_highlights_task: None,
2167 linked_editing_range_task: None,
2168 pending_rename: None,
2169 searchable: !is_minimap,
2170 cursor_shape: EditorSettings::get_global(cx)
2171 .cursor_shape
2172 .unwrap_or_default(),
2173 current_line_highlight: None,
2174 autoindent_mode: Some(AutoindentMode::EachLine),
2175
2176 workspace: None,
2177 input_enabled: !is_minimap,
2178 use_modal_editing: full_mode,
2179 read_only: is_minimap,
2180 use_autoclose: true,
2181 use_auto_surround: true,
2182 auto_replace_emoji_shortcode: false,
2183 jsx_tag_auto_close_enabled_in_any_buffer: false,
2184 leader_id: None,
2185 remote_id: None,
2186 hover_state: HoverState::default(),
2187 pending_mouse_down: None,
2188 hovered_link_state: None,
2189 edit_prediction_provider: None,
2190 active_edit_prediction: None,
2191 stale_edit_prediction_in_menu: None,
2192 edit_prediction_preview: EditPredictionPreview::Inactive {
2193 released_too_fast: false,
2194 },
2195 inline_diagnostics_enabled: full_mode,
2196 diagnostics_enabled: full_mode,
2197 word_completions_enabled: full_mode,
2198 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2199 gutter_hovered: false,
2200 pixel_position_of_newest_cursor: None,
2201 last_bounds: None,
2202 last_position_map: None,
2203 expect_bounds_change: None,
2204 gutter_dimensions: GutterDimensions::default(),
2205 style: None,
2206 show_cursor_names: false,
2207 hovered_cursors: HashMap::default(),
2208 next_editor_action_id: EditorActionId::default(),
2209 editor_actions: Rc::default(),
2210 edit_predictions_hidden_for_vim_mode: false,
2211 show_edit_predictions_override: None,
2212 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2213 edit_prediction_settings: EditPredictionSettings::Disabled,
2214 edit_prediction_indent_conflict: false,
2215 edit_prediction_requires_modifier_in_indent_conflict: true,
2216 custom_context_menu: None,
2217 show_git_blame_gutter: false,
2218 show_git_blame_inline: false,
2219 show_selection_menu: None,
2220 show_git_blame_inline_delay_task: None,
2221 git_blame_inline_enabled: full_mode
2222 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2223 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2224 buffer_serialization: is_minimap.not().then(|| {
2225 BufferSerialization::new(
2226 ProjectSettings::get_global(cx)
2227 .session
2228 .restore_unsaved_buffers,
2229 )
2230 }),
2231 blame: None,
2232 blame_subscription: None,
2233 tasks: BTreeMap::default(),
2234
2235 breakpoint_store,
2236 gutter_breakpoint_indicator: (None, None),
2237 hovered_diff_hunk_row: None,
2238 _subscriptions: (!is_minimap)
2239 .then(|| {
2240 vec![
2241 cx.observe(&multi_buffer, Self::on_buffer_changed),
2242 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2243 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2244 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2245 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2246 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2247 cx.observe_window_activation(window, |editor, window, cx| {
2248 let active = window.is_window_active();
2249 editor.blink_manager.update(cx, |blink_manager, cx| {
2250 if active {
2251 blink_manager.enable(cx);
2252 } else {
2253 blink_manager.disable(cx);
2254 }
2255 });
2256 if active {
2257 editor.show_mouse_cursor(cx);
2258 }
2259 }),
2260 ]
2261 })
2262 .unwrap_or_default(),
2263 tasks_update_task: None,
2264 pull_diagnostics_task: Task::ready(()),
2265 colors: None,
2266 refresh_colors_task: Task::ready(()),
2267 inlay_hints: None,
2268 next_color_inlay_id: 0,
2269 post_scroll_update: Task::ready(()),
2270 linked_edit_ranges: Default::default(),
2271 in_project_search: false,
2272 previous_search_ranges: None,
2273 breadcrumb_header: None,
2274 focused_block: None,
2275 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2276 addons: HashMap::default(),
2277 registered_buffers: HashMap::default(),
2278 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2279 selection_mark_mode: false,
2280 toggle_fold_multiple_buffers: Task::ready(()),
2281 serialize_selections: Task::ready(()),
2282 serialize_folds: Task::ready(()),
2283 text_style_refinement: None,
2284 load_diff_task: load_uncommitted_diff,
2285 temporary_diff_override: false,
2286 mouse_cursor_hidden: false,
2287 minimap: None,
2288 hide_mouse_mode: EditorSettings::get_global(cx)
2289 .hide_mouse
2290 .unwrap_or_default(),
2291 change_list: ChangeList::new(),
2292 mode,
2293 selection_drag_state: SelectionDragState::None,
2294 folding_newlines: Task::ready(()),
2295 lookup_key: None,
2296 };
2297
2298 if is_minimap {
2299 return editor;
2300 }
2301
2302 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2303 editor
2304 ._subscriptions
2305 .push(cx.observe(breakpoints, |_, _, cx| {
2306 cx.notify();
2307 }));
2308 }
2309 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2310 editor._subscriptions.extend(project_subscriptions);
2311
2312 editor._subscriptions.push(cx.subscribe_in(
2313 &cx.entity(),
2314 window,
2315 |editor, _, e: &EditorEvent, window, cx| match e {
2316 EditorEvent::ScrollPositionChanged { local, .. } => {
2317 if *local {
2318 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2319 editor.inline_blame_popover.take();
2320 let new_anchor = editor.scroll_manager.anchor();
2321 let snapshot = editor.snapshot(window, cx);
2322 editor.update_restoration_data(cx, move |data| {
2323 data.scroll_position = (
2324 new_anchor.top_row(snapshot.buffer_snapshot()),
2325 new_anchor.offset,
2326 );
2327 });
2328
2329 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2330 cx.background_executor()
2331 .timer(Duration::from_millis(50))
2332 .await;
2333 editor
2334 .update_in(cx, |editor, window, cx| {
2335 editor.register_visible_buffers(cx);
2336 editor.refresh_colors_for_visible_range(None, window, cx);
2337 editor.refresh_inlay_hints(
2338 InlayHintRefreshReason::NewLinesShown,
2339 cx,
2340 );
2341 })
2342 .ok();
2343 });
2344 }
2345 }
2346 EditorEvent::Edited { .. } => {
2347 if vim_flavor(cx).is_none() {
2348 let display_map = editor.display_snapshot(cx);
2349 let selections = editor.selections.all_adjusted_display(&display_map);
2350 let pop_state = editor
2351 .change_list
2352 .last()
2353 .map(|previous| {
2354 previous.len() == selections.len()
2355 && previous.iter().enumerate().all(|(ix, p)| {
2356 p.to_display_point(&display_map).row()
2357 == selections[ix].head().row()
2358 })
2359 })
2360 .unwrap_or(false);
2361 let new_positions = selections
2362 .into_iter()
2363 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2364 .collect();
2365 editor
2366 .change_list
2367 .push_to_change_list(pop_state, new_positions);
2368 }
2369 }
2370 _ => (),
2371 },
2372 ));
2373
2374 if let Some(dap_store) = editor
2375 .project
2376 .as_ref()
2377 .map(|project| project.read(cx).dap_store())
2378 {
2379 let weak_editor = cx.weak_entity();
2380
2381 editor
2382 ._subscriptions
2383 .push(
2384 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2385 let session_entity = cx.entity();
2386 weak_editor
2387 .update(cx, |editor, cx| {
2388 editor._subscriptions.push(
2389 cx.subscribe(&session_entity, Self::on_debug_session_event),
2390 );
2391 })
2392 .ok();
2393 }),
2394 );
2395
2396 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2397 editor
2398 ._subscriptions
2399 .push(cx.subscribe(&session, Self::on_debug_session_event));
2400 }
2401 }
2402
2403 // skip adding the initial selection to selection history
2404 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2405 editor.end_selection(window, cx);
2406 editor.selection_history.mode = SelectionHistoryMode::Normal;
2407
2408 editor.scroll_manager.show_scrollbars(window, cx);
2409 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2410
2411 if full_mode {
2412 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2413 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2414
2415 if editor.git_blame_inline_enabled {
2416 editor.start_git_blame_inline(false, window, cx);
2417 }
2418
2419 editor.go_to_active_debug_line(window, cx);
2420
2421 editor.minimap =
2422 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2423 editor.colors = Some(LspColorData::new(cx));
2424 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2425
2426 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2427 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2428 }
2429 editor.update_lsp_data(None, window, cx);
2430 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2431 }
2432
2433 editor
2434 }
2435
2436 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2437 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2438 }
2439
2440 pub fn deploy_mouse_context_menu(
2441 &mut self,
2442 position: gpui::Point<Pixels>,
2443 context_menu: Entity<ContextMenu>,
2444 window: &mut Window,
2445 cx: &mut Context<Self>,
2446 ) {
2447 self.mouse_context_menu = Some(MouseContextMenu::new(
2448 self,
2449 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2450 context_menu,
2451 window,
2452 cx,
2453 ));
2454 }
2455
2456 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2457 self.mouse_context_menu
2458 .as_ref()
2459 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2460 }
2461
2462 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2463 if self
2464 .selections
2465 .pending_anchor()
2466 .is_some_and(|pending_selection| {
2467 let snapshot = self.buffer().read(cx).snapshot(cx);
2468 pending_selection.range().includes(range, &snapshot)
2469 })
2470 {
2471 return true;
2472 }
2473
2474 self.selections
2475 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2476 .into_iter()
2477 .any(|selection| {
2478 // This is needed to cover a corner case, if we just check for an existing
2479 // selection in the fold range, having a cursor at the start of the fold
2480 // marks it as selected. Non-empty selections don't cause this.
2481 let length = selection.end - selection.start;
2482 length > 0
2483 })
2484 }
2485
2486 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2487 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2488 }
2489
2490 fn key_context_internal(
2491 &self,
2492 has_active_edit_prediction: bool,
2493 window: &mut Window,
2494 cx: &mut App,
2495 ) -> KeyContext {
2496 let mut key_context = KeyContext::new_with_defaults();
2497 key_context.add("Editor");
2498 let mode = match self.mode {
2499 EditorMode::SingleLine => "single_line",
2500 EditorMode::AutoHeight { .. } => "auto_height",
2501 EditorMode::Minimap { .. } => "minimap",
2502 EditorMode::Full { .. } => "full",
2503 };
2504
2505 if EditorSettings::jupyter_enabled(cx) {
2506 key_context.add("jupyter");
2507 }
2508
2509 key_context.set("mode", mode);
2510 if self.pending_rename.is_some() {
2511 key_context.add("renaming");
2512 }
2513
2514 if let Some(snippet_stack) = self.snippet_stack.last() {
2515 key_context.add("in_snippet");
2516
2517 if snippet_stack.active_index > 0 {
2518 key_context.add("has_previous_tabstop");
2519 }
2520
2521 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2522 key_context.add("has_next_tabstop");
2523 }
2524 }
2525
2526 match self.context_menu.borrow().as_ref() {
2527 Some(CodeContextMenu::Completions(menu)) => {
2528 if menu.visible() {
2529 key_context.add("menu");
2530 key_context.add("showing_completions");
2531 }
2532 }
2533 Some(CodeContextMenu::CodeActions(menu)) => {
2534 if menu.visible() {
2535 key_context.add("menu");
2536 key_context.add("showing_code_actions")
2537 }
2538 }
2539 None => {}
2540 }
2541
2542 if self.signature_help_state.has_multiple_signatures() {
2543 key_context.add("showing_signature_help");
2544 }
2545
2546 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2547 if !self.focus_handle(cx).contains_focused(window, cx)
2548 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2549 {
2550 for addon in self.addons.values() {
2551 addon.extend_key_context(&mut key_context, cx)
2552 }
2553 }
2554
2555 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2556 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2557 Some(
2558 file.full_path(cx)
2559 .extension()?
2560 .to_string_lossy()
2561 .into_owned(),
2562 )
2563 }) {
2564 key_context.set("extension", extension);
2565 }
2566 } else {
2567 key_context.add("multibuffer");
2568 }
2569
2570 if has_active_edit_prediction {
2571 if self.edit_prediction_in_conflict() {
2572 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2573 } else {
2574 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2575 key_context.add("copilot_suggestion");
2576 }
2577 }
2578
2579 if self.selection_mark_mode {
2580 key_context.add("selection_mode");
2581 }
2582
2583 let disjoint = self.selections.disjoint_anchors();
2584 let snapshot = self.snapshot(window, cx);
2585 let snapshot = snapshot.buffer_snapshot();
2586 if self.mode == EditorMode::SingleLine
2587 && let [selection] = disjoint
2588 && selection.start == selection.end
2589 && selection.end.to_offset(snapshot) == snapshot.len()
2590 {
2591 key_context.add("end_of_input");
2592 }
2593
2594 key_context
2595 }
2596
2597 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2598 self.last_bounds.as_ref()
2599 }
2600
2601 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2602 if self.mouse_cursor_hidden {
2603 self.mouse_cursor_hidden = false;
2604 cx.notify();
2605 }
2606 }
2607
2608 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2609 let hide_mouse_cursor = match origin {
2610 HideMouseCursorOrigin::TypingAction => {
2611 matches!(
2612 self.hide_mouse_mode,
2613 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2614 )
2615 }
2616 HideMouseCursorOrigin::MovementAction => {
2617 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2618 }
2619 };
2620 if self.mouse_cursor_hidden != hide_mouse_cursor {
2621 self.mouse_cursor_hidden = hide_mouse_cursor;
2622 cx.notify();
2623 }
2624 }
2625
2626 pub fn edit_prediction_in_conflict(&self) -> bool {
2627 if !self.show_edit_predictions_in_menu() {
2628 return false;
2629 }
2630
2631 let showing_completions = self
2632 .context_menu
2633 .borrow()
2634 .as_ref()
2635 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2636
2637 showing_completions
2638 || self.edit_prediction_requires_modifier()
2639 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2640 // bindings to insert tab characters.
2641 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2642 }
2643
2644 pub fn accept_edit_prediction_keybind(
2645 &self,
2646 accept_partial: bool,
2647 window: &mut Window,
2648 cx: &mut App,
2649 ) -> AcceptEditPredictionBinding {
2650 let key_context = self.key_context_internal(true, window, cx);
2651 let in_conflict = self.edit_prediction_in_conflict();
2652
2653 let bindings = if accept_partial {
2654 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2655 } else {
2656 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2657 };
2658
2659 // TODO: if the binding contains multiple keystrokes, display all of them, not
2660 // just the first one.
2661 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2662 !in_conflict
2663 || binding
2664 .keystrokes()
2665 .first()
2666 .is_some_and(|keystroke| keystroke.modifiers().modified())
2667 }))
2668 }
2669
2670 pub fn new_file(
2671 workspace: &mut Workspace,
2672 _: &workspace::NewFile,
2673 window: &mut Window,
2674 cx: &mut Context<Workspace>,
2675 ) {
2676 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2677 "Failed to create buffer",
2678 window,
2679 cx,
2680 |e, _, _| match e.error_code() {
2681 ErrorCode::RemoteUpgradeRequired => Some(format!(
2682 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2683 e.error_tag("required").unwrap_or("the latest version")
2684 )),
2685 _ => None,
2686 },
2687 );
2688 }
2689
2690 pub fn new_in_workspace(
2691 workspace: &mut Workspace,
2692 window: &mut Window,
2693 cx: &mut Context<Workspace>,
2694 ) -> Task<Result<Entity<Editor>>> {
2695 let project = workspace.project().clone();
2696 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2697
2698 cx.spawn_in(window, async move |workspace, cx| {
2699 let buffer = create.await?;
2700 workspace.update_in(cx, |workspace, window, cx| {
2701 let editor =
2702 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2703 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2704 editor
2705 })
2706 })
2707 }
2708
2709 fn new_file_vertical(
2710 workspace: &mut Workspace,
2711 _: &workspace::NewFileSplitVertical,
2712 window: &mut Window,
2713 cx: &mut Context<Workspace>,
2714 ) {
2715 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2716 }
2717
2718 fn new_file_horizontal(
2719 workspace: &mut Workspace,
2720 _: &workspace::NewFileSplitHorizontal,
2721 window: &mut Window,
2722 cx: &mut Context<Workspace>,
2723 ) {
2724 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2725 }
2726
2727 fn new_file_split(
2728 workspace: &mut Workspace,
2729 action: &workspace::NewFileSplit,
2730 window: &mut Window,
2731 cx: &mut Context<Workspace>,
2732 ) {
2733 Self::new_file_in_direction(workspace, action.0, window, cx)
2734 }
2735
2736 fn new_file_in_direction(
2737 workspace: &mut Workspace,
2738 direction: SplitDirection,
2739 window: &mut Window,
2740 cx: &mut Context<Workspace>,
2741 ) {
2742 let project = workspace.project().clone();
2743 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2744
2745 cx.spawn_in(window, async move |workspace, cx| {
2746 let buffer = create.await?;
2747 workspace.update_in(cx, move |workspace, window, cx| {
2748 workspace.split_item(
2749 direction,
2750 Box::new(
2751 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2752 ),
2753 window,
2754 cx,
2755 )
2756 })?;
2757 anyhow::Ok(())
2758 })
2759 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2760 match e.error_code() {
2761 ErrorCode::RemoteUpgradeRequired => Some(format!(
2762 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2763 e.error_tag("required").unwrap_or("the latest version")
2764 )),
2765 _ => None,
2766 }
2767 });
2768 }
2769
2770 pub fn leader_id(&self) -> Option<CollaboratorId> {
2771 self.leader_id
2772 }
2773
2774 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2775 &self.buffer
2776 }
2777
2778 pub fn project(&self) -> Option<&Entity<Project>> {
2779 self.project.as_ref()
2780 }
2781
2782 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2783 self.workspace.as_ref()?.0.upgrade()
2784 }
2785
2786 /// Returns the workspace serialization ID if this editor should be serialized.
2787 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2788 self.workspace
2789 .as_ref()
2790 .filter(|_| self.should_serialize_buffer())
2791 .and_then(|workspace| workspace.1)
2792 }
2793
2794 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2795 self.buffer().read(cx).title(cx)
2796 }
2797
2798 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2799 let git_blame_gutter_max_author_length = self
2800 .render_git_blame_gutter(cx)
2801 .then(|| {
2802 if let Some(blame) = self.blame.as_ref() {
2803 let max_author_length =
2804 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2805 Some(max_author_length)
2806 } else {
2807 None
2808 }
2809 })
2810 .flatten();
2811
2812 EditorSnapshot {
2813 mode: self.mode.clone(),
2814 show_gutter: self.show_gutter,
2815 show_line_numbers: self.show_line_numbers,
2816 show_git_diff_gutter: self.show_git_diff_gutter,
2817 show_code_actions: self.show_code_actions,
2818 show_runnables: self.show_runnables,
2819 show_breakpoints: self.show_breakpoints,
2820 git_blame_gutter_max_author_length,
2821 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2822 placeholder_display_snapshot: self
2823 .placeholder_display_map
2824 .as_ref()
2825 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2826 scroll_anchor: self.scroll_manager.anchor(),
2827 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2828 is_focused: self.focus_handle.is_focused(window),
2829 current_line_highlight: self
2830 .current_line_highlight
2831 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2832 gutter_hovered: self.gutter_hovered,
2833 }
2834 }
2835
2836 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2837 self.buffer.read(cx).language_at(point, cx)
2838 }
2839
2840 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2841 self.buffer.read(cx).read(cx).file_at(point).cloned()
2842 }
2843
2844 pub fn active_excerpt(
2845 &self,
2846 cx: &App,
2847 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2848 self.buffer
2849 .read(cx)
2850 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2851 }
2852
2853 pub fn mode(&self) -> &EditorMode {
2854 &self.mode
2855 }
2856
2857 pub fn set_mode(&mut self, mode: EditorMode) {
2858 self.mode = mode;
2859 }
2860
2861 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2862 self.collaboration_hub.as_deref()
2863 }
2864
2865 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2866 self.collaboration_hub = Some(hub);
2867 }
2868
2869 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2870 self.in_project_search = in_project_search;
2871 }
2872
2873 pub fn set_custom_context_menu(
2874 &mut self,
2875 f: impl 'static
2876 + Fn(
2877 &mut Self,
2878 DisplayPoint,
2879 &mut Window,
2880 &mut Context<Self>,
2881 ) -> Option<Entity<ui::ContextMenu>>,
2882 ) {
2883 self.custom_context_menu = Some(Box::new(f))
2884 }
2885
2886 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2887 self.completion_provider = provider;
2888 }
2889
2890 #[cfg(any(test, feature = "test-support"))]
2891 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2892 self.completion_provider.clone()
2893 }
2894
2895 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2896 self.semantics_provider.clone()
2897 }
2898
2899 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2900 self.semantics_provider = provider;
2901 }
2902
2903 pub fn set_edit_prediction_provider<T>(
2904 &mut self,
2905 provider: Option<Entity<T>>,
2906 window: &mut Window,
2907 cx: &mut Context<Self>,
2908 ) where
2909 T: EditPredictionProvider,
2910 {
2911 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2912 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2913 if this.focus_handle.is_focused(window) {
2914 this.update_visible_edit_prediction(window, cx);
2915 }
2916 }),
2917 provider: Arc::new(provider),
2918 });
2919 self.update_edit_prediction_settings(cx);
2920 self.refresh_edit_prediction(false, false, window, cx);
2921 }
2922
2923 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2924 self.placeholder_display_map
2925 .as_ref()
2926 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2927 }
2928
2929 pub fn set_placeholder_text(
2930 &mut self,
2931 placeholder_text: &str,
2932 window: &mut Window,
2933 cx: &mut Context<Self>,
2934 ) {
2935 let multibuffer = cx
2936 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2937
2938 let style = window.text_style();
2939
2940 self.placeholder_display_map = Some(cx.new(|cx| {
2941 DisplayMap::new(
2942 multibuffer,
2943 style.font(),
2944 style.font_size.to_pixels(window.rem_size()),
2945 None,
2946 FILE_HEADER_HEIGHT,
2947 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2948 Default::default(),
2949 DiagnosticSeverity::Off,
2950 cx,
2951 )
2952 }));
2953 cx.notify();
2954 }
2955
2956 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2957 self.cursor_shape = cursor_shape;
2958
2959 // Disrupt blink for immediate user feedback that the cursor shape has changed
2960 self.blink_manager.update(cx, BlinkManager::show_cursor);
2961
2962 cx.notify();
2963 }
2964
2965 pub fn set_current_line_highlight(
2966 &mut self,
2967 current_line_highlight: Option<CurrentLineHighlight>,
2968 ) {
2969 self.current_line_highlight = current_line_highlight;
2970 }
2971
2972 pub fn range_for_match<T: std::marker::Copy>(
2973 &self,
2974 range: &Range<T>,
2975 collapse: bool,
2976 ) -> Range<T> {
2977 if collapse {
2978 return range.start..range.start;
2979 }
2980 range.clone()
2981 }
2982
2983 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2984 if self.display_map.read(cx).clip_at_line_ends != clip {
2985 self.display_map
2986 .update(cx, |map, _| map.clip_at_line_ends = clip);
2987 }
2988 }
2989
2990 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2991 self.input_enabled = input_enabled;
2992 }
2993
2994 pub fn set_edit_predictions_hidden_for_vim_mode(
2995 &mut self,
2996 hidden: bool,
2997 window: &mut Window,
2998 cx: &mut Context<Self>,
2999 ) {
3000 if hidden != self.edit_predictions_hidden_for_vim_mode {
3001 self.edit_predictions_hidden_for_vim_mode = hidden;
3002 if hidden {
3003 self.update_visible_edit_prediction(window, cx);
3004 } else {
3005 self.refresh_edit_prediction(true, false, window, cx);
3006 }
3007 }
3008 }
3009
3010 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3011 self.menu_edit_predictions_policy = value;
3012 }
3013
3014 pub fn set_autoindent(&mut self, autoindent: bool) {
3015 if autoindent {
3016 self.autoindent_mode = Some(AutoindentMode::EachLine);
3017 } else {
3018 self.autoindent_mode = None;
3019 }
3020 }
3021
3022 pub fn read_only(&self, cx: &App) -> bool {
3023 self.read_only || self.buffer.read(cx).read_only()
3024 }
3025
3026 pub fn set_read_only(&mut self, read_only: bool) {
3027 self.read_only = read_only;
3028 }
3029
3030 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3031 self.use_autoclose = autoclose;
3032 }
3033
3034 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3035 self.use_auto_surround = auto_surround;
3036 }
3037
3038 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3039 self.auto_replace_emoji_shortcode = auto_replace;
3040 }
3041
3042 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3043 self.buffer_serialization = should_serialize.then(|| {
3044 BufferSerialization::new(
3045 ProjectSettings::get_global(cx)
3046 .session
3047 .restore_unsaved_buffers,
3048 )
3049 })
3050 }
3051
3052 fn should_serialize_buffer(&self) -> bool {
3053 self.buffer_serialization.is_some()
3054 }
3055
3056 pub fn toggle_edit_predictions(
3057 &mut self,
3058 _: &ToggleEditPrediction,
3059 window: &mut Window,
3060 cx: &mut Context<Self>,
3061 ) {
3062 if self.show_edit_predictions_override.is_some() {
3063 self.set_show_edit_predictions(None, window, cx);
3064 } else {
3065 let show_edit_predictions = !self.edit_predictions_enabled();
3066 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3067 }
3068 }
3069
3070 pub fn set_show_edit_predictions(
3071 &mut self,
3072 show_edit_predictions: Option<bool>,
3073 window: &mut Window,
3074 cx: &mut Context<Self>,
3075 ) {
3076 self.show_edit_predictions_override = show_edit_predictions;
3077 self.update_edit_prediction_settings(cx);
3078
3079 if let Some(false) = show_edit_predictions {
3080 self.discard_edit_prediction(false, cx);
3081 } else {
3082 self.refresh_edit_prediction(false, true, window, cx);
3083 }
3084 }
3085
3086 fn edit_predictions_disabled_in_scope(
3087 &self,
3088 buffer: &Entity<Buffer>,
3089 buffer_position: language::Anchor,
3090 cx: &App,
3091 ) -> bool {
3092 let snapshot = buffer.read(cx).snapshot();
3093 let settings = snapshot.settings_at(buffer_position, cx);
3094
3095 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3096 return false;
3097 };
3098
3099 scope.override_name().is_some_and(|scope_name| {
3100 settings
3101 .edit_predictions_disabled_in
3102 .iter()
3103 .any(|s| s == scope_name)
3104 })
3105 }
3106
3107 pub fn set_use_modal_editing(&mut self, to: bool) {
3108 self.use_modal_editing = to;
3109 }
3110
3111 pub fn use_modal_editing(&self) -> bool {
3112 self.use_modal_editing
3113 }
3114
3115 fn selections_did_change(
3116 &mut self,
3117 local: bool,
3118 old_cursor_position: &Anchor,
3119 effects: SelectionEffects,
3120 window: &mut Window,
3121 cx: &mut Context<Self>,
3122 ) {
3123 window.invalidate_character_coordinates();
3124
3125 // Copy selections to primary selection buffer
3126 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3127 if local {
3128 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3129 let buffer_handle = self.buffer.read(cx).read(cx);
3130
3131 let mut text = String::new();
3132 for (index, selection) in selections.iter().enumerate() {
3133 let text_for_selection = buffer_handle
3134 .text_for_range(selection.start..selection.end)
3135 .collect::<String>();
3136
3137 text.push_str(&text_for_selection);
3138 if index != selections.len() - 1 {
3139 text.push('\n');
3140 }
3141 }
3142
3143 if !text.is_empty() {
3144 cx.write_to_primary(ClipboardItem::new_string(text));
3145 }
3146 }
3147
3148 let selection_anchors = self.selections.disjoint_anchors_arc();
3149
3150 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3151 self.buffer.update(cx, |buffer, cx| {
3152 buffer.set_active_selections(
3153 &selection_anchors,
3154 self.selections.line_mode(),
3155 self.cursor_shape,
3156 cx,
3157 )
3158 });
3159 }
3160 let display_map = self
3161 .display_map
3162 .update(cx, |display_map, cx| display_map.snapshot(cx));
3163 let buffer = display_map.buffer_snapshot();
3164 if self.selections.count() == 1 {
3165 self.add_selections_state = None;
3166 }
3167 self.select_next_state = None;
3168 self.select_prev_state = None;
3169 self.select_syntax_node_history.try_clear();
3170 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3171 self.snippet_stack.invalidate(&selection_anchors, buffer);
3172 self.take_rename(false, window, cx);
3173
3174 let newest_selection = self.selections.newest_anchor();
3175 let new_cursor_position = newest_selection.head();
3176 let selection_start = newest_selection.start;
3177
3178 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3179 self.push_to_nav_history(
3180 *old_cursor_position,
3181 Some(new_cursor_position.to_point(buffer)),
3182 false,
3183 effects.nav_history == Some(true),
3184 cx,
3185 );
3186 }
3187
3188 if local {
3189 if let Some(buffer_id) = new_cursor_position.buffer_id {
3190 self.register_buffer(buffer_id, cx);
3191 }
3192
3193 let mut context_menu = self.context_menu.borrow_mut();
3194 let completion_menu = match context_menu.as_ref() {
3195 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3196 Some(CodeContextMenu::CodeActions(_)) => {
3197 *context_menu = None;
3198 None
3199 }
3200 None => None,
3201 };
3202 let completion_position = completion_menu.map(|menu| menu.initial_position);
3203 drop(context_menu);
3204
3205 if effects.completions
3206 && let Some(completion_position) = completion_position
3207 {
3208 let start_offset = selection_start.to_offset(buffer);
3209 let position_matches = start_offset == completion_position.to_offset(buffer);
3210 let continue_showing = if position_matches {
3211 if self.snippet_stack.is_empty() {
3212 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3213 == Some(CharKind::Word)
3214 } else {
3215 // Snippet choices can be shown even when the cursor is in whitespace.
3216 // Dismissing the menu with actions like backspace is handled by
3217 // invalidation regions.
3218 true
3219 }
3220 } else {
3221 false
3222 };
3223
3224 if continue_showing {
3225 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3226 } else {
3227 self.hide_context_menu(window, cx);
3228 }
3229 }
3230
3231 hide_hover(self, cx);
3232
3233 if old_cursor_position.to_display_point(&display_map).row()
3234 != new_cursor_position.to_display_point(&display_map).row()
3235 {
3236 self.available_code_actions.take();
3237 }
3238 self.refresh_code_actions(window, cx);
3239 self.refresh_document_highlights(cx);
3240 refresh_linked_ranges(self, window, cx);
3241
3242 self.refresh_selected_text_highlights(false, window, cx);
3243 self.refresh_matching_bracket_highlights(window, cx);
3244 self.update_visible_edit_prediction(window, cx);
3245 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3246 self.inline_blame_popover.take();
3247 if self.git_blame_inline_enabled {
3248 self.start_inline_blame_timer(window, cx);
3249 }
3250 }
3251
3252 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3253 cx.emit(EditorEvent::SelectionsChanged { local });
3254
3255 let selections = &self.selections.disjoint_anchors_arc();
3256 if selections.len() == 1 {
3257 cx.emit(SearchEvent::ActiveMatchChanged)
3258 }
3259 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3260 let inmemory_selections = selections
3261 .iter()
3262 .map(|s| {
3263 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3264 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3265 })
3266 .collect();
3267 self.update_restoration_data(cx, |data| {
3268 data.selections = inmemory_selections;
3269 });
3270
3271 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3272 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3273 {
3274 let snapshot = self.buffer().read(cx).snapshot(cx);
3275 let selections = selections.clone();
3276 let background_executor = cx.background_executor().clone();
3277 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3278 self.serialize_selections = cx.background_spawn(async move {
3279 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3280 let db_selections = selections
3281 .iter()
3282 .map(|selection| {
3283 (
3284 selection.start.to_offset(&snapshot),
3285 selection.end.to_offset(&snapshot),
3286 )
3287 })
3288 .collect();
3289
3290 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3291 .await
3292 .with_context(|| {
3293 format!(
3294 "persisting editor selections for editor {editor_id}, \
3295 workspace {workspace_id:?}"
3296 )
3297 })
3298 .log_err();
3299 });
3300 }
3301 }
3302
3303 cx.notify();
3304 }
3305
3306 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3307 use text::ToOffset as _;
3308 use text::ToPoint as _;
3309
3310 if self.mode.is_minimap()
3311 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3312 {
3313 return;
3314 }
3315
3316 if !self.buffer().read(cx).is_singleton() {
3317 return;
3318 }
3319
3320 let display_snapshot = self
3321 .display_map
3322 .update(cx, |display_map, cx| display_map.snapshot(cx));
3323 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3324 return;
3325 };
3326 let inmemory_folds = display_snapshot
3327 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3328 .map(|fold| {
3329 fold.range.start.text_anchor.to_point(&snapshot)
3330 ..fold.range.end.text_anchor.to_point(&snapshot)
3331 })
3332 .collect();
3333 self.update_restoration_data(cx, |data| {
3334 data.folds = inmemory_folds;
3335 });
3336
3337 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3338 return;
3339 };
3340 let background_executor = cx.background_executor().clone();
3341 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3342 let db_folds = display_snapshot
3343 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3344 .map(|fold| {
3345 (
3346 fold.range.start.text_anchor.to_offset(&snapshot),
3347 fold.range.end.text_anchor.to_offset(&snapshot),
3348 )
3349 })
3350 .collect();
3351 self.serialize_folds = cx.background_spawn(async move {
3352 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3353 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3354 .await
3355 .with_context(|| {
3356 format!(
3357 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3358 )
3359 })
3360 .log_err();
3361 });
3362 }
3363
3364 pub fn sync_selections(
3365 &mut self,
3366 other: Entity<Editor>,
3367 cx: &mut Context<Self>,
3368 ) -> gpui::Subscription {
3369 assert_eq!(self.buffer(), other.read(cx).buffer());
3370 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3371 if !other_selections.is_empty() {
3372 self.selections
3373 .change_with(&self.display_snapshot(cx), |selections| {
3374 selections.select_anchors(other_selections);
3375 });
3376 }
3377
3378 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3379 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3380 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3381 if other_selections.is_empty() {
3382 return;
3383 }
3384 let snapshot = this.display_snapshot(cx);
3385 this.selections.change_with(&snapshot, |selections| {
3386 selections.select_anchors(other_selections);
3387 });
3388 }
3389 });
3390
3391 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3392 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3393 let these_selections = this.selections.disjoint_anchors().to_vec();
3394 if these_selections.is_empty() {
3395 return;
3396 }
3397 other.update(cx, |other_editor, cx| {
3398 let snapshot = other_editor.display_snapshot(cx);
3399 other_editor
3400 .selections
3401 .change_with(&snapshot, |selections| {
3402 selections.select_anchors(these_selections);
3403 })
3404 });
3405 }
3406 });
3407
3408 Subscription::join(other_subscription, this_subscription)
3409 }
3410
3411 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3412 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3413 /// effects of selection change occur at the end of the transaction.
3414 pub fn change_selections<R>(
3415 &mut self,
3416 effects: SelectionEffects,
3417 window: &mut Window,
3418 cx: &mut Context<Self>,
3419 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3420 ) -> R {
3421 let snapshot = self.display_snapshot(cx);
3422 if let Some(state) = &mut self.deferred_selection_effects_state {
3423 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3424 state.effects.completions = effects.completions;
3425 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3426 let (changed, result) = self.selections.change_with(&snapshot, change);
3427 state.changed |= changed;
3428 return result;
3429 }
3430 let mut state = DeferredSelectionEffectsState {
3431 changed: false,
3432 effects,
3433 old_cursor_position: self.selections.newest_anchor().head(),
3434 history_entry: SelectionHistoryEntry {
3435 selections: self.selections.disjoint_anchors_arc(),
3436 select_next_state: self.select_next_state.clone(),
3437 select_prev_state: self.select_prev_state.clone(),
3438 add_selections_state: self.add_selections_state.clone(),
3439 },
3440 };
3441 let (changed, result) = self.selections.change_with(&snapshot, change);
3442 state.changed = state.changed || changed;
3443 if self.defer_selection_effects {
3444 self.deferred_selection_effects_state = Some(state);
3445 } else {
3446 self.apply_selection_effects(state, window, cx);
3447 }
3448 result
3449 }
3450
3451 /// Defers the effects of selection change, so that the effects of multiple calls to
3452 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3453 /// to selection history and the state of popovers based on selection position aren't
3454 /// erroneously updated.
3455 pub fn with_selection_effects_deferred<R>(
3456 &mut self,
3457 window: &mut Window,
3458 cx: &mut Context<Self>,
3459 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3460 ) -> R {
3461 let already_deferred = self.defer_selection_effects;
3462 self.defer_selection_effects = true;
3463 let result = update(self, window, cx);
3464 if !already_deferred {
3465 self.defer_selection_effects = false;
3466 if let Some(state) = self.deferred_selection_effects_state.take() {
3467 self.apply_selection_effects(state, window, cx);
3468 }
3469 }
3470 result
3471 }
3472
3473 fn apply_selection_effects(
3474 &mut self,
3475 state: DeferredSelectionEffectsState,
3476 window: &mut Window,
3477 cx: &mut Context<Self>,
3478 ) {
3479 if state.changed {
3480 self.selection_history.push(state.history_entry);
3481
3482 if let Some(autoscroll) = state.effects.scroll {
3483 self.request_autoscroll(autoscroll, cx);
3484 }
3485
3486 let old_cursor_position = &state.old_cursor_position;
3487
3488 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3489
3490 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3491 self.show_signature_help(&ShowSignatureHelp, window, cx);
3492 }
3493 }
3494 }
3495
3496 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3497 where
3498 I: IntoIterator<Item = (Range<S>, T)>,
3499 S: ToOffset,
3500 T: Into<Arc<str>>,
3501 {
3502 if self.read_only(cx) {
3503 return;
3504 }
3505
3506 self.buffer
3507 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3508 }
3509
3510 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3511 where
3512 I: IntoIterator<Item = (Range<S>, T)>,
3513 S: ToOffset,
3514 T: Into<Arc<str>>,
3515 {
3516 if self.read_only(cx) {
3517 return;
3518 }
3519
3520 self.buffer.update(cx, |buffer, cx| {
3521 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3522 });
3523 }
3524
3525 pub fn edit_with_block_indent<I, S, T>(
3526 &mut self,
3527 edits: I,
3528 original_indent_columns: Vec<Option<u32>>,
3529 cx: &mut Context<Self>,
3530 ) where
3531 I: IntoIterator<Item = (Range<S>, T)>,
3532 S: ToOffset,
3533 T: Into<Arc<str>>,
3534 {
3535 if self.read_only(cx) {
3536 return;
3537 }
3538
3539 self.buffer.update(cx, |buffer, cx| {
3540 buffer.edit(
3541 edits,
3542 Some(AutoindentMode::Block {
3543 original_indent_columns,
3544 }),
3545 cx,
3546 )
3547 });
3548 }
3549
3550 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3551 self.hide_context_menu(window, cx);
3552
3553 match phase {
3554 SelectPhase::Begin {
3555 position,
3556 add,
3557 click_count,
3558 } => self.begin_selection(position, add, click_count, window, cx),
3559 SelectPhase::BeginColumnar {
3560 position,
3561 goal_column,
3562 reset,
3563 mode,
3564 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3565 SelectPhase::Extend {
3566 position,
3567 click_count,
3568 } => self.extend_selection(position, click_count, window, cx),
3569 SelectPhase::Update {
3570 position,
3571 goal_column,
3572 scroll_delta,
3573 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3574 SelectPhase::End => self.end_selection(window, cx),
3575 }
3576 }
3577
3578 fn extend_selection(
3579 &mut self,
3580 position: DisplayPoint,
3581 click_count: usize,
3582 window: &mut Window,
3583 cx: &mut Context<Self>,
3584 ) {
3585 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3586 let tail = self.selections.newest::<usize>(&display_map).tail();
3587 let click_count = click_count.max(match self.selections.select_mode() {
3588 SelectMode::Character => 1,
3589 SelectMode::Word(_) => 2,
3590 SelectMode::Line(_) => 3,
3591 SelectMode::All => 4,
3592 });
3593 self.begin_selection(position, false, click_count, window, cx);
3594
3595 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3596
3597 let current_selection = match self.selections.select_mode() {
3598 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3599 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3600 };
3601
3602 let mut pending_selection = self
3603 .selections
3604 .pending_anchor()
3605 .cloned()
3606 .expect("extend_selection not called with pending selection");
3607
3608 if pending_selection
3609 .start
3610 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3611 == Ordering::Greater
3612 {
3613 pending_selection.start = current_selection.start;
3614 }
3615 if pending_selection
3616 .end
3617 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3618 == Ordering::Less
3619 {
3620 pending_selection.end = current_selection.end;
3621 pending_selection.reversed = true;
3622 }
3623
3624 let mut pending_mode = self.selections.pending_mode().unwrap();
3625 match &mut pending_mode {
3626 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3627 _ => {}
3628 }
3629
3630 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3631 SelectionEffects::scroll(Autoscroll::fit())
3632 } else {
3633 SelectionEffects::no_scroll()
3634 };
3635
3636 self.change_selections(effects, window, cx, |s| {
3637 s.set_pending(pending_selection.clone(), pending_mode);
3638 s.set_is_extending(true);
3639 });
3640 }
3641
3642 fn begin_selection(
3643 &mut self,
3644 position: DisplayPoint,
3645 add: bool,
3646 click_count: usize,
3647 window: &mut Window,
3648 cx: &mut Context<Self>,
3649 ) {
3650 if !self.focus_handle.is_focused(window) {
3651 self.last_focused_descendant = None;
3652 window.focus(&self.focus_handle);
3653 }
3654
3655 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3656 let buffer = display_map.buffer_snapshot();
3657 let position = display_map.clip_point(position, Bias::Left);
3658
3659 let start;
3660 let end;
3661 let mode;
3662 let mut auto_scroll;
3663 match click_count {
3664 1 => {
3665 start = buffer.anchor_before(position.to_point(&display_map));
3666 end = start;
3667 mode = SelectMode::Character;
3668 auto_scroll = true;
3669 }
3670 2 => {
3671 let position = display_map
3672 .clip_point(position, Bias::Left)
3673 .to_offset(&display_map, Bias::Left);
3674 let (range, _) = buffer.surrounding_word(position, None);
3675 start = buffer.anchor_before(range.start);
3676 end = buffer.anchor_before(range.end);
3677 mode = SelectMode::Word(start..end);
3678 auto_scroll = true;
3679 }
3680 3 => {
3681 let position = display_map
3682 .clip_point(position, Bias::Left)
3683 .to_point(&display_map);
3684 let line_start = display_map.prev_line_boundary(position).0;
3685 let next_line_start = buffer.clip_point(
3686 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3687 Bias::Left,
3688 );
3689 start = buffer.anchor_before(line_start);
3690 end = buffer.anchor_before(next_line_start);
3691 mode = SelectMode::Line(start..end);
3692 auto_scroll = true;
3693 }
3694 _ => {
3695 start = buffer.anchor_before(0);
3696 end = buffer.anchor_before(buffer.len());
3697 mode = SelectMode::All;
3698 auto_scroll = false;
3699 }
3700 }
3701 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3702
3703 let point_to_delete: Option<usize> = {
3704 let selected_points: Vec<Selection<Point>> =
3705 self.selections.disjoint_in_range(start..end, &display_map);
3706
3707 if !add || click_count > 1 {
3708 None
3709 } else if !selected_points.is_empty() {
3710 Some(selected_points[0].id)
3711 } else {
3712 let clicked_point_already_selected =
3713 self.selections.disjoint_anchors().iter().find(|selection| {
3714 selection.start.to_point(buffer) == start.to_point(buffer)
3715 || selection.end.to_point(buffer) == end.to_point(buffer)
3716 });
3717
3718 clicked_point_already_selected.map(|selection| selection.id)
3719 }
3720 };
3721
3722 let selections_count = self.selections.count();
3723 let effects = if auto_scroll {
3724 SelectionEffects::default()
3725 } else {
3726 SelectionEffects::no_scroll()
3727 };
3728
3729 self.change_selections(effects, window, cx, |s| {
3730 if let Some(point_to_delete) = point_to_delete {
3731 s.delete(point_to_delete);
3732
3733 if selections_count == 1 {
3734 s.set_pending_anchor_range(start..end, mode);
3735 }
3736 } else {
3737 if !add {
3738 s.clear_disjoint();
3739 }
3740
3741 s.set_pending_anchor_range(start..end, mode);
3742 }
3743 });
3744 }
3745
3746 fn begin_columnar_selection(
3747 &mut self,
3748 position: DisplayPoint,
3749 goal_column: u32,
3750 reset: bool,
3751 mode: ColumnarMode,
3752 window: &mut Window,
3753 cx: &mut Context<Self>,
3754 ) {
3755 if !self.focus_handle.is_focused(window) {
3756 self.last_focused_descendant = None;
3757 window.focus(&self.focus_handle);
3758 }
3759
3760 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3761
3762 if reset {
3763 let pointer_position = display_map
3764 .buffer_snapshot()
3765 .anchor_before(position.to_point(&display_map));
3766
3767 self.change_selections(
3768 SelectionEffects::scroll(Autoscroll::newest()),
3769 window,
3770 cx,
3771 |s| {
3772 s.clear_disjoint();
3773 s.set_pending_anchor_range(
3774 pointer_position..pointer_position,
3775 SelectMode::Character,
3776 );
3777 },
3778 );
3779 };
3780
3781 let tail = self.selections.newest::<Point>(&display_map).tail();
3782 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3783 self.columnar_selection_state = match mode {
3784 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3785 selection_tail: selection_anchor,
3786 display_point: if reset {
3787 if position.column() != goal_column {
3788 Some(DisplayPoint::new(position.row(), goal_column))
3789 } else {
3790 None
3791 }
3792 } else {
3793 None
3794 },
3795 }),
3796 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3797 selection_tail: selection_anchor,
3798 }),
3799 };
3800
3801 if !reset {
3802 self.select_columns(position, goal_column, &display_map, window, cx);
3803 }
3804 }
3805
3806 fn update_selection(
3807 &mut self,
3808 position: DisplayPoint,
3809 goal_column: u32,
3810 scroll_delta: gpui::Point<f32>,
3811 window: &mut Window,
3812 cx: &mut Context<Self>,
3813 ) {
3814 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3815
3816 if self.columnar_selection_state.is_some() {
3817 self.select_columns(position, goal_column, &display_map, window, cx);
3818 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3819 let buffer = display_map.buffer_snapshot();
3820 let head;
3821 let tail;
3822 let mode = self.selections.pending_mode().unwrap();
3823 match &mode {
3824 SelectMode::Character => {
3825 head = position.to_point(&display_map);
3826 tail = pending.tail().to_point(buffer);
3827 }
3828 SelectMode::Word(original_range) => {
3829 let offset = display_map
3830 .clip_point(position, Bias::Left)
3831 .to_offset(&display_map, Bias::Left);
3832 let original_range = original_range.to_offset(buffer);
3833
3834 let head_offset = if buffer.is_inside_word(offset, None)
3835 || original_range.contains(&offset)
3836 {
3837 let (word_range, _) = buffer.surrounding_word(offset, None);
3838 if word_range.start < original_range.start {
3839 word_range.start
3840 } else {
3841 word_range.end
3842 }
3843 } else {
3844 offset
3845 };
3846
3847 head = head_offset.to_point(buffer);
3848 if head_offset <= original_range.start {
3849 tail = original_range.end.to_point(buffer);
3850 } else {
3851 tail = original_range.start.to_point(buffer);
3852 }
3853 }
3854 SelectMode::Line(original_range) => {
3855 let original_range = original_range.to_point(display_map.buffer_snapshot());
3856
3857 let position = display_map
3858 .clip_point(position, Bias::Left)
3859 .to_point(&display_map);
3860 let line_start = display_map.prev_line_boundary(position).0;
3861 let next_line_start = buffer.clip_point(
3862 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3863 Bias::Left,
3864 );
3865
3866 if line_start < original_range.start {
3867 head = line_start
3868 } else {
3869 head = next_line_start
3870 }
3871
3872 if head <= original_range.start {
3873 tail = original_range.end;
3874 } else {
3875 tail = original_range.start;
3876 }
3877 }
3878 SelectMode::All => {
3879 return;
3880 }
3881 };
3882
3883 if head < tail {
3884 pending.start = buffer.anchor_before(head);
3885 pending.end = buffer.anchor_before(tail);
3886 pending.reversed = true;
3887 } else {
3888 pending.start = buffer.anchor_before(tail);
3889 pending.end = buffer.anchor_before(head);
3890 pending.reversed = false;
3891 }
3892
3893 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3894 s.set_pending(pending.clone(), mode);
3895 });
3896 } else {
3897 log::error!("update_selection dispatched with no pending selection");
3898 return;
3899 }
3900
3901 self.apply_scroll_delta(scroll_delta, window, cx);
3902 cx.notify();
3903 }
3904
3905 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3906 self.columnar_selection_state.take();
3907 if let Some(pending_mode) = self.selections.pending_mode() {
3908 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3909 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3910 s.select(selections);
3911 s.clear_pending();
3912 if s.is_extending() {
3913 s.set_is_extending(false);
3914 } else {
3915 s.set_select_mode(pending_mode);
3916 }
3917 });
3918 }
3919 }
3920
3921 fn select_columns(
3922 &mut self,
3923 head: DisplayPoint,
3924 goal_column: u32,
3925 display_map: &DisplaySnapshot,
3926 window: &mut Window,
3927 cx: &mut Context<Self>,
3928 ) {
3929 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3930 return;
3931 };
3932
3933 let tail = match columnar_state {
3934 ColumnarSelectionState::FromMouse {
3935 selection_tail,
3936 display_point,
3937 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3938 ColumnarSelectionState::FromSelection { selection_tail } => {
3939 selection_tail.to_display_point(display_map)
3940 }
3941 };
3942
3943 let start_row = cmp::min(tail.row(), head.row());
3944 let end_row = cmp::max(tail.row(), head.row());
3945 let start_column = cmp::min(tail.column(), goal_column);
3946 let end_column = cmp::max(tail.column(), goal_column);
3947 let reversed = start_column < tail.column();
3948
3949 let selection_ranges = (start_row.0..=end_row.0)
3950 .map(DisplayRow)
3951 .filter_map(|row| {
3952 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3953 || start_column <= display_map.line_len(row))
3954 && !display_map.is_block_line(row)
3955 {
3956 let start = display_map
3957 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3958 .to_point(display_map);
3959 let end = display_map
3960 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3961 .to_point(display_map);
3962 if reversed {
3963 Some(end..start)
3964 } else {
3965 Some(start..end)
3966 }
3967 } else {
3968 None
3969 }
3970 })
3971 .collect::<Vec<_>>();
3972 if selection_ranges.is_empty() {
3973 return;
3974 }
3975
3976 let ranges = match columnar_state {
3977 ColumnarSelectionState::FromMouse { .. } => {
3978 let mut non_empty_ranges = selection_ranges
3979 .iter()
3980 .filter(|selection_range| selection_range.start != selection_range.end)
3981 .peekable();
3982 if non_empty_ranges.peek().is_some() {
3983 non_empty_ranges.cloned().collect()
3984 } else {
3985 selection_ranges
3986 }
3987 }
3988 _ => selection_ranges,
3989 };
3990
3991 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3992 s.select_ranges(ranges);
3993 });
3994 cx.notify();
3995 }
3996
3997 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3998 self.selections
3999 .all_adjusted(snapshot)
4000 .iter()
4001 .any(|selection| !selection.is_empty())
4002 }
4003
4004 pub fn has_pending_nonempty_selection(&self) -> bool {
4005 let pending_nonempty_selection = match self.selections.pending_anchor() {
4006 Some(Selection { start, end, .. }) => start != end,
4007 None => false,
4008 };
4009
4010 pending_nonempty_selection
4011 || (self.columnar_selection_state.is_some()
4012 && self.selections.disjoint_anchors().len() > 1)
4013 }
4014
4015 pub fn has_pending_selection(&self) -> bool {
4016 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4017 }
4018
4019 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4020 self.selection_mark_mode = false;
4021 self.selection_drag_state = SelectionDragState::None;
4022
4023 if self.clear_expanded_diff_hunks(cx) {
4024 cx.notify();
4025 return;
4026 }
4027 if self.dismiss_menus_and_popups(true, window, cx) {
4028 return;
4029 }
4030
4031 if self.mode.is_full()
4032 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4033 {
4034 return;
4035 }
4036
4037 cx.propagate();
4038 }
4039
4040 pub fn dismiss_menus_and_popups(
4041 &mut self,
4042 is_user_requested: bool,
4043 window: &mut Window,
4044 cx: &mut Context<Self>,
4045 ) -> bool {
4046 if self.take_rename(false, window, cx).is_some() {
4047 return true;
4048 }
4049
4050 if self.hide_blame_popover(true, cx) {
4051 return true;
4052 }
4053
4054 if hide_hover(self, cx) {
4055 return true;
4056 }
4057
4058 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
4059 return true;
4060 }
4061
4062 if self.hide_context_menu(window, cx).is_some() {
4063 return true;
4064 }
4065
4066 if self.mouse_context_menu.take().is_some() {
4067 return true;
4068 }
4069
4070 if is_user_requested && self.discard_edit_prediction(true, cx) {
4071 return true;
4072 }
4073
4074 if self.snippet_stack.pop().is_some() {
4075 return true;
4076 }
4077
4078 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4079 self.dismiss_diagnostics(cx);
4080 return true;
4081 }
4082
4083 false
4084 }
4085
4086 fn linked_editing_ranges_for(
4087 &self,
4088 selection: Range<text::Anchor>,
4089 cx: &App,
4090 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4091 if self.linked_edit_ranges.is_empty() {
4092 return None;
4093 }
4094 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4095 selection.end.buffer_id.and_then(|end_buffer_id| {
4096 if selection.start.buffer_id != Some(end_buffer_id) {
4097 return None;
4098 }
4099 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4100 let snapshot = buffer.read(cx).snapshot();
4101 self.linked_edit_ranges
4102 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4103 .map(|ranges| (ranges, snapshot, buffer))
4104 })?;
4105 use text::ToOffset as TO;
4106 // find offset from the start of current range to current cursor position
4107 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4108
4109 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4110 let start_difference = start_offset - start_byte_offset;
4111 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4112 let end_difference = end_offset - start_byte_offset;
4113 // Current range has associated linked ranges.
4114 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4115 for range in linked_ranges.iter() {
4116 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4117 let end_offset = start_offset + end_difference;
4118 let start_offset = start_offset + start_difference;
4119 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4120 continue;
4121 }
4122 if self.selections.disjoint_anchor_ranges().any(|s| {
4123 if s.start.buffer_id != selection.start.buffer_id
4124 || s.end.buffer_id != selection.end.buffer_id
4125 {
4126 return false;
4127 }
4128 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4129 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4130 }) {
4131 continue;
4132 }
4133 let start = buffer_snapshot.anchor_after(start_offset);
4134 let end = buffer_snapshot.anchor_after(end_offset);
4135 linked_edits
4136 .entry(buffer.clone())
4137 .or_default()
4138 .push(start..end);
4139 }
4140 Some(linked_edits)
4141 }
4142
4143 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4144 let text: Arc<str> = text.into();
4145
4146 if self.read_only(cx) {
4147 return;
4148 }
4149
4150 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4151
4152 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4153 let mut bracket_inserted = false;
4154 let mut edits = Vec::new();
4155 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4156 let mut new_selections = Vec::with_capacity(selections.len());
4157 let mut new_autoclose_regions = Vec::new();
4158 let snapshot = self.buffer.read(cx).read(cx);
4159 let mut clear_linked_edit_ranges = false;
4160
4161 for (selection, autoclose_region) in
4162 self.selections_with_autoclose_regions(selections, &snapshot)
4163 {
4164 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4165 // Determine if the inserted text matches the opening or closing
4166 // bracket of any of this language's bracket pairs.
4167 let mut bracket_pair = None;
4168 let mut is_bracket_pair_start = false;
4169 let mut is_bracket_pair_end = false;
4170 if !text.is_empty() {
4171 let mut bracket_pair_matching_end = None;
4172 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4173 // and they are removing the character that triggered IME popup.
4174 for (pair, enabled) in scope.brackets() {
4175 if !pair.close && !pair.surround {
4176 continue;
4177 }
4178
4179 if enabled && pair.start.ends_with(text.as_ref()) {
4180 let prefix_len = pair.start.len() - text.len();
4181 let preceding_text_matches_prefix = prefix_len == 0
4182 || (selection.start.column >= (prefix_len as u32)
4183 && snapshot.contains_str_at(
4184 Point::new(
4185 selection.start.row,
4186 selection.start.column - (prefix_len as u32),
4187 ),
4188 &pair.start[..prefix_len],
4189 ));
4190 if preceding_text_matches_prefix {
4191 bracket_pair = Some(pair.clone());
4192 is_bracket_pair_start = true;
4193 break;
4194 }
4195 }
4196 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4197 {
4198 // take first bracket pair matching end, but don't break in case a later bracket
4199 // pair matches start
4200 bracket_pair_matching_end = Some(pair.clone());
4201 }
4202 }
4203 if let Some(end) = bracket_pair_matching_end
4204 && bracket_pair.is_none()
4205 {
4206 bracket_pair = Some(end);
4207 is_bracket_pair_end = true;
4208 }
4209 }
4210
4211 if let Some(bracket_pair) = bracket_pair {
4212 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4213 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4214 let auto_surround =
4215 self.use_auto_surround && snapshot_settings.use_auto_surround;
4216 if selection.is_empty() {
4217 if is_bracket_pair_start {
4218 // If the inserted text is a suffix of an opening bracket and the
4219 // selection is preceded by the rest of the opening bracket, then
4220 // insert the closing bracket.
4221 let following_text_allows_autoclose = snapshot
4222 .chars_at(selection.start)
4223 .next()
4224 .is_none_or(|c| scope.should_autoclose_before(c));
4225
4226 let preceding_text_allows_autoclose = selection.start.column == 0
4227 || snapshot
4228 .reversed_chars_at(selection.start)
4229 .next()
4230 .is_none_or(|c| {
4231 bracket_pair.start != bracket_pair.end
4232 || !snapshot
4233 .char_classifier_at(selection.start)
4234 .is_word(c)
4235 });
4236
4237 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4238 && bracket_pair.start.len() == 1
4239 {
4240 let target = bracket_pair.start.chars().next().unwrap();
4241 let current_line_count = snapshot
4242 .reversed_chars_at(selection.start)
4243 .take_while(|&c| c != '\n')
4244 .filter(|&c| c == target)
4245 .count();
4246 current_line_count % 2 == 1
4247 } else {
4248 false
4249 };
4250
4251 if autoclose
4252 && bracket_pair.close
4253 && following_text_allows_autoclose
4254 && preceding_text_allows_autoclose
4255 && !is_closing_quote
4256 {
4257 let anchor = snapshot.anchor_before(selection.end);
4258 new_selections.push((selection.map(|_| anchor), text.len()));
4259 new_autoclose_regions.push((
4260 anchor,
4261 text.len(),
4262 selection.id,
4263 bracket_pair.clone(),
4264 ));
4265 edits.push((
4266 selection.range(),
4267 format!("{}{}", text, bracket_pair.end).into(),
4268 ));
4269 bracket_inserted = true;
4270 continue;
4271 }
4272 }
4273
4274 if let Some(region) = autoclose_region {
4275 // If the selection is followed by an auto-inserted closing bracket,
4276 // then don't insert that closing bracket again; just move the selection
4277 // past the closing bracket.
4278 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4279 && text.as_ref() == region.pair.end.as_str()
4280 && snapshot.contains_str_at(region.range.end, text.as_ref());
4281 if should_skip {
4282 let anchor = snapshot.anchor_after(selection.end);
4283 new_selections
4284 .push((selection.map(|_| anchor), region.pair.end.len()));
4285 continue;
4286 }
4287 }
4288
4289 let always_treat_brackets_as_autoclosed = snapshot
4290 .language_settings_at(selection.start, cx)
4291 .always_treat_brackets_as_autoclosed;
4292 if always_treat_brackets_as_autoclosed
4293 && is_bracket_pair_end
4294 && snapshot.contains_str_at(selection.end, text.as_ref())
4295 {
4296 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4297 // and the inserted text is a closing bracket and the selection is followed
4298 // by the closing bracket then move the selection past the closing bracket.
4299 let anchor = snapshot.anchor_after(selection.end);
4300 new_selections.push((selection.map(|_| anchor), text.len()));
4301 continue;
4302 }
4303 }
4304 // If an opening bracket is 1 character long and is typed while
4305 // text is selected, then surround that text with the bracket pair.
4306 else if auto_surround
4307 && bracket_pair.surround
4308 && is_bracket_pair_start
4309 && bracket_pair.start.chars().count() == 1
4310 {
4311 edits.push((selection.start..selection.start, text.clone()));
4312 edits.push((
4313 selection.end..selection.end,
4314 bracket_pair.end.as_str().into(),
4315 ));
4316 bracket_inserted = true;
4317 new_selections.push((
4318 Selection {
4319 id: selection.id,
4320 start: snapshot.anchor_after(selection.start),
4321 end: snapshot.anchor_before(selection.end),
4322 reversed: selection.reversed,
4323 goal: selection.goal,
4324 },
4325 0,
4326 ));
4327 continue;
4328 }
4329 }
4330 }
4331
4332 if self.auto_replace_emoji_shortcode
4333 && selection.is_empty()
4334 && text.as_ref().ends_with(':')
4335 && let Some(possible_emoji_short_code) =
4336 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4337 && !possible_emoji_short_code.is_empty()
4338 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4339 {
4340 let emoji_shortcode_start = Point::new(
4341 selection.start.row,
4342 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4343 );
4344
4345 // Remove shortcode from buffer
4346 edits.push((
4347 emoji_shortcode_start..selection.start,
4348 "".to_string().into(),
4349 ));
4350 new_selections.push((
4351 Selection {
4352 id: selection.id,
4353 start: snapshot.anchor_after(emoji_shortcode_start),
4354 end: snapshot.anchor_before(selection.start),
4355 reversed: selection.reversed,
4356 goal: selection.goal,
4357 },
4358 0,
4359 ));
4360
4361 // Insert emoji
4362 let selection_start_anchor = snapshot.anchor_after(selection.start);
4363 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4364 edits.push((selection.start..selection.end, emoji.to_string().into()));
4365
4366 continue;
4367 }
4368
4369 // If not handling any auto-close operation, then just replace the selected
4370 // text with the given input and move the selection to the end of the
4371 // newly inserted text.
4372 let anchor = snapshot.anchor_after(selection.end);
4373 if !self.linked_edit_ranges.is_empty() {
4374 let start_anchor = snapshot.anchor_before(selection.start);
4375
4376 let is_word_char = text.chars().next().is_none_or(|char| {
4377 let classifier = snapshot
4378 .char_classifier_at(start_anchor.to_offset(&snapshot))
4379 .scope_context(Some(CharScopeContext::LinkedEdit));
4380 classifier.is_word(char)
4381 });
4382
4383 if is_word_char {
4384 if let Some(ranges) = self
4385 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4386 {
4387 for (buffer, edits) in ranges {
4388 linked_edits
4389 .entry(buffer.clone())
4390 .or_default()
4391 .extend(edits.into_iter().map(|range| (range, text.clone())));
4392 }
4393 }
4394 } else {
4395 clear_linked_edit_ranges = true;
4396 }
4397 }
4398
4399 new_selections.push((selection.map(|_| anchor), 0));
4400 edits.push((selection.start..selection.end, text.clone()));
4401 }
4402
4403 drop(snapshot);
4404
4405 self.transact(window, cx, |this, window, cx| {
4406 if clear_linked_edit_ranges {
4407 this.linked_edit_ranges.clear();
4408 }
4409 let initial_buffer_versions =
4410 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4411
4412 this.buffer.update(cx, |buffer, cx| {
4413 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4414 });
4415 for (buffer, edits) in linked_edits {
4416 buffer.update(cx, |buffer, cx| {
4417 let snapshot = buffer.snapshot();
4418 let edits = edits
4419 .into_iter()
4420 .map(|(range, text)| {
4421 use text::ToPoint as TP;
4422 let end_point = TP::to_point(&range.end, &snapshot);
4423 let start_point = TP::to_point(&range.start, &snapshot);
4424 (start_point..end_point, text)
4425 })
4426 .sorted_by_key(|(range, _)| range.start);
4427 buffer.edit(edits, None, cx);
4428 })
4429 }
4430 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4431 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4432 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4433 let new_selections =
4434 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4435 .zip(new_selection_deltas)
4436 .map(|(selection, delta)| Selection {
4437 id: selection.id,
4438 start: selection.start + delta,
4439 end: selection.end + delta,
4440 reversed: selection.reversed,
4441 goal: SelectionGoal::None,
4442 })
4443 .collect::<Vec<_>>();
4444
4445 let mut i = 0;
4446 for (position, delta, selection_id, pair) in new_autoclose_regions {
4447 let position = position.to_offset(map.buffer_snapshot()) + delta;
4448 let start = map.buffer_snapshot().anchor_before(position);
4449 let end = map.buffer_snapshot().anchor_after(position);
4450 while let Some(existing_state) = this.autoclose_regions.get(i) {
4451 match existing_state
4452 .range
4453 .start
4454 .cmp(&start, map.buffer_snapshot())
4455 {
4456 Ordering::Less => i += 1,
4457 Ordering::Greater => break,
4458 Ordering::Equal => {
4459 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4460 Ordering::Less => i += 1,
4461 Ordering::Equal => break,
4462 Ordering::Greater => break,
4463 }
4464 }
4465 }
4466 }
4467 this.autoclose_regions.insert(
4468 i,
4469 AutocloseRegion {
4470 selection_id,
4471 range: start..end,
4472 pair,
4473 },
4474 );
4475 }
4476
4477 let had_active_edit_prediction = this.has_active_edit_prediction();
4478 this.change_selections(
4479 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4480 window,
4481 cx,
4482 |s| s.select(new_selections),
4483 );
4484
4485 if !bracket_inserted
4486 && let Some(on_type_format_task) =
4487 this.trigger_on_type_formatting(text.to_string(), window, cx)
4488 {
4489 on_type_format_task.detach_and_log_err(cx);
4490 }
4491
4492 let editor_settings = EditorSettings::get_global(cx);
4493 if bracket_inserted
4494 && (editor_settings.auto_signature_help
4495 || editor_settings.show_signature_help_after_edits)
4496 {
4497 this.show_signature_help(&ShowSignatureHelp, window, cx);
4498 }
4499
4500 let trigger_in_words =
4501 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4502 if this.hard_wrap.is_some() {
4503 let latest: Range<Point> = this.selections.newest(&map).range();
4504 if latest.is_empty()
4505 && this
4506 .buffer()
4507 .read(cx)
4508 .snapshot(cx)
4509 .line_len(MultiBufferRow(latest.start.row))
4510 == latest.start.column
4511 {
4512 this.rewrap_impl(
4513 RewrapOptions {
4514 override_language_settings: true,
4515 preserve_existing_whitespace: true,
4516 },
4517 cx,
4518 )
4519 }
4520 }
4521 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4522 refresh_linked_ranges(this, window, cx);
4523 this.refresh_edit_prediction(true, false, window, cx);
4524 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4525 });
4526 }
4527
4528 fn find_possible_emoji_shortcode_at_position(
4529 snapshot: &MultiBufferSnapshot,
4530 position: Point,
4531 ) -> Option<String> {
4532 let mut chars = Vec::new();
4533 let mut found_colon = false;
4534 for char in snapshot.reversed_chars_at(position).take(100) {
4535 // Found a possible emoji shortcode in the middle of the buffer
4536 if found_colon {
4537 if char.is_whitespace() {
4538 chars.reverse();
4539 return Some(chars.iter().collect());
4540 }
4541 // If the previous character is not a whitespace, we are in the middle of a word
4542 // and we only want to complete the shortcode if the word is made up of other emojis
4543 let mut containing_word = String::new();
4544 for ch in snapshot
4545 .reversed_chars_at(position)
4546 .skip(chars.len() + 1)
4547 .take(100)
4548 {
4549 if ch.is_whitespace() {
4550 break;
4551 }
4552 containing_word.push(ch);
4553 }
4554 let containing_word = containing_word.chars().rev().collect::<String>();
4555 if util::word_consists_of_emojis(containing_word.as_str()) {
4556 chars.reverse();
4557 return Some(chars.iter().collect());
4558 }
4559 }
4560
4561 if char.is_whitespace() || !char.is_ascii() {
4562 return None;
4563 }
4564 if char == ':' {
4565 found_colon = true;
4566 } else {
4567 chars.push(char);
4568 }
4569 }
4570 // Found a possible emoji shortcode at the beginning of the buffer
4571 chars.reverse();
4572 Some(chars.iter().collect())
4573 }
4574
4575 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4576 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4577 self.transact(window, cx, |this, window, cx| {
4578 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4579 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4580 let multi_buffer = this.buffer.read(cx);
4581 let buffer = multi_buffer.snapshot(cx);
4582 selections
4583 .iter()
4584 .map(|selection| {
4585 let start_point = selection.start.to_point(&buffer);
4586 let mut existing_indent =
4587 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4588 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4589 let start = selection.start;
4590 let end = selection.end;
4591 let selection_is_empty = start == end;
4592 let language_scope = buffer.language_scope_at(start);
4593 let (
4594 comment_delimiter,
4595 doc_delimiter,
4596 insert_extra_newline,
4597 indent_on_newline,
4598 indent_on_extra_newline,
4599 ) = if let Some(language) = &language_scope {
4600 let mut insert_extra_newline =
4601 insert_extra_newline_brackets(&buffer, start..end, language)
4602 || insert_extra_newline_tree_sitter(&buffer, start..end);
4603
4604 // Comment extension on newline is allowed only for cursor selections
4605 let comment_delimiter = maybe!({
4606 if !selection_is_empty {
4607 return None;
4608 }
4609
4610 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4611 return None;
4612 }
4613
4614 let delimiters = language.line_comment_prefixes();
4615 let max_len_of_delimiter =
4616 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4617 let (snapshot, range) =
4618 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4619
4620 let num_of_whitespaces = snapshot
4621 .chars_for_range(range.clone())
4622 .take_while(|c| c.is_whitespace())
4623 .count();
4624 let comment_candidate = snapshot
4625 .chars_for_range(range.clone())
4626 .skip(num_of_whitespaces)
4627 .take(max_len_of_delimiter)
4628 .collect::<String>();
4629 let (delimiter, trimmed_len) = delimiters
4630 .iter()
4631 .filter_map(|delimiter| {
4632 let prefix = delimiter.trim_end();
4633 if comment_candidate.starts_with(prefix) {
4634 Some((delimiter, prefix.len()))
4635 } else {
4636 None
4637 }
4638 })
4639 .max_by_key(|(_, len)| *len)?;
4640
4641 if let Some(BlockCommentConfig {
4642 start: block_start, ..
4643 }) = language.block_comment()
4644 {
4645 let block_start_trimmed = block_start.trim_end();
4646 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4647 let line_content = snapshot
4648 .chars_for_range(range)
4649 .skip(num_of_whitespaces)
4650 .take(block_start_trimmed.len())
4651 .collect::<String>();
4652
4653 if line_content.starts_with(block_start_trimmed) {
4654 return None;
4655 }
4656 }
4657 }
4658
4659 let cursor_is_placed_after_comment_marker =
4660 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4661 if cursor_is_placed_after_comment_marker {
4662 Some(delimiter.clone())
4663 } else {
4664 None
4665 }
4666 });
4667
4668 let mut indent_on_newline = IndentSize::spaces(0);
4669 let mut indent_on_extra_newline = IndentSize::spaces(0);
4670
4671 let doc_delimiter = maybe!({
4672 if !selection_is_empty {
4673 return None;
4674 }
4675
4676 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4677 return None;
4678 }
4679
4680 let BlockCommentConfig {
4681 start: start_tag,
4682 end: end_tag,
4683 prefix: delimiter,
4684 tab_size: len,
4685 } = language.documentation_comment()?;
4686 let is_within_block_comment = buffer
4687 .language_scope_at(start_point)
4688 .is_some_and(|scope| scope.override_name() == Some("comment"));
4689 if !is_within_block_comment {
4690 return None;
4691 }
4692
4693 let (snapshot, range) =
4694 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4695
4696 let num_of_whitespaces = snapshot
4697 .chars_for_range(range.clone())
4698 .take_while(|c| c.is_whitespace())
4699 .count();
4700
4701 // 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.
4702 let column = start_point.column;
4703 let cursor_is_after_start_tag = {
4704 let start_tag_len = start_tag.len();
4705 let start_tag_line = snapshot
4706 .chars_for_range(range.clone())
4707 .skip(num_of_whitespaces)
4708 .take(start_tag_len)
4709 .collect::<String>();
4710 if start_tag_line.starts_with(start_tag.as_ref()) {
4711 num_of_whitespaces + start_tag_len <= column as usize
4712 } else {
4713 false
4714 }
4715 };
4716
4717 let cursor_is_after_delimiter = {
4718 let delimiter_trim = delimiter.trim_end();
4719 let delimiter_line = snapshot
4720 .chars_for_range(range.clone())
4721 .skip(num_of_whitespaces)
4722 .take(delimiter_trim.len())
4723 .collect::<String>();
4724 if delimiter_line.starts_with(delimiter_trim) {
4725 num_of_whitespaces + delimiter_trim.len() <= column as usize
4726 } else {
4727 false
4728 }
4729 };
4730
4731 let cursor_is_before_end_tag_if_exists = {
4732 let mut char_position = 0u32;
4733 let mut end_tag_offset = None;
4734
4735 'outer: for chunk in snapshot.text_for_range(range) {
4736 if let Some(byte_pos) = chunk.find(&**end_tag) {
4737 let chars_before_match =
4738 chunk[..byte_pos].chars().count() as u32;
4739 end_tag_offset =
4740 Some(char_position + chars_before_match);
4741 break 'outer;
4742 }
4743 char_position += chunk.chars().count() as u32;
4744 }
4745
4746 if let Some(end_tag_offset) = end_tag_offset {
4747 let cursor_is_before_end_tag = column <= end_tag_offset;
4748 if cursor_is_after_start_tag {
4749 if cursor_is_before_end_tag {
4750 insert_extra_newline = true;
4751 }
4752 let cursor_is_at_start_of_end_tag =
4753 column == end_tag_offset;
4754 if cursor_is_at_start_of_end_tag {
4755 indent_on_extra_newline.len = *len;
4756 }
4757 }
4758 cursor_is_before_end_tag
4759 } else {
4760 true
4761 }
4762 };
4763
4764 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4765 && cursor_is_before_end_tag_if_exists
4766 {
4767 if cursor_is_after_start_tag {
4768 indent_on_newline.len = *len;
4769 }
4770 Some(delimiter.clone())
4771 } else {
4772 None
4773 }
4774 });
4775
4776 (
4777 comment_delimiter,
4778 doc_delimiter,
4779 insert_extra_newline,
4780 indent_on_newline,
4781 indent_on_extra_newline,
4782 )
4783 } else {
4784 (
4785 None,
4786 None,
4787 false,
4788 IndentSize::default(),
4789 IndentSize::default(),
4790 )
4791 };
4792
4793 let prevent_auto_indent = doc_delimiter.is_some();
4794 let delimiter = comment_delimiter.or(doc_delimiter);
4795
4796 let capacity_for_delimiter =
4797 delimiter.as_deref().map(str::len).unwrap_or_default();
4798 let mut new_text = String::with_capacity(
4799 1 + capacity_for_delimiter
4800 + existing_indent.len as usize
4801 + indent_on_newline.len as usize
4802 + indent_on_extra_newline.len as usize,
4803 );
4804 new_text.push('\n');
4805 new_text.extend(existing_indent.chars());
4806 new_text.extend(indent_on_newline.chars());
4807
4808 if let Some(delimiter) = &delimiter {
4809 new_text.push_str(delimiter);
4810 }
4811
4812 if insert_extra_newline {
4813 new_text.push('\n');
4814 new_text.extend(existing_indent.chars());
4815 new_text.extend(indent_on_extra_newline.chars());
4816 }
4817
4818 let anchor = buffer.anchor_after(end);
4819 let new_selection = selection.map(|_| anchor);
4820 (
4821 ((start..end, new_text), prevent_auto_indent),
4822 (insert_extra_newline, new_selection),
4823 )
4824 })
4825 .unzip()
4826 };
4827
4828 let mut auto_indent_edits = Vec::new();
4829 let mut edits = Vec::new();
4830 for (edit, prevent_auto_indent) in edits_with_flags {
4831 if prevent_auto_indent {
4832 edits.push(edit);
4833 } else {
4834 auto_indent_edits.push(edit);
4835 }
4836 }
4837 if !edits.is_empty() {
4838 this.edit(edits, cx);
4839 }
4840 if !auto_indent_edits.is_empty() {
4841 this.edit_with_autoindent(auto_indent_edits, cx);
4842 }
4843
4844 let buffer = this.buffer.read(cx).snapshot(cx);
4845 let new_selections = selection_info
4846 .into_iter()
4847 .map(|(extra_newline_inserted, new_selection)| {
4848 let mut cursor = new_selection.end.to_point(&buffer);
4849 if extra_newline_inserted {
4850 cursor.row -= 1;
4851 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4852 }
4853 new_selection.map(|_| cursor)
4854 })
4855 .collect();
4856
4857 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4858 this.refresh_edit_prediction(true, false, window, cx);
4859 });
4860 }
4861
4862 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4863 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4864
4865 let buffer = self.buffer.read(cx);
4866 let snapshot = buffer.snapshot(cx);
4867
4868 let mut edits = Vec::new();
4869 let mut rows = Vec::new();
4870
4871 for (rows_inserted, selection) in self
4872 .selections
4873 .all_adjusted(&self.display_snapshot(cx))
4874 .into_iter()
4875 .enumerate()
4876 {
4877 let cursor = selection.head();
4878 let row = cursor.row;
4879
4880 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4881
4882 let newline = "\n".to_string();
4883 edits.push((start_of_line..start_of_line, newline));
4884
4885 rows.push(row + rows_inserted as u32);
4886 }
4887
4888 self.transact(window, cx, |editor, window, cx| {
4889 editor.edit(edits, cx);
4890
4891 editor.change_selections(Default::default(), window, cx, |s| {
4892 let mut index = 0;
4893 s.move_cursors_with(|map, _, _| {
4894 let row = rows[index];
4895 index += 1;
4896
4897 let point = Point::new(row, 0);
4898 let boundary = map.next_line_boundary(point).1;
4899 let clipped = map.clip_point(boundary, Bias::Left);
4900
4901 (clipped, SelectionGoal::None)
4902 });
4903 });
4904
4905 let mut indent_edits = Vec::new();
4906 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4907 for row in rows {
4908 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4909 for (row, indent) in indents {
4910 if indent.len == 0 {
4911 continue;
4912 }
4913
4914 let text = match indent.kind {
4915 IndentKind::Space => " ".repeat(indent.len as usize),
4916 IndentKind::Tab => "\t".repeat(indent.len as usize),
4917 };
4918 let point = Point::new(row.0, 0);
4919 indent_edits.push((point..point, text));
4920 }
4921 }
4922 editor.edit(indent_edits, cx);
4923 });
4924 }
4925
4926 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4927 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4928
4929 let buffer = self.buffer.read(cx);
4930 let snapshot = buffer.snapshot(cx);
4931
4932 let mut edits = Vec::new();
4933 let mut rows = Vec::new();
4934 let mut rows_inserted = 0;
4935
4936 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4937 let cursor = selection.head();
4938 let row = cursor.row;
4939
4940 let point = Point::new(row + 1, 0);
4941 let start_of_line = snapshot.clip_point(point, Bias::Left);
4942
4943 let newline = "\n".to_string();
4944 edits.push((start_of_line..start_of_line, newline));
4945
4946 rows_inserted += 1;
4947 rows.push(row + rows_inserted);
4948 }
4949
4950 self.transact(window, cx, |editor, window, cx| {
4951 editor.edit(edits, cx);
4952
4953 editor.change_selections(Default::default(), window, cx, |s| {
4954 let mut index = 0;
4955 s.move_cursors_with(|map, _, _| {
4956 let row = rows[index];
4957 index += 1;
4958
4959 let point = Point::new(row, 0);
4960 let boundary = map.next_line_boundary(point).1;
4961 let clipped = map.clip_point(boundary, Bias::Left);
4962
4963 (clipped, SelectionGoal::None)
4964 });
4965 });
4966
4967 let mut indent_edits = Vec::new();
4968 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4969 for row in rows {
4970 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4971 for (row, indent) in indents {
4972 if indent.len == 0 {
4973 continue;
4974 }
4975
4976 let text = match indent.kind {
4977 IndentKind::Space => " ".repeat(indent.len as usize),
4978 IndentKind::Tab => "\t".repeat(indent.len as usize),
4979 };
4980 let point = Point::new(row.0, 0);
4981 indent_edits.push((point..point, text));
4982 }
4983 }
4984 editor.edit(indent_edits, cx);
4985 });
4986 }
4987
4988 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4989 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4990 original_indent_columns: Vec::new(),
4991 });
4992 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4993 }
4994
4995 fn insert_with_autoindent_mode(
4996 &mut self,
4997 text: &str,
4998 autoindent_mode: Option<AutoindentMode>,
4999 window: &mut Window,
5000 cx: &mut Context<Self>,
5001 ) {
5002 if self.read_only(cx) {
5003 return;
5004 }
5005
5006 let text: Arc<str> = text.into();
5007 self.transact(window, cx, |this, window, cx| {
5008 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5009 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5010 let anchors = {
5011 let snapshot = buffer.read(cx);
5012 old_selections
5013 .iter()
5014 .map(|s| {
5015 let anchor = snapshot.anchor_after(s.head());
5016 s.map(|_| anchor)
5017 })
5018 .collect::<Vec<_>>()
5019 };
5020 buffer.edit(
5021 old_selections
5022 .iter()
5023 .map(|s| (s.start..s.end, text.clone())),
5024 autoindent_mode,
5025 cx,
5026 );
5027 anchors
5028 });
5029
5030 this.change_selections(Default::default(), window, cx, |s| {
5031 s.select_anchors(selection_anchors);
5032 });
5033
5034 cx.notify();
5035 });
5036 }
5037
5038 fn trigger_completion_on_input(
5039 &mut self,
5040 text: &str,
5041 trigger_in_words: bool,
5042 window: &mut Window,
5043 cx: &mut Context<Self>,
5044 ) {
5045 let completions_source = self
5046 .context_menu
5047 .borrow()
5048 .as_ref()
5049 .and_then(|menu| match menu {
5050 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5051 CodeContextMenu::CodeActions(_) => None,
5052 });
5053
5054 match completions_source {
5055 Some(CompletionsMenuSource::Words { .. }) => {
5056 self.open_or_update_completions_menu(
5057 Some(CompletionsMenuSource::Words {
5058 ignore_threshold: false,
5059 }),
5060 None,
5061 window,
5062 cx,
5063 );
5064 }
5065 Some(CompletionsMenuSource::Normal)
5066 | Some(CompletionsMenuSource::SnippetChoices)
5067 | None
5068 if self.is_completion_trigger(
5069 text,
5070 trigger_in_words,
5071 completions_source.is_some(),
5072 cx,
5073 ) =>
5074 {
5075 self.show_completions(
5076 &ShowCompletions {
5077 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
5078 },
5079 window,
5080 cx,
5081 )
5082 }
5083 _ => {
5084 self.hide_context_menu(window, cx);
5085 }
5086 }
5087 }
5088
5089 fn is_completion_trigger(
5090 &self,
5091 text: &str,
5092 trigger_in_words: bool,
5093 menu_is_open: bool,
5094 cx: &mut Context<Self>,
5095 ) -> bool {
5096 let position = self.selections.newest_anchor().head();
5097 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
5098 return false;
5099 };
5100
5101 if let Some(completion_provider) = &self.completion_provider {
5102 completion_provider.is_completion_trigger(
5103 &buffer,
5104 position.text_anchor,
5105 text,
5106 trigger_in_words,
5107 menu_is_open,
5108 cx,
5109 )
5110 } else {
5111 false
5112 }
5113 }
5114
5115 /// If any empty selections is touching the start of its innermost containing autoclose
5116 /// region, expand it to select the brackets.
5117 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5118 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5119 let buffer = self.buffer.read(cx).read(cx);
5120 let new_selections = self
5121 .selections_with_autoclose_regions(selections, &buffer)
5122 .map(|(mut selection, region)| {
5123 if !selection.is_empty() {
5124 return selection;
5125 }
5126
5127 if let Some(region) = region {
5128 let mut range = region.range.to_offset(&buffer);
5129 if selection.start == range.start && range.start >= region.pair.start.len() {
5130 range.start -= region.pair.start.len();
5131 if buffer.contains_str_at(range.start, ®ion.pair.start)
5132 && buffer.contains_str_at(range.end, ®ion.pair.end)
5133 {
5134 range.end += region.pair.end.len();
5135 selection.start = range.start;
5136 selection.end = range.end;
5137
5138 return selection;
5139 }
5140 }
5141 }
5142
5143 let always_treat_brackets_as_autoclosed = buffer
5144 .language_settings_at(selection.start, cx)
5145 .always_treat_brackets_as_autoclosed;
5146
5147 if !always_treat_brackets_as_autoclosed {
5148 return selection;
5149 }
5150
5151 if let Some(scope) = buffer.language_scope_at(selection.start) {
5152 for (pair, enabled) in scope.brackets() {
5153 if !enabled || !pair.close {
5154 continue;
5155 }
5156
5157 if buffer.contains_str_at(selection.start, &pair.end) {
5158 let pair_start_len = pair.start.len();
5159 if buffer.contains_str_at(
5160 selection.start.saturating_sub(pair_start_len),
5161 &pair.start,
5162 ) {
5163 selection.start -= pair_start_len;
5164 selection.end += pair.end.len();
5165
5166 return selection;
5167 }
5168 }
5169 }
5170 }
5171
5172 selection
5173 })
5174 .collect();
5175
5176 drop(buffer);
5177 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5178 selections.select(new_selections)
5179 });
5180 }
5181
5182 /// Iterate the given selections, and for each one, find the smallest surrounding
5183 /// autoclose region. This uses the ordering of the selections and the autoclose
5184 /// regions to avoid repeated comparisons.
5185 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5186 &'a self,
5187 selections: impl IntoIterator<Item = Selection<D>>,
5188 buffer: &'a MultiBufferSnapshot,
5189 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5190 let mut i = 0;
5191 let mut regions = self.autoclose_regions.as_slice();
5192 selections.into_iter().map(move |selection| {
5193 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5194
5195 let mut enclosing = None;
5196 while let Some(pair_state) = regions.get(i) {
5197 if pair_state.range.end.to_offset(buffer) < range.start {
5198 regions = ®ions[i + 1..];
5199 i = 0;
5200 } else if pair_state.range.start.to_offset(buffer) > range.end {
5201 break;
5202 } else {
5203 if pair_state.selection_id == selection.id {
5204 enclosing = Some(pair_state);
5205 }
5206 i += 1;
5207 }
5208 }
5209
5210 (selection, enclosing)
5211 })
5212 }
5213
5214 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5215 fn invalidate_autoclose_regions(
5216 &mut self,
5217 mut selections: &[Selection<Anchor>],
5218 buffer: &MultiBufferSnapshot,
5219 ) {
5220 self.autoclose_regions.retain(|state| {
5221 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5222 return false;
5223 }
5224
5225 let mut i = 0;
5226 while let Some(selection) = selections.get(i) {
5227 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5228 selections = &selections[1..];
5229 continue;
5230 }
5231 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5232 break;
5233 }
5234 if selection.id == state.selection_id {
5235 return true;
5236 } else {
5237 i += 1;
5238 }
5239 }
5240 false
5241 });
5242 }
5243
5244 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5245 let offset = position.to_offset(buffer);
5246 let (word_range, kind) =
5247 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5248 if offset > word_range.start && kind == Some(CharKind::Word) {
5249 Some(
5250 buffer
5251 .text_for_range(word_range.start..offset)
5252 .collect::<String>(),
5253 )
5254 } else {
5255 None
5256 }
5257 }
5258
5259 pub fn visible_excerpts(
5260 &self,
5261 cx: &mut Context<Editor>,
5262 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5263 let Some(project) = self.project() else {
5264 return HashMap::default();
5265 };
5266 let project = project.read(cx);
5267 let multi_buffer = self.buffer().read(cx);
5268 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5269 let multi_buffer_visible_start = self
5270 .scroll_manager
5271 .anchor()
5272 .anchor
5273 .to_point(&multi_buffer_snapshot);
5274 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5275 multi_buffer_visible_start
5276 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5277 Bias::Left,
5278 );
5279 multi_buffer_snapshot
5280 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5281 .into_iter()
5282 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5283 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5284 let buffer_file = project::File::from_dyn(buffer.file())?;
5285 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5286 let worktree_entry = buffer_worktree
5287 .read(cx)
5288 .entry_for_id(buffer_file.project_entry_id()?)?;
5289 if worktree_entry.is_ignored {
5290 None
5291 } else {
5292 Some((
5293 excerpt_id,
5294 (
5295 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5296 buffer.version().clone(),
5297 excerpt_visible_range,
5298 ),
5299 ))
5300 }
5301 })
5302 .collect()
5303 }
5304
5305 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5306 TextLayoutDetails {
5307 text_system: window.text_system().clone(),
5308 editor_style: self.style.clone().unwrap(),
5309 rem_size: window.rem_size(),
5310 scroll_anchor: self.scroll_manager.anchor(),
5311 visible_rows: self.visible_line_count(),
5312 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5313 }
5314 }
5315
5316 fn trigger_on_type_formatting(
5317 &self,
5318 input: String,
5319 window: &mut Window,
5320 cx: &mut Context<Self>,
5321 ) -> Option<Task<Result<()>>> {
5322 if input.len() != 1 {
5323 return None;
5324 }
5325
5326 let project = self.project()?;
5327 let position = self.selections.newest_anchor().head();
5328 let (buffer, buffer_position) = self
5329 .buffer
5330 .read(cx)
5331 .text_anchor_for_position(position, cx)?;
5332
5333 let settings = language_settings::language_settings(
5334 buffer
5335 .read(cx)
5336 .language_at(buffer_position)
5337 .map(|l| l.name()),
5338 buffer.read(cx).file(),
5339 cx,
5340 );
5341 if !settings.use_on_type_format {
5342 return None;
5343 }
5344
5345 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5346 // hence we do LSP request & edit on host side only — add formats to host's history.
5347 let push_to_lsp_host_history = true;
5348 // If this is not the host, append its history with new edits.
5349 let push_to_client_history = project.read(cx).is_via_collab();
5350
5351 let on_type_formatting = project.update(cx, |project, cx| {
5352 project.on_type_format(
5353 buffer.clone(),
5354 buffer_position,
5355 input,
5356 push_to_lsp_host_history,
5357 cx,
5358 )
5359 });
5360 Some(cx.spawn_in(window, async move |editor, cx| {
5361 if let Some(transaction) = on_type_formatting.await? {
5362 if push_to_client_history {
5363 buffer
5364 .update(cx, |buffer, _| {
5365 buffer.push_transaction(transaction, Instant::now());
5366 buffer.finalize_last_transaction();
5367 })
5368 .ok();
5369 }
5370 editor.update(cx, |editor, cx| {
5371 editor.refresh_document_highlights(cx);
5372 })?;
5373 }
5374 Ok(())
5375 }))
5376 }
5377
5378 pub fn show_word_completions(
5379 &mut self,
5380 _: &ShowWordCompletions,
5381 window: &mut Window,
5382 cx: &mut Context<Self>,
5383 ) {
5384 self.open_or_update_completions_menu(
5385 Some(CompletionsMenuSource::Words {
5386 ignore_threshold: true,
5387 }),
5388 None,
5389 window,
5390 cx,
5391 );
5392 }
5393
5394 pub fn show_completions(
5395 &mut self,
5396 options: &ShowCompletions,
5397 window: &mut Window,
5398 cx: &mut Context<Self>,
5399 ) {
5400 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5401 }
5402
5403 fn open_or_update_completions_menu(
5404 &mut self,
5405 requested_source: Option<CompletionsMenuSource>,
5406 trigger: Option<&str>,
5407 window: &mut Window,
5408 cx: &mut Context<Self>,
5409 ) {
5410 if self.pending_rename.is_some() {
5411 return;
5412 }
5413
5414 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5415
5416 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5417 // inserted and selected. To handle that case, the start of the selection is used so that
5418 // the menu starts with all choices.
5419 let position = self
5420 .selections
5421 .newest_anchor()
5422 .start
5423 .bias_right(&multibuffer_snapshot);
5424 if position.diff_base_anchor.is_some() {
5425 return;
5426 }
5427 let buffer_position = multibuffer_snapshot.anchor_before(position);
5428 let Some(buffer) = buffer_position
5429 .buffer_id
5430 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5431 else {
5432 return;
5433 };
5434 let buffer_snapshot = buffer.read(cx).snapshot();
5435
5436 let query: Option<Arc<String>> =
5437 Self::completion_query(&multibuffer_snapshot, buffer_position)
5438 .map(|query| query.into());
5439
5440 drop(multibuffer_snapshot);
5441
5442 // Hide the current completions menu when query is empty. Without this, cached
5443 // completions from before the trigger char may be reused (#32774).
5444 if query.is_none() {
5445 let menu_is_open = matches!(
5446 self.context_menu.borrow().as_ref(),
5447 Some(CodeContextMenu::Completions(_))
5448 );
5449 if menu_is_open {
5450 self.hide_context_menu(window, cx);
5451 }
5452 }
5453
5454 let mut ignore_word_threshold = false;
5455 let provider = match requested_source {
5456 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5457 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5458 ignore_word_threshold = ignore_threshold;
5459 None
5460 }
5461 Some(CompletionsMenuSource::SnippetChoices) => {
5462 log::error!("bug: SnippetChoices requested_source is not handled");
5463 None
5464 }
5465 };
5466
5467 let sort_completions = provider
5468 .as_ref()
5469 .is_some_and(|provider| provider.sort_completions());
5470
5471 let filter_completions = provider
5472 .as_ref()
5473 .is_none_or(|provider| provider.filter_completions());
5474
5475 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5476 if filter_completions {
5477 menu.filter(query.clone(), provider.clone(), window, cx);
5478 }
5479 // When `is_incomplete` is false, no need to re-query completions when the current query
5480 // is a suffix of the initial query.
5481 if !menu.is_incomplete {
5482 // If the new query is a suffix of the old query (typing more characters) and
5483 // the previous result was complete, the existing completions can be filtered.
5484 //
5485 // Note that this is always true for snippet completions.
5486 let query_matches = match (&menu.initial_query, &query) {
5487 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5488 (None, _) => true,
5489 _ => false,
5490 };
5491 if query_matches {
5492 let position_matches = if menu.initial_position == position {
5493 true
5494 } else {
5495 let snapshot = self.buffer.read(cx).read(cx);
5496 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5497 };
5498 if position_matches {
5499 return;
5500 }
5501 }
5502 }
5503 };
5504
5505 let trigger_kind = match trigger {
5506 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5507 CompletionTriggerKind::TRIGGER_CHARACTER
5508 }
5509 _ => CompletionTriggerKind::INVOKED,
5510 };
5511 let completion_context = CompletionContext {
5512 trigger_character: trigger.and_then(|trigger| {
5513 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5514 Some(String::from(trigger))
5515 } else {
5516 None
5517 }
5518 }),
5519 trigger_kind,
5520 };
5521
5522 let Anchor {
5523 excerpt_id: buffer_excerpt_id,
5524 text_anchor: buffer_position,
5525 ..
5526 } = buffer_position;
5527
5528 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5529 buffer_snapshot.surrounding_word(buffer_position, None)
5530 {
5531 let word_to_exclude = buffer_snapshot
5532 .text_for_range(word_range.clone())
5533 .collect::<String>();
5534 (
5535 buffer_snapshot.anchor_before(word_range.start)
5536 ..buffer_snapshot.anchor_after(buffer_position),
5537 Some(word_to_exclude),
5538 )
5539 } else {
5540 (buffer_position..buffer_position, None)
5541 };
5542
5543 let language = buffer_snapshot
5544 .language_at(buffer_position)
5545 .map(|language| language.name());
5546
5547 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5548 .completions
5549 .clone();
5550
5551 let show_completion_documentation = buffer_snapshot
5552 .settings_at(buffer_position, cx)
5553 .show_completion_documentation;
5554
5555 // The document can be large, so stay in reasonable bounds when searching for words,
5556 // otherwise completion pop-up might be slow to appear.
5557 const WORD_LOOKUP_ROWS: u32 = 5_000;
5558 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5559 let min_word_search = buffer_snapshot.clip_point(
5560 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5561 Bias::Left,
5562 );
5563 let max_word_search = buffer_snapshot.clip_point(
5564 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5565 Bias::Right,
5566 );
5567 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5568 ..buffer_snapshot.point_to_offset(max_word_search);
5569
5570 let skip_digits = query
5571 .as_ref()
5572 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5573
5574 let omit_word_completions = !self.word_completions_enabled
5575 || (!ignore_word_threshold
5576 && match &query {
5577 Some(query) => query.chars().count() < completion_settings.words_min_length,
5578 None => completion_settings.words_min_length != 0,
5579 });
5580
5581 let (mut words, provider_responses) = match &provider {
5582 Some(provider) => {
5583 let provider_responses = provider.completions(
5584 buffer_excerpt_id,
5585 &buffer,
5586 buffer_position,
5587 completion_context,
5588 window,
5589 cx,
5590 );
5591
5592 let words = match (omit_word_completions, completion_settings.words) {
5593 (true, _) | (_, WordsCompletionMode::Disabled) => {
5594 Task::ready(BTreeMap::default())
5595 }
5596 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5597 .background_spawn(async move {
5598 buffer_snapshot.words_in_range(WordsQuery {
5599 fuzzy_contents: None,
5600 range: word_search_range,
5601 skip_digits,
5602 })
5603 }),
5604 };
5605
5606 (words, provider_responses)
5607 }
5608 None => {
5609 let words = if omit_word_completions {
5610 Task::ready(BTreeMap::default())
5611 } else {
5612 cx.background_spawn(async move {
5613 buffer_snapshot.words_in_range(WordsQuery {
5614 fuzzy_contents: None,
5615 range: word_search_range,
5616 skip_digits,
5617 })
5618 })
5619 };
5620 (words, Task::ready(Ok(Vec::new())))
5621 }
5622 };
5623
5624 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5625
5626 let id = post_inc(&mut self.next_completion_id);
5627 let task = cx.spawn_in(window, async move |editor, cx| {
5628 let Ok(()) = editor.update(cx, |this, _| {
5629 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5630 }) else {
5631 return;
5632 };
5633
5634 // TODO: Ideally completions from different sources would be selectively re-queried, so
5635 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5636 let mut completions = Vec::new();
5637 let mut is_incomplete = false;
5638 let mut display_options: Option<CompletionDisplayOptions> = None;
5639 if let Some(provider_responses) = provider_responses.await.log_err()
5640 && !provider_responses.is_empty()
5641 {
5642 for response in provider_responses {
5643 completions.extend(response.completions);
5644 is_incomplete = is_incomplete || response.is_incomplete;
5645 match display_options.as_mut() {
5646 None => {
5647 display_options = Some(response.display_options);
5648 }
5649 Some(options) => options.merge(&response.display_options),
5650 }
5651 }
5652 if completion_settings.words == WordsCompletionMode::Fallback {
5653 words = Task::ready(BTreeMap::default());
5654 }
5655 }
5656 let display_options = display_options.unwrap_or_default();
5657
5658 let mut words = words.await;
5659 if let Some(word_to_exclude) = &word_to_exclude {
5660 words.remove(word_to_exclude);
5661 }
5662 for lsp_completion in &completions {
5663 words.remove(&lsp_completion.new_text);
5664 }
5665 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5666 replace_range: word_replace_range.clone(),
5667 new_text: word.clone(),
5668 label: CodeLabel::plain(word, None),
5669 icon_path: None,
5670 documentation: None,
5671 source: CompletionSource::BufferWord {
5672 word_range,
5673 resolved: false,
5674 },
5675 insert_text_mode: Some(InsertTextMode::AS_IS),
5676 confirm: None,
5677 }));
5678
5679 let menu = if completions.is_empty() {
5680 None
5681 } else {
5682 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5683 let languages = editor
5684 .workspace
5685 .as_ref()
5686 .and_then(|(workspace, _)| workspace.upgrade())
5687 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5688 let menu = CompletionsMenu::new(
5689 id,
5690 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5691 sort_completions,
5692 show_completion_documentation,
5693 position,
5694 query.clone(),
5695 is_incomplete,
5696 buffer.clone(),
5697 completions.into(),
5698 display_options,
5699 snippet_sort_order,
5700 languages,
5701 language,
5702 cx,
5703 );
5704
5705 let query = if filter_completions { query } else { None };
5706 let matches_task = if let Some(query) = query {
5707 menu.do_async_filtering(query, cx)
5708 } else {
5709 Task::ready(menu.unfiltered_matches())
5710 };
5711 (menu, matches_task)
5712 }) else {
5713 return;
5714 };
5715
5716 let matches = matches_task.await;
5717
5718 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5719 // Newer menu already set, so exit.
5720 if let Some(CodeContextMenu::Completions(prev_menu)) =
5721 editor.context_menu.borrow().as_ref()
5722 && prev_menu.id > id
5723 {
5724 return;
5725 };
5726
5727 // Only valid to take prev_menu because it the new menu is immediately set
5728 // below, or the menu is hidden.
5729 if let Some(CodeContextMenu::Completions(prev_menu)) =
5730 editor.context_menu.borrow_mut().take()
5731 {
5732 let position_matches =
5733 if prev_menu.initial_position == menu.initial_position {
5734 true
5735 } else {
5736 let snapshot = editor.buffer.read(cx).read(cx);
5737 prev_menu.initial_position.to_offset(&snapshot)
5738 == menu.initial_position.to_offset(&snapshot)
5739 };
5740 if position_matches {
5741 // Preserve markdown cache before `set_filter_results` because it will
5742 // try to populate the documentation cache.
5743 menu.preserve_markdown_cache(prev_menu);
5744 }
5745 };
5746
5747 menu.set_filter_results(matches, provider, window, cx);
5748 }) else {
5749 return;
5750 };
5751
5752 menu.visible().then_some(menu)
5753 };
5754
5755 editor
5756 .update_in(cx, |editor, window, cx| {
5757 if editor.focus_handle.is_focused(window)
5758 && let Some(menu) = menu
5759 {
5760 *editor.context_menu.borrow_mut() =
5761 Some(CodeContextMenu::Completions(menu));
5762
5763 crate::hover_popover::hide_hover(editor, cx);
5764 if editor.show_edit_predictions_in_menu() {
5765 editor.update_visible_edit_prediction(window, cx);
5766 } else {
5767 editor.discard_edit_prediction(false, cx);
5768 }
5769
5770 cx.notify();
5771 return;
5772 }
5773
5774 if editor.completion_tasks.len() <= 1 {
5775 // If there are no more completion tasks and the last menu was empty, we should hide it.
5776 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5777 // If it was already hidden and we don't show edit predictions in the menu,
5778 // we should also show the edit prediction when available.
5779 if was_hidden && editor.show_edit_predictions_in_menu() {
5780 editor.update_visible_edit_prediction(window, cx);
5781 }
5782 }
5783 })
5784 .ok();
5785 });
5786
5787 self.completion_tasks.push((id, task));
5788 }
5789
5790 #[cfg(feature = "test-support")]
5791 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5792 let menu = self.context_menu.borrow();
5793 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5794 let completions = menu.completions.borrow();
5795 Some(completions.to_vec())
5796 } else {
5797 None
5798 }
5799 }
5800
5801 pub fn with_completions_menu_matching_id<R>(
5802 &self,
5803 id: CompletionId,
5804 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5805 ) -> R {
5806 let mut context_menu = self.context_menu.borrow_mut();
5807 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5808 return f(None);
5809 };
5810 if completions_menu.id != id {
5811 return f(None);
5812 }
5813 f(Some(completions_menu))
5814 }
5815
5816 pub fn confirm_completion(
5817 &mut self,
5818 action: &ConfirmCompletion,
5819 window: &mut Window,
5820 cx: &mut Context<Self>,
5821 ) -> Option<Task<Result<()>>> {
5822 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5823 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5824 }
5825
5826 pub fn confirm_completion_insert(
5827 &mut self,
5828 _: &ConfirmCompletionInsert,
5829 window: &mut Window,
5830 cx: &mut Context<Self>,
5831 ) -> Option<Task<Result<()>>> {
5832 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5833 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5834 }
5835
5836 pub fn confirm_completion_replace(
5837 &mut self,
5838 _: &ConfirmCompletionReplace,
5839 window: &mut Window,
5840 cx: &mut Context<Self>,
5841 ) -> Option<Task<Result<()>>> {
5842 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5843 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5844 }
5845
5846 pub fn compose_completion(
5847 &mut self,
5848 action: &ComposeCompletion,
5849 window: &mut Window,
5850 cx: &mut Context<Self>,
5851 ) -> Option<Task<Result<()>>> {
5852 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5853 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5854 }
5855
5856 fn do_completion(
5857 &mut self,
5858 item_ix: Option<usize>,
5859 intent: CompletionIntent,
5860 window: &mut Window,
5861 cx: &mut Context<Editor>,
5862 ) -> Option<Task<Result<()>>> {
5863 use language::ToOffset as _;
5864
5865 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5866 else {
5867 return None;
5868 };
5869
5870 let candidate_id = {
5871 let entries = completions_menu.entries.borrow();
5872 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5873 if self.show_edit_predictions_in_menu() {
5874 self.discard_edit_prediction(true, cx);
5875 }
5876 mat.candidate_id
5877 };
5878
5879 let completion = completions_menu
5880 .completions
5881 .borrow()
5882 .get(candidate_id)?
5883 .clone();
5884 cx.stop_propagation();
5885
5886 let buffer_handle = completions_menu.buffer.clone();
5887
5888 let CompletionEdit {
5889 new_text,
5890 snippet,
5891 replace_range,
5892 } = process_completion_for_edit(
5893 &completion,
5894 intent,
5895 &buffer_handle,
5896 &completions_menu.initial_position.text_anchor,
5897 cx,
5898 );
5899
5900 let buffer = buffer_handle.read(cx);
5901 let snapshot = self.buffer.read(cx).snapshot(cx);
5902 let newest_anchor = self.selections.newest_anchor();
5903 let replace_range_multibuffer = {
5904 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5905 excerpt.map_range_from_buffer(replace_range.clone())
5906 };
5907 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5908 return None;
5909 }
5910
5911 let old_text = buffer
5912 .text_for_range(replace_range.clone())
5913 .collect::<String>();
5914 let lookbehind = newest_anchor
5915 .start
5916 .text_anchor
5917 .to_offset(buffer)
5918 .saturating_sub(replace_range.start);
5919 let lookahead = replace_range
5920 .end
5921 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5922 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5923 let suffix = &old_text[lookbehind.min(old_text.len())..];
5924
5925 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5926 let mut ranges = Vec::new();
5927 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5928
5929 for selection in &selections {
5930 let range = if selection.id == newest_anchor.id {
5931 replace_range_multibuffer.clone()
5932 } else {
5933 let mut range = selection.range();
5934
5935 // if prefix is present, don't duplicate it
5936 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5937 range.start = range.start.saturating_sub(lookbehind);
5938
5939 // if suffix is also present, mimic the newest cursor and replace it
5940 if selection.id != newest_anchor.id
5941 && snapshot.contains_str_at(range.end, suffix)
5942 {
5943 range.end += lookahead;
5944 }
5945 }
5946 range
5947 };
5948
5949 ranges.push(range.clone());
5950
5951 if !self.linked_edit_ranges.is_empty() {
5952 let start_anchor = snapshot.anchor_before(range.start);
5953 let end_anchor = snapshot.anchor_after(range.end);
5954 if let Some(ranges) = self
5955 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5956 {
5957 for (buffer, edits) in ranges {
5958 linked_edits
5959 .entry(buffer.clone())
5960 .or_default()
5961 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5962 }
5963 }
5964 }
5965 }
5966
5967 let common_prefix_len = old_text
5968 .chars()
5969 .zip(new_text.chars())
5970 .take_while(|(a, b)| a == b)
5971 .map(|(a, _)| a.len_utf8())
5972 .sum::<usize>();
5973
5974 cx.emit(EditorEvent::InputHandled {
5975 utf16_range_to_replace: None,
5976 text: new_text[common_prefix_len..].into(),
5977 });
5978
5979 self.transact(window, cx, |editor, window, cx| {
5980 if let Some(mut snippet) = snippet {
5981 snippet.text = new_text.to_string();
5982 editor
5983 .insert_snippet(&ranges, snippet, window, cx)
5984 .log_err();
5985 } else {
5986 editor.buffer.update(cx, |multi_buffer, cx| {
5987 let auto_indent = match completion.insert_text_mode {
5988 Some(InsertTextMode::AS_IS) => None,
5989 _ => editor.autoindent_mode.clone(),
5990 };
5991 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5992 multi_buffer.edit(edits, auto_indent, cx);
5993 });
5994 }
5995 for (buffer, edits) in linked_edits {
5996 buffer.update(cx, |buffer, cx| {
5997 let snapshot = buffer.snapshot();
5998 let edits = edits
5999 .into_iter()
6000 .map(|(range, text)| {
6001 use text::ToPoint as TP;
6002 let end_point = TP::to_point(&range.end, &snapshot);
6003 let start_point = TP::to_point(&range.start, &snapshot);
6004 (start_point..end_point, text)
6005 })
6006 .sorted_by_key(|(range, _)| range.start);
6007 buffer.edit(edits, None, cx);
6008 })
6009 }
6010
6011 editor.refresh_edit_prediction(true, false, window, cx);
6012 });
6013 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6014
6015 let show_new_completions_on_confirm = completion
6016 .confirm
6017 .as_ref()
6018 .is_some_and(|confirm| confirm(intent, window, cx));
6019 if show_new_completions_on_confirm {
6020 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6021 }
6022
6023 let provider = self.completion_provider.as_ref()?;
6024 drop(completion);
6025 let apply_edits = provider.apply_additional_edits_for_completion(
6026 buffer_handle,
6027 completions_menu.completions.clone(),
6028 candidate_id,
6029 true,
6030 cx,
6031 );
6032
6033 let editor_settings = EditorSettings::get_global(cx);
6034 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6035 // After the code completion is finished, users often want to know what signatures are needed.
6036 // so we should automatically call signature_help
6037 self.show_signature_help(&ShowSignatureHelp, window, cx);
6038 }
6039
6040 Some(cx.foreground_executor().spawn(async move {
6041 apply_edits.await?;
6042 Ok(())
6043 }))
6044 }
6045
6046 pub fn toggle_code_actions(
6047 &mut self,
6048 action: &ToggleCodeActions,
6049 window: &mut Window,
6050 cx: &mut Context<Self>,
6051 ) {
6052 let quick_launch = action.quick_launch;
6053 let mut context_menu = self.context_menu.borrow_mut();
6054 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6055 if code_actions.deployed_from == action.deployed_from {
6056 // Toggle if we're selecting the same one
6057 *context_menu = None;
6058 cx.notify();
6059 return;
6060 } else {
6061 // Otherwise, clear it and start a new one
6062 *context_menu = None;
6063 cx.notify();
6064 }
6065 }
6066 drop(context_menu);
6067 let snapshot = self.snapshot(window, cx);
6068 let deployed_from = action.deployed_from.clone();
6069 let action = action.clone();
6070 self.completion_tasks.clear();
6071 self.discard_edit_prediction(false, cx);
6072
6073 let multibuffer_point = match &action.deployed_from {
6074 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6075 DisplayPoint::new(*row, 0).to_point(&snapshot)
6076 }
6077 _ => self
6078 .selections
6079 .newest::<Point>(&snapshot.display_snapshot)
6080 .head(),
6081 };
6082 let Some((buffer, buffer_row)) = snapshot
6083 .buffer_snapshot()
6084 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6085 .and_then(|(buffer_snapshot, range)| {
6086 self.buffer()
6087 .read(cx)
6088 .buffer(buffer_snapshot.remote_id())
6089 .map(|buffer| (buffer, range.start.row))
6090 })
6091 else {
6092 return;
6093 };
6094 let buffer_id = buffer.read(cx).remote_id();
6095 let tasks = self
6096 .tasks
6097 .get(&(buffer_id, buffer_row))
6098 .map(|t| Arc::new(t.to_owned()));
6099
6100 if !self.focus_handle.is_focused(window) {
6101 return;
6102 }
6103 let project = self.project.clone();
6104
6105 let code_actions_task = match deployed_from {
6106 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6107 _ => self.code_actions(buffer_row, window, cx),
6108 };
6109
6110 let runnable_task = match deployed_from {
6111 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6112 _ => {
6113 let mut task_context_task = Task::ready(None);
6114 if let Some(tasks) = &tasks
6115 && let Some(project) = project
6116 {
6117 task_context_task =
6118 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6119 }
6120
6121 cx.spawn_in(window, {
6122 let buffer = buffer.clone();
6123 async move |editor, cx| {
6124 let task_context = task_context_task.await;
6125
6126 let resolved_tasks =
6127 tasks
6128 .zip(task_context.clone())
6129 .map(|(tasks, task_context)| ResolvedTasks {
6130 templates: tasks.resolve(&task_context).collect(),
6131 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6132 multibuffer_point.row,
6133 tasks.column,
6134 )),
6135 });
6136 let debug_scenarios = editor
6137 .update(cx, |editor, cx| {
6138 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6139 })?
6140 .await;
6141 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6142 }
6143 })
6144 }
6145 };
6146
6147 cx.spawn_in(window, async move |editor, cx| {
6148 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6149 let code_actions = code_actions_task.await;
6150 let spawn_straight_away = quick_launch
6151 && resolved_tasks
6152 .as_ref()
6153 .is_some_and(|tasks| tasks.templates.len() == 1)
6154 && code_actions
6155 .as_ref()
6156 .is_none_or(|actions| actions.is_empty())
6157 && debug_scenarios.is_empty();
6158
6159 editor.update_in(cx, |editor, window, cx| {
6160 crate::hover_popover::hide_hover(editor, cx);
6161 let actions = CodeActionContents::new(
6162 resolved_tasks,
6163 code_actions,
6164 debug_scenarios,
6165 task_context.unwrap_or_default(),
6166 );
6167
6168 // Don't show the menu if there are no actions available
6169 if actions.is_empty() {
6170 cx.notify();
6171 return Task::ready(Ok(()));
6172 }
6173
6174 *editor.context_menu.borrow_mut() =
6175 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6176 buffer,
6177 actions,
6178 selected_item: Default::default(),
6179 scroll_handle: UniformListScrollHandle::default(),
6180 deployed_from,
6181 }));
6182 cx.notify();
6183 if spawn_straight_away
6184 && let Some(task) = editor.confirm_code_action(
6185 &ConfirmCodeAction { item_ix: Some(0) },
6186 window,
6187 cx,
6188 )
6189 {
6190 return task;
6191 }
6192
6193 Task::ready(Ok(()))
6194 })
6195 })
6196 .detach_and_log_err(cx);
6197 }
6198
6199 fn debug_scenarios(
6200 &mut self,
6201 resolved_tasks: &Option<ResolvedTasks>,
6202 buffer: &Entity<Buffer>,
6203 cx: &mut App,
6204 ) -> Task<Vec<task::DebugScenario>> {
6205 maybe!({
6206 let project = self.project()?;
6207 let dap_store = project.read(cx).dap_store();
6208 let mut scenarios = vec![];
6209 let resolved_tasks = resolved_tasks.as_ref()?;
6210 let buffer = buffer.read(cx);
6211 let language = buffer.language()?;
6212 let file = buffer.file();
6213 let debug_adapter = language_settings(language.name().into(), file, cx)
6214 .debuggers
6215 .first()
6216 .map(SharedString::from)
6217 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6218
6219 dap_store.update(cx, |dap_store, cx| {
6220 for (_, task) in &resolved_tasks.templates {
6221 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6222 task.original_task().clone(),
6223 debug_adapter.clone().into(),
6224 task.display_label().to_owned().into(),
6225 cx,
6226 );
6227 scenarios.push(maybe_scenario);
6228 }
6229 });
6230 Some(cx.background_spawn(async move {
6231 futures::future::join_all(scenarios)
6232 .await
6233 .into_iter()
6234 .flatten()
6235 .collect::<Vec<_>>()
6236 }))
6237 })
6238 .unwrap_or_else(|| Task::ready(vec![]))
6239 }
6240
6241 fn code_actions(
6242 &mut self,
6243 buffer_row: u32,
6244 window: &mut Window,
6245 cx: &mut Context<Self>,
6246 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6247 let mut task = self.code_actions_task.take();
6248 cx.spawn_in(window, async move |editor, cx| {
6249 while let Some(prev_task) = task {
6250 prev_task.await.log_err();
6251 task = editor
6252 .update(cx, |this, _| this.code_actions_task.take())
6253 .ok()?;
6254 }
6255
6256 editor
6257 .update(cx, |editor, cx| {
6258 editor
6259 .available_code_actions
6260 .clone()
6261 .and_then(|(location, code_actions)| {
6262 let snapshot = location.buffer.read(cx).snapshot();
6263 let point_range = location.range.to_point(&snapshot);
6264 let point_range = point_range.start.row..=point_range.end.row;
6265 if point_range.contains(&buffer_row) {
6266 Some(code_actions)
6267 } else {
6268 None
6269 }
6270 })
6271 })
6272 .ok()
6273 .flatten()
6274 })
6275 }
6276
6277 pub fn confirm_code_action(
6278 &mut self,
6279 action: &ConfirmCodeAction,
6280 window: &mut Window,
6281 cx: &mut Context<Self>,
6282 ) -> Option<Task<Result<()>>> {
6283 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6284
6285 let actions_menu =
6286 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6287 menu
6288 } else {
6289 return None;
6290 };
6291
6292 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6293 let action = actions_menu.actions.get(action_ix)?;
6294 let title = action.label();
6295 let buffer = actions_menu.buffer;
6296 let workspace = self.workspace()?;
6297
6298 match action {
6299 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6300 workspace.update(cx, |workspace, cx| {
6301 workspace.schedule_resolved_task(
6302 task_source_kind,
6303 resolved_task,
6304 false,
6305 window,
6306 cx,
6307 );
6308
6309 Some(Task::ready(Ok(())))
6310 })
6311 }
6312 CodeActionsItem::CodeAction {
6313 excerpt_id,
6314 action,
6315 provider,
6316 } => {
6317 let apply_code_action =
6318 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6319 let workspace = workspace.downgrade();
6320 Some(cx.spawn_in(window, async move |editor, cx| {
6321 let project_transaction = apply_code_action.await?;
6322 Self::open_project_transaction(
6323 &editor,
6324 workspace,
6325 project_transaction,
6326 title,
6327 cx,
6328 )
6329 .await
6330 }))
6331 }
6332 CodeActionsItem::DebugScenario(scenario) => {
6333 let context = actions_menu.actions.context;
6334
6335 workspace.update(cx, |workspace, cx| {
6336 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6337 workspace.start_debug_session(
6338 scenario,
6339 context,
6340 Some(buffer),
6341 None,
6342 window,
6343 cx,
6344 );
6345 });
6346 Some(Task::ready(Ok(())))
6347 }
6348 }
6349 }
6350
6351 pub async fn open_project_transaction(
6352 editor: &WeakEntity<Editor>,
6353 workspace: WeakEntity<Workspace>,
6354 transaction: ProjectTransaction,
6355 title: String,
6356 cx: &mut AsyncWindowContext,
6357 ) -> Result<()> {
6358 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6359 cx.update(|_, cx| {
6360 entries.sort_unstable_by_key(|(buffer, _)| {
6361 buffer.read(cx).file().map(|f| f.path().clone())
6362 });
6363 })?;
6364 if entries.is_empty() {
6365 return Ok(());
6366 }
6367
6368 // If the project transaction's edits are all contained within this editor, then
6369 // avoid opening a new editor to display them.
6370
6371 if let [(buffer, transaction)] = &*entries {
6372 let excerpt = editor.update(cx, |editor, cx| {
6373 editor
6374 .buffer()
6375 .read(cx)
6376 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6377 })?;
6378 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6379 && excerpted_buffer == *buffer
6380 {
6381 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6382 let excerpt_range = excerpt_range.to_offset(buffer);
6383 buffer
6384 .edited_ranges_for_transaction::<usize>(transaction)
6385 .all(|range| {
6386 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6387 })
6388 })?;
6389
6390 if all_edits_within_excerpt {
6391 return Ok(());
6392 }
6393 }
6394 }
6395
6396 let mut ranges_to_highlight = Vec::new();
6397 let excerpt_buffer = cx.new(|cx| {
6398 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6399 for (buffer_handle, transaction) in &entries {
6400 let edited_ranges = buffer_handle
6401 .read(cx)
6402 .edited_ranges_for_transaction::<Point>(transaction)
6403 .collect::<Vec<_>>();
6404 let (ranges, _) = multibuffer.set_excerpts_for_path(
6405 PathKey::for_buffer(buffer_handle, cx),
6406 buffer_handle.clone(),
6407 edited_ranges,
6408 multibuffer_context_lines(cx),
6409 cx,
6410 );
6411
6412 ranges_to_highlight.extend(ranges);
6413 }
6414 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6415 multibuffer
6416 })?;
6417
6418 workspace.update_in(cx, |workspace, window, cx| {
6419 let project = workspace.project().clone();
6420 let editor =
6421 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6422 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6423 editor.update(cx, |editor, cx| {
6424 editor.highlight_background::<Self>(
6425 &ranges_to_highlight,
6426 |theme| theme.colors().editor_highlighted_line_background,
6427 cx,
6428 );
6429 });
6430 })?;
6431
6432 Ok(())
6433 }
6434
6435 pub fn clear_code_action_providers(&mut self) {
6436 self.code_action_providers.clear();
6437 self.available_code_actions.take();
6438 }
6439
6440 pub fn add_code_action_provider(
6441 &mut self,
6442 provider: Rc<dyn CodeActionProvider>,
6443 window: &mut Window,
6444 cx: &mut Context<Self>,
6445 ) {
6446 if self
6447 .code_action_providers
6448 .iter()
6449 .any(|existing_provider| existing_provider.id() == provider.id())
6450 {
6451 return;
6452 }
6453
6454 self.code_action_providers.push(provider);
6455 self.refresh_code_actions(window, cx);
6456 }
6457
6458 pub fn remove_code_action_provider(
6459 &mut self,
6460 id: Arc<str>,
6461 window: &mut Window,
6462 cx: &mut Context<Self>,
6463 ) {
6464 self.code_action_providers
6465 .retain(|provider| provider.id() != id);
6466 self.refresh_code_actions(window, cx);
6467 }
6468
6469 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6470 !self.code_action_providers.is_empty()
6471 && EditorSettings::get_global(cx).toolbar.code_actions
6472 }
6473
6474 pub fn has_available_code_actions(&self) -> bool {
6475 self.available_code_actions
6476 .as_ref()
6477 .is_some_and(|(_, actions)| !actions.is_empty())
6478 }
6479
6480 fn render_inline_code_actions(
6481 &self,
6482 icon_size: ui::IconSize,
6483 display_row: DisplayRow,
6484 is_active: bool,
6485 cx: &mut Context<Self>,
6486 ) -> AnyElement {
6487 let show_tooltip = !self.context_menu_visible();
6488 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6489 .icon_size(icon_size)
6490 .shape(ui::IconButtonShape::Square)
6491 .icon_color(ui::Color::Hidden)
6492 .toggle_state(is_active)
6493 .when(show_tooltip, |this| {
6494 this.tooltip({
6495 let focus_handle = self.focus_handle.clone();
6496 move |_window, cx| {
6497 Tooltip::for_action_in(
6498 "Toggle Code Actions",
6499 &ToggleCodeActions {
6500 deployed_from: None,
6501 quick_launch: false,
6502 },
6503 &focus_handle,
6504 cx,
6505 )
6506 }
6507 })
6508 })
6509 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6510 window.focus(&editor.focus_handle(cx));
6511 editor.toggle_code_actions(
6512 &crate::actions::ToggleCodeActions {
6513 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6514 display_row,
6515 )),
6516 quick_launch: false,
6517 },
6518 window,
6519 cx,
6520 );
6521 }))
6522 .into_any_element()
6523 }
6524
6525 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6526 &self.context_menu
6527 }
6528
6529 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6530 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6531 cx.background_executor()
6532 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6533 .await;
6534
6535 let (start_buffer, start, _, end, newest_selection) = this
6536 .update(cx, |this, cx| {
6537 let newest_selection = this.selections.newest_anchor().clone();
6538 if newest_selection.head().diff_base_anchor.is_some() {
6539 return None;
6540 }
6541 let display_snapshot = this.display_snapshot(cx);
6542 let newest_selection_adjusted =
6543 this.selections.newest_adjusted(&display_snapshot);
6544 let buffer = this.buffer.read(cx);
6545
6546 let (start_buffer, start) =
6547 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6548 let (end_buffer, end) =
6549 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6550
6551 Some((start_buffer, start, end_buffer, end, newest_selection))
6552 })?
6553 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6554 .context(
6555 "Expected selection to lie in a single buffer when refreshing code actions",
6556 )?;
6557 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6558 let providers = this.code_action_providers.clone();
6559 let tasks = this
6560 .code_action_providers
6561 .iter()
6562 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6563 .collect::<Vec<_>>();
6564 (providers, tasks)
6565 })?;
6566
6567 let mut actions = Vec::new();
6568 for (provider, provider_actions) in
6569 providers.into_iter().zip(future::join_all(tasks).await)
6570 {
6571 if let Some(provider_actions) = provider_actions.log_err() {
6572 actions.extend(provider_actions.into_iter().map(|action| {
6573 AvailableCodeAction {
6574 excerpt_id: newest_selection.start.excerpt_id,
6575 action,
6576 provider: provider.clone(),
6577 }
6578 }));
6579 }
6580 }
6581
6582 this.update(cx, |this, cx| {
6583 this.available_code_actions = if actions.is_empty() {
6584 None
6585 } else {
6586 Some((
6587 Location {
6588 buffer: start_buffer,
6589 range: start..end,
6590 },
6591 actions.into(),
6592 ))
6593 };
6594 cx.notify();
6595 })
6596 }));
6597 }
6598
6599 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6600 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6601 self.show_git_blame_inline = false;
6602
6603 self.show_git_blame_inline_delay_task =
6604 Some(cx.spawn_in(window, async move |this, cx| {
6605 cx.background_executor().timer(delay).await;
6606
6607 this.update(cx, |this, cx| {
6608 this.show_git_blame_inline = true;
6609 cx.notify();
6610 })
6611 .log_err();
6612 }));
6613 }
6614 }
6615
6616 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6617 let snapshot = self.snapshot(window, cx);
6618 let cursor = self
6619 .selections
6620 .newest::<Point>(&snapshot.display_snapshot)
6621 .head();
6622 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6623 else {
6624 return;
6625 };
6626
6627 let Some(blame) = self.blame.as_ref() else {
6628 return;
6629 };
6630
6631 let row_info = RowInfo {
6632 buffer_id: Some(buffer.remote_id()),
6633 buffer_row: Some(point.row),
6634 ..Default::default()
6635 };
6636 let Some((buffer, blame_entry)) = blame
6637 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6638 .flatten()
6639 else {
6640 return;
6641 };
6642
6643 let anchor = self.selections.newest_anchor().head();
6644 let position = self.to_pixel_point(anchor, &snapshot, window);
6645 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6646 self.show_blame_popover(
6647 buffer,
6648 &blame_entry,
6649 position + last_bounds.origin,
6650 true,
6651 cx,
6652 );
6653 };
6654 }
6655
6656 fn show_blame_popover(
6657 &mut self,
6658 buffer: BufferId,
6659 blame_entry: &BlameEntry,
6660 position: gpui::Point<Pixels>,
6661 ignore_timeout: bool,
6662 cx: &mut Context<Self>,
6663 ) {
6664 if let Some(state) = &mut self.inline_blame_popover {
6665 state.hide_task.take();
6666 } else {
6667 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6668 let blame_entry = blame_entry.clone();
6669 let show_task = cx.spawn(async move |editor, cx| {
6670 if !ignore_timeout {
6671 cx.background_executor()
6672 .timer(std::time::Duration::from_millis(blame_popover_delay))
6673 .await;
6674 }
6675 editor
6676 .update(cx, |editor, cx| {
6677 editor.inline_blame_popover_show_task.take();
6678 let Some(blame) = editor.blame.as_ref() else {
6679 return;
6680 };
6681 let blame = blame.read(cx);
6682 let details = blame.details_for_entry(buffer, &blame_entry);
6683 let markdown = cx.new(|cx| {
6684 Markdown::new(
6685 details
6686 .as_ref()
6687 .map(|message| message.message.clone())
6688 .unwrap_or_default(),
6689 None,
6690 None,
6691 cx,
6692 )
6693 });
6694 editor.inline_blame_popover = Some(InlineBlamePopover {
6695 position,
6696 hide_task: None,
6697 popover_bounds: None,
6698 popover_state: InlineBlamePopoverState {
6699 scroll_handle: ScrollHandle::new(),
6700 commit_message: details,
6701 markdown,
6702 },
6703 keyboard_grace: ignore_timeout,
6704 });
6705 cx.notify();
6706 })
6707 .ok();
6708 });
6709 self.inline_blame_popover_show_task = Some(show_task);
6710 }
6711 }
6712
6713 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6714 self.inline_blame_popover_show_task.take();
6715 if let Some(state) = &mut self.inline_blame_popover {
6716 let hide_task = cx.spawn(async move |editor, cx| {
6717 if !ignore_timeout {
6718 cx.background_executor()
6719 .timer(std::time::Duration::from_millis(100))
6720 .await;
6721 }
6722 editor
6723 .update(cx, |editor, cx| {
6724 editor.inline_blame_popover.take();
6725 cx.notify();
6726 })
6727 .ok();
6728 });
6729 state.hide_task = Some(hide_task);
6730 true
6731 } else {
6732 false
6733 }
6734 }
6735
6736 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6737 if self.pending_rename.is_some() {
6738 return None;
6739 }
6740
6741 let provider = self.semantics_provider.clone()?;
6742 let buffer = self.buffer.read(cx);
6743 let newest_selection = self.selections.newest_anchor().clone();
6744 let cursor_position = newest_selection.head();
6745 let (cursor_buffer, cursor_buffer_position) =
6746 buffer.text_anchor_for_position(cursor_position, cx)?;
6747 let (tail_buffer, tail_buffer_position) =
6748 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6749 if cursor_buffer != tail_buffer {
6750 return None;
6751 }
6752
6753 let snapshot = cursor_buffer.read(cx).snapshot();
6754 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6755 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6756 if start_word_range != end_word_range {
6757 self.document_highlights_task.take();
6758 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6759 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6760 return None;
6761 }
6762
6763 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6764 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6765 cx.background_executor()
6766 .timer(Duration::from_millis(debounce))
6767 .await;
6768
6769 let highlights = if let Some(highlights) = cx
6770 .update(|cx| {
6771 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6772 })
6773 .ok()
6774 .flatten()
6775 {
6776 highlights.await.log_err()
6777 } else {
6778 None
6779 };
6780
6781 if let Some(highlights) = highlights {
6782 this.update(cx, |this, cx| {
6783 if this.pending_rename.is_some() {
6784 return;
6785 }
6786
6787 let buffer = this.buffer.read(cx);
6788 if buffer
6789 .text_anchor_for_position(cursor_position, cx)
6790 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6791 {
6792 return;
6793 }
6794
6795 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6796 let mut write_ranges = Vec::new();
6797 let mut read_ranges = Vec::new();
6798 for highlight in highlights {
6799 let buffer_id = cursor_buffer.read(cx).remote_id();
6800 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6801 {
6802 let start = highlight
6803 .range
6804 .start
6805 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6806 let end = highlight
6807 .range
6808 .end
6809 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6810 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6811 continue;
6812 }
6813
6814 let range =
6815 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6816 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6817 write_ranges.push(range);
6818 } else {
6819 read_ranges.push(range);
6820 }
6821 }
6822 }
6823
6824 this.highlight_background::<DocumentHighlightRead>(
6825 &read_ranges,
6826 |theme| theme.colors().editor_document_highlight_read_background,
6827 cx,
6828 );
6829 this.highlight_background::<DocumentHighlightWrite>(
6830 &write_ranges,
6831 |theme| theme.colors().editor_document_highlight_write_background,
6832 cx,
6833 );
6834 cx.notify();
6835 })
6836 .log_err();
6837 }
6838 }));
6839 None
6840 }
6841
6842 fn prepare_highlight_query_from_selection(
6843 &mut self,
6844 window: &Window,
6845 cx: &mut Context<Editor>,
6846 ) -> Option<(String, Range<Anchor>)> {
6847 if matches!(self.mode, EditorMode::SingleLine) {
6848 return None;
6849 }
6850 if !EditorSettings::get_global(cx).selection_highlight {
6851 return None;
6852 }
6853 if self.selections.count() != 1 || self.selections.line_mode() {
6854 return None;
6855 }
6856 let snapshot = self.snapshot(window, cx);
6857 let selection = self.selections.newest::<Point>(&snapshot);
6858 // If the selection spans multiple rows OR it is empty
6859 if selection.start.row != selection.end.row
6860 || selection.start.column == selection.end.column
6861 {
6862 return None;
6863 }
6864 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6865 let query = snapshot
6866 .buffer_snapshot()
6867 .text_for_range(selection_anchor_range.clone())
6868 .collect::<String>();
6869 if query.trim().is_empty() {
6870 return None;
6871 }
6872 Some((query, selection_anchor_range))
6873 }
6874
6875 fn update_selection_occurrence_highlights(
6876 &mut self,
6877 query_text: String,
6878 query_range: Range<Anchor>,
6879 multi_buffer_range_to_query: Range<Point>,
6880 use_debounce: bool,
6881 window: &mut Window,
6882 cx: &mut Context<Editor>,
6883 ) -> Task<()> {
6884 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6885 cx.spawn_in(window, async move |editor, cx| {
6886 if use_debounce {
6887 cx.background_executor()
6888 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6889 .await;
6890 }
6891 let match_task = cx.background_spawn(async move {
6892 let buffer_ranges = multi_buffer_snapshot
6893 .range_to_buffer_ranges(multi_buffer_range_to_query)
6894 .into_iter()
6895 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6896 let mut match_ranges = Vec::new();
6897 let Ok(regex) = project::search::SearchQuery::text(
6898 query_text.clone(),
6899 false,
6900 false,
6901 false,
6902 Default::default(),
6903 Default::default(),
6904 false,
6905 None,
6906 ) else {
6907 return Vec::default();
6908 };
6909 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6910 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6911 match_ranges.extend(
6912 regex
6913 .search(buffer_snapshot, Some(search_range.clone()))
6914 .await
6915 .into_iter()
6916 .filter_map(|match_range| {
6917 let match_start = buffer_snapshot
6918 .anchor_after(search_range.start + match_range.start);
6919 let match_end = buffer_snapshot
6920 .anchor_before(search_range.start + match_range.end);
6921 let match_anchor_range = Anchor::range_in_buffer(
6922 excerpt_id,
6923 buffer_snapshot.remote_id(),
6924 match_start..match_end,
6925 );
6926 (match_anchor_range != query_range).then_some(match_anchor_range)
6927 }),
6928 );
6929 }
6930 match_ranges
6931 });
6932 let match_ranges = match_task.await;
6933 editor
6934 .update_in(cx, |editor, _, cx| {
6935 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6936 if !match_ranges.is_empty() {
6937 editor.highlight_background::<SelectedTextHighlight>(
6938 &match_ranges,
6939 |theme| theme.colors().editor_document_highlight_bracket_background,
6940 cx,
6941 )
6942 }
6943 })
6944 .log_err();
6945 })
6946 }
6947
6948 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6949 struct NewlineFold;
6950 let type_id = std::any::TypeId::of::<NewlineFold>();
6951 if !self.mode.is_single_line() {
6952 return;
6953 }
6954 let snapshot = self.snapshot(window, cx);
6955 if snapshot.buffer_snapshot().max_point().row == 0 {
6956 return;
6957 }
6958 let task = cx.background_spawn(async move {
6959 let new_newlines = snapshot
6960 .buffer_chars_at(0)
6961 .filter_map(|(c, i)| {
6962 if c == '\n' {
6963 Some(
6964 snapshot.buffer_snapshot().anchor_after(i)
6965 ..snapshot.buffer_snapshot().anchor_before(i + 1),
6966 )
6967 } else {
6968 None
6969 }
6970 })
6971 .collect::<Vec<_>>();
6972 let existing_newlines = snapshot
6973 .folds_in_range(0..snapshot.buffer_snapshot().len())
6974 .filter_map(|fold| {
6975 if fold.placeholder.type_tag == Some(type_id) {
6976 Some(fold.range.start..fold.range.end)
6977 } else {
6978 None
6979 }
6980 })
6981 .collect::<Vec<_>>();
6982
6983 (new_newlines, existing_newlines)
6984 });
6985 self.folding_newlines = cx.spawn(async move |this, cx| {
6986 let (new_newlines, existing_newlines) = task.await;
6987 if new_newlines == existing_newlines {
6988 return;
6989 }
6990 let placeholder = FoldPlaceholder {
6991 render: Arc::new(move |_, _, cx| {
6992 div()
6993 .bg(cx.theme().status().hint_background)
6994 .border_b_1()
6995 .size_full()
6996 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6997 .border_color(cx.theme().status().hint)
6998 .child("\\n")
6999 .into_any()
7000 }),
7001 constrain_width: false,
7002 merge_adjacent: false,
7003 type_tag: Some(type_id),
7004 };
7005 let creases = new_newlines
7006 .into_iter()
7007 .map(|range| Crease::simple(range, placeholder.clone()))
7008 .collect();
7009 this.update(cx, |this, cx| {
7010 this.display_map.update(cx, |display_map, cx| {
7011 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7012 display_map.fold(creases, cx);
7013 });
7014 })
7015 .ok();
7016 });
7017 }
7018
7019 fn refresh_selected_text_highlights(
7020 &mut self,
7021 on_buffer_edit: bool,
7022 window: &mut Window,
7023 cx: &mut Context<Editor>,
7024 ) {
7025 let Some((query_text, query_range)) =
7026 self.prepare_highlight_query_from_selection(window, cx)
7027 else {
7028 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7029 self.quick_selection_highlight_task.take();
7030 self.debounced_selection_highlight_task.take();
7031 return;
7032 };
7033 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7034 if on_buffer_edit
7035 || self
7036 .quick_selection_highlight_task
7037 .as_ref()
7038 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7039 {
7040 let multi_buffer_visible_start = self
7041 .scroll_manager
7042 .anchor()
7043 .anchor
7044 .to_point(&multi_buffer_snapshot);
7045 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7046 multi_buffer_visible_start
7047 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7048 Bias::Left,
7049 );
7050 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7051 self.quick_selection_highlight_task = Some((
7052 query_range.clone(),
7053 self.update_selection_occurrence_highlights(
7054 query_text.clone(),
7055 query_range.clone(),
7056 multi_buffer_visible_range,
7057 false,
7058 window,
7059 cx,
7060 ),
7061 ));
7062 }
7063 if on_buffer_edit
7064 || self
7065 .debounced_selection_highlight_task
7066 .as_ref()
7067 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7068 {
7069 let multi_buffer_start = multi_buffer_snapshot
7070 .anchor_before(0)
7071 .to_point(&multi_buffer_snapshot);
7072 let multi_buffer_end = multi_buffer_snapshot
7073 .anchor_after(multi_buffer_snapshot.len())
7074 .to_point(&multi_buffer_snapshot);
7075 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7076 self.debounced_selection_highlight_task = Some((
7077 query_range.clone(),
7078 self.update_selection_occurrence_highlights(
7079 query_text,
7080 query_range,
7081 multi_buffer_full_range,
7082 true,
7083 window,
7084 cx,
7085 ),
7086 ));
7087 }
7088 }
7089
7090 pub fn refresh_edit_prediction(
7091 &mut self,
7092 debounce: bool,
7093 user_requested: bool,
7094 window: &mut Window,
7095 cx: &mut Context<Self>,
7096 ) -> Option<()> {
7097 if DisableAiSettings::get_global(cx).disable_ai {
7098 return None;
7099 }
7100
7101 let provider = self.edit_prediction_provider()?;
7102 let cursor = self.selections.newest_anchor().head();
7103 let (buffer, cursor_buffer_position) =
7104 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7105
7106 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7107 self.discard_edit_prediction(false, cx);
7108 return None;
7109 }
7110
7111 self.update_visible_edit_prediction(window, cx);
7112
7113 if !user_requested
7114 && (!self.should_show_edit_predictions()
7115 || !self.is_focused(window)
7116 || buffer.read(cx).is_empty())
7117 {
7118 self.discard_edit_prediction(false, cx);
7119 return None;
7120 }
7121
7122 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7123 Some(())
7124 }
7125
7126 fn show_edit_predictions_in_menu(&self) -> bool {
7127 match self.edit_prediction_settings {
7128 EditPredictionSettings::Disabled => false,
7129 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7130 }
7131 }
7132
7133 pub fn edit_predictions_enabled(&self) -> bool {
7134 match self.edit_prediction_settings {
7135 EditPredictionSettings::Disabled => false,
7136 EditPredictionSettings::Enabled { .. } => true,
7137 }
7138 }
7139
7140 fn edit_prediction_requires_modifier(&self) -> bool {
7141 match self.edit_prediction_settings {
7142 EditPredictionSettings::Disabled => false,
7143 EditPredictionSettings::Enabled {
7144 preview_requires_modifier,
7145 ..
7146 } => preview_requires_modifier,
7147 }
7148 }
7149
7150 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7151 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7152 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7153 self.discard_edit_prediction(false, cx);
7154 } else {
7155 let selection = self.selections.newest_anchor();
7156 let cursor = selection.head();
7157
7158 if let Some((buffer, cursor_buffer_position)) =
7159 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7160 {
7161 self.edit_prediction_settings =
7162 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7163 }
7164 }
7165 }
7166
7167 fn edit_prediction_settings_at_position(
7168 &self,
7169 buffer: &Entity<Buffer>,
7170 buffer_position: language::Anchor,
7171 cx: &App,
7172 ) -> EditPredictionSettings {
7173 if !self.mode.is_full()
7174 || !self.show_edit_predictions_override.unwrap_or(true)
7175 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7176 {
7177 return EditPredictionSettings::Disabled;
7178 }
7179
7180 let buffer = buffer.read(cx);
7181
7182 let file = buffer.file();
7183
7184 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7185 return EditPredictionSettings::Disabled;
7186 };
7187
7188 let by_provider = matches!(
7189 self.menu_edit_predictions_policy,
7190 MenuEditPredictionsPolicy::ByProvider
7191 );
7192
7193 let show_in_menu = by_provider
7194 && self
7195 .edit_prediction_provider
7196 .as_ref()
7197 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7198
7199 let preview_requires_modifier =
7200 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7201
7202 EditPredictionSettings::Enabled {
7203 show_in_menu,
7204 preview_requires_modifier,
7205 }
7206 }
7207
7208 fn should_show_edit_predictions(&self) -> bool {
7209 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7210 }
7211
7212 pub fn edit_prediction_preview_is_active(&self) -> bool {
7213 matches!(
7214 self.edit_prediction_preview,
7215 EditPredictionPreview::Active { .. }
7216 )
7217 }
7218
7219 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7220 let cursor = self.selections.newest_anchor().head();
7221 if let Some((buffer, cursor_position)) =
7222 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7223 {
7224 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7225 } else {
7226 false
7227 }
7228 }
7229
7230 pub fn supports_minimap(&self, cx: &App) -> bool {
7231 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7232 }
7233
7234 fn edit_predictions_enabled_in_buffer(
7235 &self,
7236 buffer: &Entity<Buffer>,
7237 buffer_position: language::Anchor,
7238 cx: &App,
7239 ) -> bool {
7240 maybe!({
7241 if self.read_only(cx) {
7242 return Some(false);
7243 }
7244 let provider = self.edit_prediction_provider()?;
7245 if !provider.is_enabled(buffer, buffer_position, cx) {
7246 return Some(false);
7247 }
7248 let buffer = buffer.read(cx);
7249 let Some(file) = buffer.file() else {
7250 return Some(true);
7251 };
7252 let settings = all_language_settings(Some(file), cx);
7253 Some(settings.edit_predictions_enabled_for_file(file, cx))
7254 })
7255 .unwrap_or(false)
7256 }
7257
7258 fn cycle_edit_prediction(
7259 &mut self,
7260 direction: Direction,
7261 window: &mut Window,
7262 cx: &mut Context<Self>,
7263 ) -> Option<()> {
7264 let provider = self.edit_prediction_provider()?;
7265 let cursor = self.selections.newest_anchor().head();
7266 let (buffer, cursor_buffer_position) =
7267 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7268 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7269 return None;
7270 }
7271
7272 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7273 self.update_visible_edit_prediction(window, cx);
7274
7275 Some(())
7276 }
7277
7278 pub fn show_edit_prediction(
7279 &mut self,
7280 _: &ShowEditPrediction,
7281 window: &mut Window,
7282 cx: &mut Context<Self>,
7283 ) {
7284 if !self.has_active_edit_prediction() {
7285 self.refresh_edit_prediction(false, true, window, cx);
7286 return;
7287 }
7288
7289 self.update_visible_edit_prediction(window, cx);
7290 }
7291
7292 pub fn display_cursor_names(
7293 &mut self,
7294 _: &DisplayCursorNames,
7295 window: &mut Window,
7296 cx: &mut Context<Self>,
7297 ) {
7298 self.show_cursor_names(window, cx);
7299 }
7300
7301 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7302 self.show_cursor_names = true;
7303 cx.notify();
7304 cx.spawn_in(window, async move |this, cx| {
7305 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7306 this.update(cx, |this, cx| {
7307 this.show_cursor_names = false;
7308 cx.notify()
7309 })
7310 .ok()
7311 })
7312 .detach();
7313 }
7314
7315 pub fn next_edit_prediction(
7316 &mut self,
7317 _: &NextEditPrediction,
7318 window: &mut Window,
7319 cx: &mut Context<Self>,
7320 ) {
7321 if self.has_active_edit_prediction() {
7322 self.cycle_edit_prediction(Direction::Next, window, cx);
7323 } else {
7324 let is_copilot_disabled = self
7325 .refresh_edit_prediction(false, true, window, cx)
7326 .is_none();
7327 if is_copilot_disabled {
7328 cx.propagate();
7329 }
7330 }
7331 }
7332
7333 pub fn previous_edit_prediction(
7334 &mut self,
7335 _: &PreviousEditPrediction,
7336 window: &mut Window,
7337 cx: &mut Context<Self>,
7338 ) {
7339 if self.has_active_edit_prediction() {
7340 self.cycle_edit_prediction(Direction::Prev, window, cx);
7341 } else {
7342 let is_copilot_disabled = self
7343 .refresh_edit_prediction(false, true, window, cx)
7344 .is_none();
7345 if is_copilot_disabled {
7346 cx.propagate();
7347 }
7348 }
7349 }
7350
7351 pub fn accept_edit_prediction(
7352 &mut self,
7353 _: &AcceptEditPrediction,
7354 window: &mut Window,
7355 cx: &mut Context<Self>,
7356 ) {
7357 if self.show_edit_predictions_in_menu() {
7358 self.hide_context_menu(window, cx);
7359 }
7360
7361 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7362 return;
7363 };
7364
7365 match &active_edit_prediction.completion {
7366 EditPrediction::MoveWithin { target, .. } => {
7367 let target = *target;
7368
7369 if let Some(position_map) = &self.last_position_map {
7370 if position_map
7371 .visible_row_range
7372 .contains(&target.to_display_point(&position_map.snapshot).row())
7373 || !self.edit_prediction_requires_modifier()
7374 {
7375 self.unfold_ranges(&[target..target], true, false, cx);
7376 // Note that this is also done in vim's handler of the Tab action.
7377 self.change_selections(
7378 SelectionEffects::scroll(Autoscroll::newest()),
7379 window,
7380 cx,
7381 |selections| {
7382 selections.select_anchor_ranges([target..target]);
7383 },
7384 );
7385 self.clear_row_highlights::<EditPredictionPreview>();
7386
7387 self.edit_prediction_preview
7388 .set_previous_scroll_position(None);
7389 } else {
7390 self.edit_prediction_preview
7391 .set_previous_scroll_position(Some(
7392 position_map.snapshot.scroll_anchor,
7393 ));
7394
7395 self.highlight_rows::<EditPredictionPreview>(
7396 target..target,
7397 cx.theme().colors().editor_highlighted_line_background,
7398 RowHighlightOptions {
7399 autoscroll: true,
7400 ..Default::default()
7401 },
7402 cx,
7403 );
7404 self.request_autoscroll(Autoscroll::fit(), cx);
7405 }
7406 }
7407 }
7408 EditPrediction::MoveOutside { snapshot, target } => {
7409 if let Some(workspace) = self.workspace() {
7410 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7411 .detach_and_log_err(cx);
7412 }
7413 }
7414 EditPrediction::Edit { edits, .. } => {
7415 self.report_edit_prediction_event(
7416 active_edit_prediction.completion_id.clone(),
7417 true,
7418 cx,
7419 );
7420
7421 if let Some(provider) = self.edit_prediction_provider() {
7422 provider.accept(cx);
7423 }
7424
7425 // Store the transaction ID and selections before applying the edit
7426 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7427
7428 let snapshot = self.buffer.read(cx).snapshot(cx);
7429 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7430
7431 self.buffer.update(cx, |buffer, cx| {
7432 buffer.edit(edits.iter().cloned(), None, cx)
7433 });
7434
7435 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7436 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7437 });
7438
7439 let selections = self.selections.disjoint_anchors_arc();
7440 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7441 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7442 if has_new_transaction {
7443 self.selection_history
7444 .insert_transaction(transaction_id_now, selections);
7445 }
7446 }
7447
7448 self.update_visible_edit_prediction(window, cx);
7449 if self.active_edit_prediction.is_none() {
7450 self.refresh_edit_prediction(true, true, window, cx);
7451 }
7452
7453 cx.notify();
7454 }
7455 }
7456
7457 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7458 }
7459
7460 pub fn accept_partial_edit_prediction(
7461 &mut self,
7462 _: &AcceptPartialEditPrediction,
7463 window: &mut Window,
7464 cx: &mut Context<Self>,
7465 ) {
7466 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7467 return;
7468 };
7469 if self.selections.count() != 1 {
7470 return;
7471 }
7472
7473 match &active_edit_prediction.completion {
7474 EditPrediction::MoveWithin { target, .. } => {
7475 let target = *target;
7476 self.change_selections(
7477 SelectionEffects::scroll(Autoscroll::newest()),
7478 window,
7479 cx,
7480 |selections| {
7481 selections.select_anchor_ranges([target..target]);
7482 },
7483 );
7484 }
7485 EditPrediction::MoveOutside { snapshot, target } => {
7486 if let Some(workspace) = self.workspace() {
7487 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7488 .detach_and_log_err(cx);
7489 }
7490 }
7491 EditPrediction::Edit { edits, .. } => {
7492 self.report_edit_prediction_event(
7493 active_edit_prediction.completion_id.clone(),
7494 true,
7495 cx,
7496 );
7497
7498 // Find an insertion that starts at the cursor position.
7499 let snapshot = self.buffer.read(cx).snapshot(cx);
7500 let cursor_offset = self
7501 .selections
7502 .newest::<usize>(&self.display_snapshot(cx))
7503 .head();
7504 let insertion = edits.iter().find_map(|(range, text)| {
7505 let range = range.to_offset(&snapshot);
7506 if range.is_empty() && range.start == cursor_offset {
7507 Some(text)
7508 } else {
7509 None
7510 }
7511 });
7512
7513 if let Some(text) = insertion {
7514 let mut partial_completion = text
7515 .chars()
7516 .by_ref()
7517 .take_while(|c| c.is_alphabetic())
7518 .collect::<String>();
7519 if partial_completion.is_empty() {
7520 partial_completion = text
7521 .chars()
7522 .by_ref()
7523 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7524 .collect::<String>();
7525 }
7526
7527 cx.emit(EditorEvent::InputHandled {
7528 utf16_range_to_replace: None,
7529 text: partial_completion.clone().into(),
7530 });
7531
7532 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7533
7534 self.refresh_edit_prediction(true, true, window, cx);
7535 cx.notify();
7536 } else {
7537 self.accept_edit_prediction(&Default::default(), window, cx);
7538 }
7539 }
7540 }
7541 }
7542
7543 fn discard_edit_prediction(
7544 &mut self,
7545 should_report_edit_prediction_event: bool,
7546 cx: &mut Context<Self>,
7547 ) -> bool {
7548 if should_report_edit_prediction_event {
7549 let completion_id = self
7550 .active_edit_prediction
7551 .as_ref()
7552 .and_then(|active_completion| active_completion.completion_id.clone());
7553
7554 self.report_edit_prediction_event(completion_id, false, cx);
7555 }
7556
7557 if let Some(provider) = self.edit_prediction_provider() {
7558 provider.discard(cx);
7559 }
7560
7561 self.take_active_edit_prediction(cx)
7562 }
7563
7564 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7565 let Some(provider) = self.edit_prediction_provider() else {
7566 return;
7567 };
7568
7569 let Some((_, buffer, _)) = self
7570 .buffer
7571 .read(cx)
7572 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7573 else {
7574 return;
7575 };
7576
7577 let extension = buffer
7578 .read(cx)
7579 .file()
7580 .and_then(|file| Some(file.path().extension()?.to_string()));
7581
7582 let event_type = match accepted {
7583 true => "Edit Prediction Accepted",
7584 false => "Edit Prediction Discarded",
7585 };
7586 telemetry::event!(
7587 event_type,
7588 provider = provider.name(),
7589 prediction_id = id,
7590 suggestion_accepted = accepted,
7591 file_extension = extension,
7592 );
7593 }
7594
7595 fn open_editor_at_anchor(
7596 snapshot: &language::BufferSnapshot,
7597 target: language::Anchor,
7598 workspace: &Entity<Workspace>,
7599 window: &mut Window,
7600 cx: &mut App,
7601 ) -> Task<Result<()>> {
7602 workspace.update(cx, |workspace, cx| {
7603 let path = snapshot.file().map(|file| file.full_path(cx));
7604 let Some(path) =
7605 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7606 else {
7607 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7608 };
7609 let target = text::ToPoint::to_point(&target, snapshot);
7610 let item = workspace.open_path(path, None, true, window, cx);
7611 window.spawn(cx, async move |cx| {
7612 let Some(editor) = item.await?.downcast::<Editor>() else {
7613 return Ok(());
7614 };
7615 editor
7616 .update_in(cx, |editor, window, cx| {
7617 editor.go_to_singleton_buffer_point(target, window, cx);
7618 })
7619 .ok();
7620 anyhow::Ok(())
7621 })
7622 })
7623 }
7624
7625 pub fn has_active_edit_prediction(&self) -> bool {
7626 self.active_edit_prediction.is_some()
7627 }
7628
7629 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7630 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7631 return false;
7632 };
7633
7634 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7635 self.clear_highlights::<EditPredictionHighlight>(cx);
7636 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7637 true
7638 }
7639
7640 /// Returns true when we're displaying the edit prediction popover below the cursor
7641 /// like we are not previewing and the LSP autocomplete menu is visible
7642 /// or we are in `when_holding_modifier` mode.
7643 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7644 if self.edit_prediction_preview_is_active()
7645 || !self.show_edit_predictions_in_menu()
7646 || !self.edit_predictions_enabled()
7647 {
7648 return false;
7649 }
7650
7651 if self.has_visible_completions_menu() {
7652 return true;
7653 }
7654
7655 has_completion && self.edit_prediction_requires_modifier()
7656 }
7657
7658 fn handle_modifiers_changed(
7659 &mut self,
7660 modifiers: Modifiers,
7661 position_map: &PositionMap,
7662 window: &mut Window,
7663 cx: &mut Context<Self>,
7664 ) {
7665 // Ensure that the edit prediction preview is updated, even when not
7666 // enabled, if there's an active edit prediction preview.
7667 if self.show_edit_predictions_in_menu()
7668 || matches!(
7669 self.edit_prediction_preview,
7670 EditPredictionPreview::Active { .. }
7671 )
7672 {
7673 self.update_edit_prediction_preview(&modifiers, window, cx);
7674 }
7675
7676 self.update_selection_mode(&modifiers, position_map, window, cx);
7677
7678 let mouse_position = window.mouse_position();
7679 if !position_map.text_hitbox.is_hovered(window) {
7680 return;
7681 }
7682
7683 self.update_hovered_link(
7684 position_map.point_for_position(mouse_position),
7685 &position_map.snapshot,
7686 modifiers,
7687 window,
7688 cx,
7689 )
7690 }
7691
7692 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7693 match EditorSettings::get_global(cx).multi_cursor_modifier {
7694 MultiCursorModifier::Alt => modifiers.secondary(),
7695 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7696 }
7697 }
7698
7699 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7700 match EditorSettings::get_global(cx).multi_cursor_modifier {
7701 MultiCursorModifier::Alt => modifiers.alt,
7702 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7703 }
7704 }
7705
7706 fn columnar_selection_mode(
7707 modifiers: &Modifiers,
7708 cx: &mut Context<Self>,
7709 ) -> Option<ColumnarMode> {
7710 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7711 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7712 Some(ColumnarMode::FromMouse)
7713 } else if Self::is_alt_pressed(modifiers, cx) {
7714 Some(ColumnarMode::FromSelection)
7715 } else {
7716 None
7717 }
7718 } else {
7719 None
7720 }
7721 }
7722
7723 fn update_selection_mode(
7724 &mut self,
7725 modifiers: &Modifiers,
7726 position_map: &PositionMap,
7727 window: &mut Window,
7728 cx: &mut Context<Self>,
7729 ) {
7730 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7731 return;
7732 };
7733 if self.selections.pending_anchor().is_none() {
7734 return;
7735 }
7736
7737 let mouse_position = window.mouse_position();
7738 let point_for_position = position_map.point_for_position(mouse_position);
7739 let position = point_for_position.previous_valid;
7740
7741 self.select(
7742 SelectPhase::BeginColumnar {
7743 position,
7744 reset: false,
7745 mode,
7746 goal_column: point_for_position.exact_unclipped.column(),
7747 },
7748 window,
7749 cx,
7750 );
7751 }
7752
7753 fn update_edit_prediction_preview(
7754 &mut self,
7755 modifiers: &Modifiers,
7756 window: &mut Window,
7757 cx: &mut Context<Self>,
7758 ) {
7759 let mut modifiers_held = false;
7760 if let Some(accept_keystroke) = self
7761 .accept_edit_prediction_keybind(false, window, cx)
7762 .keystroke()
7763 {
7764 modifiers_held = modifiers_held
7765 || (accept_keystroke.modifiers() == modifiers
7766 && accept_keystroke.modifiers().modified());
7767 };
7768 if let Some(accept_partial_keystroke) = self
7769 .accept_edit_prediction_keybind(true, window, cx)
7770 .keystroke()
7771 {
7772 modifiers_held = modifiers_held
7773 || (accept_partial_keystroke.modifiers() == modifiers
7774 && accept_partial_keystroke.modifiers().modified());
7775 }
7776
7777 if modifiers_held {
7778 if matches!(
7779 self.edit_prediction_preview,
7780 EditPredictionPreview::Inactive { .. }
7781 ) {
7782 self.edit_prediction_preview = EditPredictionPreview::Active {
7783 previous_scroll_position: None,
7784 since: Instant::now(),
7785 };
7786
7787 self.update_visible_edit_prediction(window, cx);
7788 cx.notify();
7789 }
7790 } else if let EditPredictionPreview::Active {
7791 previous_scroll_position,
7792 since,
7793 } = self.edit_prediction_preview
7794 {
7795 if let (Some(previous_scroll_position), Some(position_map)) =
7796 (previous_scroll_position, self.last_position_map.as_ref())
7797 {
7798 self.set_scroll_position(
7799 previous_scroll_position
7800 .scroll_position(&position_map.snapshot.display_snapshot),
7801 window,
7802 cx,
7803 );
7804 }
7805
7806 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7807 released_too_fast: since.elapsed() < Duration::from_millis(200),
7808 };
7809 self.clear_row_highlights::<EditPredictionPreview>();
7810 self.update_visible_edit_prediction(window, cx);
7811 cx.notify();
7812 }
7813 }
7814
7815 fn update_visible_edit_prediction(
7816 &mut self,
7817 _window: &mut Window,
7818 cx: &mut Context<Self>,
7819 ) -> Option<()> {
7820 if DisableAiSettings::get_global(cx).disable_ai {
7821 return None;
7822 }
7823
7824 if self.ime_transaction.is_some() {
7825 self.discard_edit_prediction(false, cx);
7826 return None;
7827 }
7828
7829 let selection = self.selections.newest_anchor();
7830 let cursor = selection.head();
7831 let multibuffer = self.buffer.read(cx).snapshot(cx);
7832 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7833 let excerpt_id = cursor.excerpt_id;
7834
7835 let show_in_menu = self.show_edit_predictions_in_menu();
7836 let completions_menu_has_precedence = !show_in_menu
7837 && (self.context_menu.borrow().is_some()
7838 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7839
7840 if completions_menu_has_precedence
7841 || !offset_selection.is_empty()
7842 || self
7843 .active_edit_prediction
7844 .as_ref()
7845 .is_some_and(|completion| {
7846 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7847 return false;
7848 };
7849 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7850 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7851 !invalidation_range.contains(&offset_selection.head())
7852 })
7853 {
7854 self.discard_edit_prediction(false, cx);
7855 return None;
7856 }
7857
7858 self.take_active_edit_prediction(cx);
7859 let Some(provider) = self.edit_prediction_provider() else {
7860 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7861 return None;
7862 };
7863
7864 let (buffer, cursor_buffer_position) =
7865 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7866
7867 self.edit_prediction_settings =
7868 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7869
7870 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7871
7872 if self.edit_prediction_indent_conflict {
7873 let cursor_point = cursor.to_point(&multibuffer);
7874
7875 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7876
7877 if let Some((_, indent)) = indents.iter().next()
7878 && indent.len == cursor_point.column
7879 {
7880 self.edit_prediction_indent_conflict = false;
7881 }
7882 }
7883
7884 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7885
7886 let (completion_id, edits, edit_preview) = match edit_prediction {
7887 edit_prediction::EditPrediction::Local {
7888 id,
7889 edits,
7890 edit_preview,
7891 } => (id, edits, edit_preview),
7892 edit_prediction::EditPrediction::Jump {
7893 id,
7894 snapshot,
7895 target,
7896 } => {
7897 self.stale_edit_prediction_in_menu = None;
7898 self.active_edit_prediction = Some(EditPredictionState {
7899 inlay_ids: vec![],
7900 completion: EditPrediction::MoveOutside { snapshot, target },
7901 completion_id: id,
7902 invalidation_range: None,
7903 });
7904 cx.notify();
7905 return Some(());
7906 }
7907 };
7908
7909 let edits = edits
7910 .into_iter()
7911 .flat_map(|(range, new_text)| {
7912 Some((
7913 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7914 new_text,
7915 ))
7916 })
7917 .collect::<Vec<_>>();
7918 if edits.is_empty() {
7919 return None;
7920 }
7921
7922 let first_edit_start = edits.first().unwrap().0.start;
7923 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7924 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7925
7926 let last_edit_end = edits.last().unwrap().0.end;
7927 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7928 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7929
7930 let cursor_row = cursor.to_point(&multibuffer).row;
7931
7932 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7933
7934 let mut inlay_ids = Vec::new();
7935 let invalidation_row_range;
7936 let move_invalidation_row_range = if cursor_row < edit_start_row {
7937 Some(cursor_row..edit_end_row)
7938 } else if cursor_row > edit_end_row {
7939 Some(edit_start_row..cursor_row)
7940 } else {
7941 None
7942 };
7943 let supports_jump = self
7944 .edit_prediction_provider
7945 .as_ref()
7946 .map(|provider| provider.provider.supports_jump_to_edit())
7947 .unwrap_or(true);
7948
7949 let is_move = supports_jump
7950 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7951 let completion = if is_move {
7952 invalidation_row_range =
7953 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7954 let target = first_edit_start;
7955 EditPrediction::MoveWithin { target, snapshot }
7956 } else {
7957 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7958 && !self.edit_predictions_hidden_for_vim_mode;
7959
7960 if show_completions_in_buffer {
7961 if edits
7962 .iter()
7963 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7964 {
7965 let mut inlays = Vec::new();
7966 for (range, new_text) in &edits {
7967 let inlay = Inlay::edit_prediction(
7968 post_inc(&mut self.next_inlay_id),
7969 range.start,
7970 new_text.as_ref(),
7971 );
7972 inlay_ids.push(inlay.id);
7973 inlays.push(inlay);
7974 }
7975
7976 self.splice_inlays(&[], inlays, cx);
7977 } else {
7978 let background_color = cx.theme().status().deleted_background;
7979 self.highlight_text::<EditPredictionHighlight>(
7980 edits.iter().map(|(range, _)| range.clone()).collect(),
7981 HighlightStyle {
7982 background_color: Some(background_color),
7983 ..Default::default()
7984 },
7985 cx,
7986 );
7987 }
7988 }
7989
7990 invalidation_row_range = edit_start_row..edit_end_row;
7991
7992 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7993 if provider.show_tab_accept_marker() {
7994 EditDisplayMode::TabAccept
7995 } else {
7996 EditDisplayMode::Inline
7997 }
7998 } else {
7999 EditDisplayMode::DiffPopover
8000 };
8001
8002 EditPrediction::Edit {
8003 edits,
8004 edit_preview,
8005 display_mode,
8006 snapshot,
8007 }
8008 };
8009
8010 let invalidation_range = multibuffer
8011 .anchor_before(Point::new(invalidation_row_range.start, 0))
8012 ..multibuffer.anchor_after(Point::new(
8013 invalidation_row_range.end,
8014 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8015 ));
8016
8017 self.stale_edit_prediction_in_menu = None;
8018 self.active_edit_prediction = Some(EditPredictionState {
8019 inlay_ids,
8020 completion,
8021 completion_id,
8022 invalidation_range: Some(invalidation_range),
8023 });
8024
8025 cx.notify();
8026
8027 Some(())
8028 }
8029
8030 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8031 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8032 }
8033
8034 fn clear_tasks(&mut self) {
8035 self.tasks.clear()
8036 }
8037
8038 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8039 if self.tasks.insert(key, value).is_some() {
8040 // This case should hopefully be rare, but just in case...
8041 log::error!(
8042 "multiple different run targets found on a single line, only the last target will be rendered"
8043 )
8044 }
8045 }
8046
8047 /// Get all display points of breakpoints that will be rendered within editor
8048 ///
8049 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8050 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8051 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8052 fn active_breakpoints(
8053 &self,
8054 range: Range<DisplayRow>,
8055 window: &mut Window,
8056 cx: &mut Context<Self>,
8057 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8058 let mut breakpoint_display_points = HashMap::default();
8059
8060 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8061 return breakpoint_display_points;
8062 };
8063
8064 let snapshot = self.snapshot(window, cx);
8065
8066 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8067 let Some(project) = self.project() else {
8068 return breakpoint_display_points;
8069 };
8070
8071 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8072 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8073
8074 for (buffer_snapshot, range, excerpt_id) in
8075 multi_buffer_snapshot.range_to_buffer_ranges(range)
8076 {
8077 let Some(buffer) = project
8078 .read(cx)
8079 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8080 else {
8081 continue;
8082 };
8083 let breakpoints = breakpoint_store.read(cx).breakpoints(
8084 &buffer,
8085 Some(
8086 buffer_snapshot.anchor_before(range.start)
8087 ..buffer_snapshot.anchor_after(range.end),
8088 ),
8089 buffer_snapshot,
8090 cx,
8091 );
8092 for (breakpoint, state) in breakpoints {
8093 let multi_buffer_anchor =
8094 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8095 let position = multi_buffer_anchor
8096 .to_point(&multi_buffer_snapshot)
8097 .to_display_point(&snapshot);
8098
8099 breakpoint_display_points.insert(
8100 position.row(),
8101 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8102 );
8103 }
8104 }
8105
8106 breakpoint_display_points
8107 }
8108
8109 fn breakpoint_context_menu(
8110 &self,
8111 anchor: Anchor,
8112 window: &mut Window,
8113 cx: &mut Context<Self>,
8114 ) -> Entity<ui::ContextMenu> {
8115 let weak_editor = cx.weak_entity();
8116 let focus_handle = self.focus_handle(cx);
8117
8118 let row = self
8119 .buffer
8120 .read(cx)
8121 .snapshot(cx)
8122 .summary_for_anchor::<Point>(&anchor)
8123 .row;
8124
8125 let breakpoint = self
8126 .breakpoint_at_row(row, window, cx)
8127 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8128
8129 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8130 "Edit Log Breakpoint"
8131 } else {
8132 "Set Log Breakpoint"
8133 };
8134
8135 let condition_breakpoint_msg = if breakpoint
8136 .as_ref()
8137 .is_some_and(|bp| bp.1.condition.is_some())
8138 {
8139 "Edit Condition Breakpoint"
8140 } else {
8141 "Set Condition Breakpoint"
8142 };
8143
8144 let hit_condition_breakpoint_msg = if breakpoint
8145 .as_ref()
8146 .is_some_and(|bp| bp.1.hit_condition.is_some())
8147 {
8148 "Edit Hit Condition Breakpoint"
8149 } else {
8150 "Set Hit Condition Breakpoint"
8151 };
8152
8153 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8154 "Unset Breakpoint"
8155 } else {
8156 "Set Breakpoint"
8157 };
8158
8159 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8160
8161 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8162 BreakpointState::Enabled => Some("Disable"),
8163 BreakpointState::Disabled => Some("Enable"),
8164 });
8165
8166 let (anchor, breakpoint) =
8167 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8168
8169 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8170 menu.on_blur_subscription(Subscription::new(|| {}))
8171 .context(focus_handle)
8172 .when(run_to_cursor, |this| {
8173 let weak_editor = weak_editor.clone();
8174 this.entry("Run to cursor", None, move |window, cx| {
8175 weak_editor
8176 .update(cx, |editor, cx| {
8177 editor.change_selections(
8178 SelectionEffects::no_scroll(),
8179 window,
8180 cx,
8181 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8182 );
8183 })
8184 .ok();
8185
8186 window.dispatch_action(Box::new(RunToCursor), cx);
8187 })
8188 .separator()
8189 })
8190 .when_some(toggle_state_msg, |this, msg| {
8191 this.entry(msg, None, {
8192 let weak_editor = weak_editor.clone();
8193 let breakpoint = breakpoint.clone();
8194 move |_window, cx| {
8195 weak_editor
8196 .update(cx, |this, cx| {
8197 this.edit_breakpoint_at_anchor(
8198 anchor,
8199 breakpoint.as_ref().clone(),
8200 BreakpointEditAction::InvertState,
8201 cx,
8202 );
8203 })
8204 .log_err();
8205 }
8206 })
8207 })
8208 .entry(set_breakpoint_msg, None, {
8209 let weak_editor = weak_editor.clone();
8210 let breakpoint = breakpoint.clone();
8211 move |_window, cx| {
8212 weak_editor
8213 .update(cx, |this, cx| {
8214 this.edit_breakpoint_at_anchor(
8215 anchor,
8216 breakpoint.as_ref().clone(),
8217 BreakpointEditAction::Toggle,
8218 cx,
8219 );
8220 })
8221 .log_err();
8222 }
8223 })
8224 .entry(log_breakpoint_msg, None, {
8225 let breakpoint = breakpoint.clone();
8226 let weak_editor = weak_editor.clone();
8227 move |window, cx| {
8228 weak_editor
8229 .update(cx, |this, cx| {
8230 this.add_edit_breakpoint_block(
8231 anchor,
8232 breakpoint.as_ref(),
8233 BreakpointPromptEditAction::Log,
8234 window,
8235 cx,
8236 );
8237 })
8238 .log_err();
8239 }
8240 })
8241 .entry(condition_breakpoint_msg, None, {
8242 let breakpoint = breakpoint.clone();
8243 let weak_editor = weak_editor.clone();
8244 move |window, cx| {
8245 weak_editor
8246 .update(cx, |this, cx| {
8247 this.add_edit_breakpoint_block(
8248 anchor,
8249 breakpoint.as_ref(),
8250 BreakpointPromptEditAction::Condition,
8251 window,
8252 cx,
8253 );
8254 })
8255 .log_err();
8256 }
8257 })
8258 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8259 weak_editor
8260 .update(cx, |this, cx| {
8261 this.add_edit_breakpoint_block(
8262 anchor,
8263 breakpoint.as_ref(),
8264 BreakpointPromptEditAction::HitCondition,
8265 window,
8266 cx,
8267 );
8268 })
8269 .log_err();
8270 })
8271 })
8272 }
8273
8274 fn render_breakpoint(
8275 &self,
8276 position: Anchor,
8277 row: DisplayRow,
8278 breakpoint: &Breakpoint,
8279 state: Option<BreakpointSessionState>,
8280 cx: &mut Context<Self>,
8281 ) -> IconButton {
8282 let is_rejected = state.is_some_and(|s| !s.verified);
8283 // Is it a breakpoint that shows up when hovering over gutter?
8284 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8285 (false, false),
8286 |PhantomBreakpointIndicator {
8287 is_active,
8288 display_row,
8289 collides_with_existing_breakpoint,
8290 }| {
8291 (
8292 is_active && display_row == row,
8293 collides_with_existing_breakpoint,
8294 )
8295 },
8296 );
8297
8298 let (color, icon) = {
8299 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8300 (false, false) => ui::IconName::DebugBreakpoint,
8301 (true, false) => ui::IconName::DebugLogBreakpoint,
8302 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8303 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8304 };
8305
8306 let color = if is_phantom {
8307 Color::Hint
8308 } else if is_rejected {
8309 Color::Disabled
8310 } else {
8311 Color::Debugger
8312 };
8313
8314 (color, icon)
8315 };
8316
8317 let breakpoint = Arc::from(breakpoint.clone());
8318
8319 let alt_as_text = gpui::Keystroke {
8320 modifiers: Modifiers::secondary_key(),
8321 ..Default::default()
8322 };
8323 let primary_action_text = if breakpoint.is_disabled() {
8324 "Enable breakpoint"
8325 } else if is_phantom && !collides_with_existing {
8326 "Set breakpoint"
8327 } else {
8328 "Unset breakpoint"
8329 };
8330 let focus_handle = self.focus_handle.clone();
8331
8332 let meta = if is_rejected {
8333 SharedString::from("No executable code is associated with this line.")
8334 } else if collides_with_existing && !breakpoint.is_disabled() {
8335 SharedString::from(format!(
8336 "{alt_as_text}-click to disable,\nright-click for more options."
8337 ))
8338 } else {
8339 SharedString::from("Right-click for more options.")
8340 };
8341 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8342 .icon_size(IconSize::XSmall)
8343 .size(ui::ButtonSize::None)
8344 .when(is_rejected, |this| {
8345 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8346 })
8347 .icon_color(color)
8348 .style(ButtonStyle::Transparent)
8349 .on_click(cx.listener({
8350 move |editor, event: &ClickEvent, window, cx| {
8351 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8352 BreakpointEditAction::InvertState
8353 } else {
8354 BreakpointEditAction::Toggle
8355 };
8356
8357 window.focus(&editor.focus_handle(cx));
8358 editor.edit_breakpoint_at_anchor(
8359 position,
8360 breakpoint.as_ref().clone(),
8361 edit_action,
8362 cx,
8363 );
8364 }
8365 }))
8366 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8367 editor.set_breakpoint_context_menu(
8368 row,
8369 Some(position),
8370 event.position(),
8371 window,
8372 cx,
8373 );
8374 }))
8375 .tooltip(move |_window, cx| {
8376 Tooltip::with_meta_in(
8377 primary_action_text,
8378 Some(&ToggleBreakpoint),
8379 meta.clone(),
8380 &focus_handle,
8381 cx,
8382 )
8383 })
8384 }
8385
8386 fn build_tasks_context(
8387 project: &Entity<Project>,
8388 buffer: &Entity<Buffer>,
8389 buffer_row: u32,
8390 tasks: &Arc<RunnableTasks>,
8391 cx: &mut Context<Self>,
8392 ) -> Task<Option<task::TaskContext>> {
8393 let position = Point::new(buffer_row, tasks.column);
8394 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8395 let location = Location {
8396 buffer: buffer.clone(),
8397 range: range_start..range_start,
8398 };
8399 // Fill in the environmental variables from the tree-sitter captures
8400 let mut captured_task_variables = TaskVariables::default();
8401 for (capture_name, value) in tasks.extra_variables.clone() {
8402 captured_task_variables.insert(
8403 task::VariableName::Custom(capture_name.into()),
8404 value.clone(),
8405 );
8406 }
8407 project.update(cx, |project, cx| {
8408 project.task_store().update(cx, |task_store, cx| {
8409 task_store.task_context_for_location(captured_task_variables, location, cx)
8410 })
8411 })
8412 }
8413
8414 pub fn spawn_nearest_task(
8415 &mut self,
8416 action: &SpawnNearestTask,
8417 window: &mut Window,
8418 cx: &mut Context<Self>,
8419 ) {
8420 let Some((workspace, _)) = self.workspace.clone() else {
8421 return;
8422 };
8423 let Some(project) = self.project.clone() else {
8424 return;
8425 };
8426
8427 // Try to find a closest, enclosing node using tree-sitter that has a task
8428 let Some((buffer, buffer_row, tasks)) = self
8429 .find_enclosing_node_task(cx)
8430 // Or find the task that's closest in row-distance.
8431 .or_else(|| self.find_closest_task(cx))
8432 else {
8433 return;
8434 };
8435
8436 let reveal_strategy = action.reveal;
8437 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8438 cx.spawn_in(window, async move |_, cx| {
8439 let context = task_context.await?;
8440 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8441
8442 let resolved = &mut resolved_task.resolved;
8443 resolved.reveal = reveal_strategy;
8444
8445 workspace
8446 .update_in(cx, |workspace, window, cx| {
8447 workspace.schedule_resolved_task(
8448 task_source_kind,
8449 resolved_task,
8450 false,
8451 window,
8452 cx,
8453 );
8454 })
8455 .ok()
8456 })
8457 .detach();
8458 }
8459
8460 fn find_closest_task(
8461 &mut self,
8462 cx: &mut Context<Self>,
8463 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8464 let cursor_row = self
8465 .selections
8466 .newest_adjusted(&self.display_snapshot(cx))
8467 .head()
8468 .row;
8469
8470 let ((buffer_id, row), tasks) = self
8471 .tasks
8472 .iter()
8473 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8474
8475 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8476 let tasks = Arc::new(tasks.to_owned());
8477 Some((buffer, *row, tasks))
8478 }
8479
8480 fn find_enclosing_node_task(
8481 &mut self,
8482 cx: &mut Context<Self>,
8483 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8484 let snapshot = self.buffer.read(cx).snapshot(cx);
8485 let offset = self
8486 .selections
8487 .newest::<usize>(&self.display_snapshot(cx))
8488 .head();
8489 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8490 let buffer_id = excerpt.buffer().remote_id();
8491
8492 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8493 let mut cursor = layer.node().walk();
8494
8495 while cursor.goto_first_child_for_byte(offset).is_some() {
8496 if cursor.node().end_byte() == offset {
8497 cursor.goto_next_sibling();
8498 }
8499 }
8500
8501 // Ascend to the smallest ancestor that contains the range and has a task.
8502 loop {
8503 let node = cursor.node();
8504 let node_range = node.byte_range();
8505 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8506
8507 // Check if this node contains our offset
8508 if node_range.start <= offset && node_range.end >= offset {
8509 // If it contains offset, check for task
8510 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8511 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8512 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8513 }
8514 }
8515
8516 if !cursor.goto_parent() {
8517 break;
8518 }
8519 }
8520 None
8521 }
8522
8523 fn render_run_indicator(
8524 &self,
8525 _style: &EditorStyle,
8526 is_active: bool,
8527 row: DisplayRow,
8528 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8529 cx: &mut Context<Self>,
8530 ) -> IconButton {
8531 let color = Color::Muted;
8532 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8533
8534 IconButton::new(
8535 ("run_indicator", row.0 as usize),
8536 ui::IconName::PlayOutlined,
8537 )
8538 .shape(ui::IconButtonShape::Square)
8539 .icon_size(IconSize::XSmall)
8540 .icon_color(color)
8541 .toggle_state(is_active)
8542 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8543 let quick_launch = match e {
8544 ClickEvent::Keyboard(_) => true,
8545 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8546 };
8547
8548 window.focus(&editor.focus_handle(cx));
8549 editor.toggle_code_actions(
8550 &ToggleCodeActions {
8551 deployed_from: Some(CodeActionSource::RunMenu(row)),
8552 quick_launch,
8553 },
8554 window,
8555 cx,
8556 );
8557 }))
8558 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8559 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8560 }))
8561 }
8562
8563 pub fn context_menu_visible(&self) -> bool {
8564 !self.edit_prediction_preview_is_active()
8565 && self
8566 .context_menu
8567 .borrow()
8568 .as_ref()
8569 .is_some_and(|menu| menu.visible())
8570 }
8571
8572 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8573 self.context_menu
8574 .borrow()
8575 .as_ref()
8576 .map(|menu| menu.origin())
8577 }
8578
8579 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8580 self.context_menu_options = Some(options);
8581 }
8582
8583 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8584 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8585
8586 fn render_edit_prediction_popover(
8587 &mut self,
8588 text_bounds: &Bounds<Pixels>,
8589 content_origin: gpui::Point<Pixels>,
8590 right_margin: Pixels,
8591 editor_snapshot: &EditorSnapshot,
8592 visible_row_range: Range<DisplayRow>,
8593 scroll_top: ScrollOffset,
8594 scroll_bottom: ScrollOffset,
8595 line_layouts: &[LineWithInvisibles],
8596 line_height: Pixels,
8597 scroll_position: gpui::Point<ScrollOffset>,
8598 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8599 newest_selection_head: Option<DisplayPoint>,
8600 editor_width: Pixels,
8601 style: &EditorStyle,
8602 window: &mut Window,
8603 cx: &mut App,
8604 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8605 if self.mode().is_minimap() {
8606 return None;
8607 }
8608 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8609
8610 if self.edit_prediction_visible_in_cursor_popover(true) {
8611 return None;
8612 }
8613
8614 match &active_edit_prediction.completion {
8615 EditPrediction::MoveWithin { target, .. } => {
8616 let target_display_point = target.to_display_point(editor_snapshot);
8617
8618 if self.edit_prediction_requires_modifier() {
8619 if !self.edit_prediction_preview_is_active() {
8620 return None;
8621 }
8622
8623 self.render_edit_prediction_modifier_jump_popover(
8624 text_bounds,
8625 content_origin,
8626 visible_row_range,
8627 line_layouts,
8628 line_height,
8629 scroll_pixel_position,
8630 newest_selection_head,
8631 target_display_point,
8632 window,
8633 cx,
8634 )
8635 } else {
8636 self.render_edit_prediction_eager_jump_popover(
8637 text_bounds,
8638 content_origin,
8639 editor_snapshot,
8640 visible_row_range,
8641 scroll_top,
8642 scroll_bottom,
8643 line_height,
8644 scroll_pixel_position,
8645 target_display_point,
8646 editor_width,
8647 window,
8648 cx,
8649 )
8650 }
8651 }
8652 EditPrediction::Edit {
8653 display_mode: EditDisplayMode::Inline,
8654 ..
8655 } => None,
8656 EditPrediction::Edit {
8657 display_mode: EditDisplayMode::TabAccept,
8658 edits,
8659 ..
8660 } => {
8661 let range = &edits.first()?.0;
8662 let target_display_point = range.end.to_display_point(editor_snapshot);
8663
8664 self.render_edit_prediction_end_of_line_popover(
8665 "Accept",
8666 editor_snapshot,
8667 visible_row_range,
8668 target_display_point,
8669 line_height,
8670 scroll_pixel_position,
8671 content_origin,
8672 editor_width,
8673 window,
8674 cx,
8675 )
8676 }
8677 EditPrediction::Edit {
8678 edits,
8679 edit_preview,
8680 display_mode: EditDisplayMode::DiffPopover,
8681 snapshot,
8682 } => self.render_edit_prediction_diff_popover(
8683 text_bounds,
8684 content_origin,
8685 right_margin,
8686 editor_snapshot,
8687 visible_row_range,
8688 line_layouts,
8689 line_height,
8690 scroll_position,
8691 scroll_pixel_position,
8692 newest_selection_head,
8693 editor_width,
8694 style,
8695 edits,
8696 edit_preview,
8697 snapshot,
8698 window,
8699 cx,
8700 ),
8701 EditPrediction::MoveOutside { snapshot, .. } => {
8702 let file_name = snapshot
8703 .file()
8704 .map(|file| file.file_name(cx))
8705 .unwrap_or("untitled");
8706 let mut element = self
8707 .render_edit_prediction_line_popover(
8708 format!("Jump to {file_name}"),
8709 Some(IconName::ZedPredict),
8710 window,
8711 cx,
8712 )
8713 .into_any();
8714
8715 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8716 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8717 let origin_y = text_bounds.size.height - size.height - px(30.);
8718 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8719 element.prepaint_at(origin, window, cx);
8720
8721 Some((element, origin))
8722 }
8723 }
8724 }
8725
8726 fn render_edit_prediction_modifier_jump_popover(
8727 &mut self,
8728 text_bounds: &Bounds<Pixels>,
8729 content_origin: gpui::Point<Pixels>,
8730 visible_row_range: Range<DisplayRow>,
8731 line_layouts: &[LineWithInvisibles],
8732 line_height: Pixels,
8733 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8734 newest_selection_head: Option<DisplayPoint>,
8735 target_display_point: DisplayPoint,
8736 window: &mut Window,
8737 cx: &mut App,
8738 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8739 let scrolled_content_origin =
8740 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8741
8742 const SCROLL_PADDING_Y: Pixels = px(12.);
8743
8744 if target_display_point.row() < visible_row_range.start {
8745 return self.render_edit_prediction_scroll_popover(
8746 |_| SCROLL_PADDING_Y,
8747 IconName::ArrowUp,
8748 visible_row_range,
8749 line_layouts,
8750 newest_selection_head,
8751 scrolled_content_origin,
8752 window,
8753 cx,
8754 );
8755 } else if target_display_point.row() >= visible_row_range.end {
8756 return self.render_edit_prediction_scroll_popover(
8757 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8758 IconName::ArrowDown,
8759 visible_row_range,
8760 line_layouts,
8761 newest_selection_head,
8762 scrolled_content_origin,
8763 window,
8764 cx,
8765 );
8766 }
8767
8768 const POLE_WIDTH: Pixels = px(2.);
8769
8770 let line_layout =
8771 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8772 let target_column = target_display_point.column() as usize;
8773
8774 let target_x = line_layout.x_for_index(target_column);
8775 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8776 - scroll_pixel_position.y;
8777
8778 let flag_on_right = target_x < text_bounds.size.width / 2.;
8779
8780 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8781 border_color.l += 0.001;
8782
8783 let mut element = v_flex()
8784 .items_end()
8785 .when(flag_on_right, |el| el.items_start())
8786 .child(if flag_on_right {
8787 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8788 .rounded_bl(px(0.))
8789 .rounded_tl(px(0.))
8790 .border_l_2()
8791 .border_color(border_color)
8792 } else {
8793 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8794 .rounded_br(px(0.))
8795 .rounded_tr(px(0.))
8796 .border_r_2()
8797 .border_color(border_color)
8798 })
8799 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8800 .into_any();
8801
8802 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8803
8804 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8805 - point(
8806 if flag_on_right {
8807 POLE_WIDTH
8808 } else {
8809 size.width - POLE_WIDTH
8810 },
8811 size.height - line_height,
8812 );
8813
8814 origin.x = origin.x.max(content_origin.x);
8815
8816 element.prepaint_at(origin, window, cx);
8817
8818 Some((element, origin))
8819 }
8820
8821 fn render_edit_prediction_scroll_popover(
8822 &mut self,
8823 to_y: impl Fn(Size<Pixels>) -> Pixels,
8824 scroll_icon: IconName,
8825 visible_row_range: Range<DisplayRow>,
8826 line_layouts: &[LineWithInvisibles],
8827 newest_selection_head: Option<DisplayPoint>,
8828 scrolled_content_origin: gpui::Point<Pixels>,
8829 window: &mut Window,
8830 cx: &mut App,
8831 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8832 let mut element = self
8833 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8834 .into_any();
8835
8836 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8837
8838 let cursor = newest_selection_head?;
8839 let cursor_row_layout =
8840 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8841 let cursor_column = cursor.column() as usize;
8842
8843 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8844
8845 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8846
8847 element.prepaint_at(origin, window, cx);
8848 Some((element, origin))
8849 }
8850
8851 fn render_edit_prediction_eager_jump_popover(
8852 &mut self,
8853 text_bounds: &Bounds<Pixels>,
8854 content_origin: gpui::Point<Pixels>,
8855 editor_snapshot: &EditorSnapshot,
8856 visible_row_range: Range<DisplayRow>,
8857 scroll_top: ScrollOffset,
8858 scroll_bottom: ScrollOffset,
8859 line_height: Pixels,
8860 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8861 target_display_point: DisplayPoint,
8862 editor_width: Pixels,
8863 window: &mut Window,
8864 cx: &mut App,
8865 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8866 if target_display_point.row().as_f64() < scroll_top {
8867 let mut element = self
8868 .render_edit_prediction_line_popover(
8869 "Jump to Edit",
8870 Some(IconName::ArrowUp),
8871 window,
8872 cx,
8873 )
8874 .into_any();
8875
8876 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8877 let offset = point(
8878 (text_bounds.size.width - size.width) / 2.,
8879 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8880 );
8881
8882 let origin = text_bounds.origin + offset;
8883 element.prepaint_at(origin, window, cx);
8884 Some((element, origin))
8885 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8886 let mut element = self
8887 .render_edit_prediction_line_popover(
8888 "Jump to Edit",
8889 Some(IconName::ArrowDown),
8890 window,
8891 cx,
8892 )
8893 .into_any();
8894
8895 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8896 let offset = point(
8897 (text_bounds.size.width - size.width) / 2.,
8898 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8899 );
8900
8901 let origin = text_bounds.origin + offset;
8902 element.prepaint_at(origin, window, cx);
8903 Some((element, origin))
8904 } else {
8905 self.render_edit_prediction_end_of_line_popover(
8906 "Jump to Edit",
8907 editor_snapshot,
8908 visible_row_range,
8909 target_display_point,
8910 line_height,
8911 scroll_pixel_position,
8912 content_origin,
8913 editor_width,
8914 window,
8915 cx,
8916 )
8917 }
8918 }
8919
8920 fn render_edit_prediction_end_of_line_popover(
8921 self: &mut Editor,
8922 label: &'static str,
8923 editor_snapshot: &EditorSnapshot,
8924 visible_row_range: Range<DisplayRow>,
8925 target_display_point: DisplayPoint,
8926 line_height: Pixels,
8927 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8928 content_origin: gpui::Point<Pixels>,
8929 editor_width: Pixels,
8930 window: &mut Window,
8931 cx: &mut App,
8932 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8933 let target_line_end = DisplayPoint::new(
8934 target_display_point.row(),
8935 editor_snapshot.line_len(target_display_point.row()),
8936 );
8937
8938 let mut element = self
8939 .render_edit_prediction_line_popover(label, None, window, cx)
8940 .into_any();
8941
8942 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8943
8944 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8945
8946 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8947 let mut origin = start_point
8948 + line_origin
8949 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8950 origin.x = origin.x.max(content_origin.x);
8951
8952 let max_x = content_origin.x + editor_width - size.width;
8953
8954 if origin.x > max_x {
8955 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8956
8957 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8958 origin.y += offset;
8959 IconName::ArrowUp
8960 } else {
8961 origin.y -= offset;
8962 IconName::ArrowDown
8963 };
8964
8965 element = self
8966 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
8967 .into_any();
8968
8969 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8970
8971 origin.x = content_origin.x + editor_width - size.width - px(2.);
8972 }
8973
8974 element.prepaint_at(origin, window, cx);
8975 Some((element, origin))
8976 }
8977
8978 fn render_edit_prediction_diff_popover(
8979 self: &Editor,
8980 text_bounds: &Bounds<Pixels>,
8981 content_origin: gpui::Point<Pixels>,
8982 right_margin: Pixels,
8983 editor_snapshot: &EditorSnapshot,
8984 visible_row_range: Range<DisplayRow>,
8985 line_layouts: &[LineWithInvisibles],
8986 line_height: Pixels,
8987 scroll_position: gpui::Point<ScrollOffset>,
8988 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8989 newest_selection_head: Option<DisplayPoint>,
8990 editor_width: Pixels,
8991 style: &EditorStyle,
8992 edits: &Vec<(Range<Anchor>, Arc<str>)>,
8993 edit_preview: &Option<language::EditPreview>,
8994 snapshot: &language::BufferSnapshot,
8995 window: &mut Window,
8996 cx: &mut App,
8997 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8998 let edit_start = edits
8999 .first()
9000 .unwrap()
9001 .0
9002 .start
9003 .to_display_point(editor_snapshot);
9004 let edit_end = edits
9005 .last()
9006 .unwrap()
9007 .0
9008 .end
9009 .to_display_point(editor_snapshot);
9010
9011 let is_visible = visible_row_range.contains(&edit_start.row())
9012 || visible_row_range.contains(&edit_end.row());
9013 if !is_visible {
9014 return None;
9015 }
9016
9017 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9018 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9019 } else {
9020 // Fallback for providers without edit_preview
9021 crate::edit_prediction_fallback_text(edits, cx)
9022 };
9023
9024 let styled_text = highlighted_edits.to_styled_text(&style.text);
9025 let line_count = highlighted_edits.text.lines().count();
9026
9027 const BORDER_WIDTH: Pixels = px(1.);
9028
9029 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9030 let has_keybind = keybind.is_some();
9031
9032 let mut element = h_flex()
9033 .items_start()
9034 .child(
9035 h_flex()
9036 .bg(cx.theme().colors().editor_background)
9037 .border(BORDER_WIDTH)
9038 .shadow_xs()
9039 .border_color(cx.theme().colors().border)
9040 .rounded_l_lg()
9041 .when(line_count > 1, |el| el.rounded_br_lg())
9042 .pr_1()
9043 .child(styled_text),
9044 )
9045 .child(
9046 h_flex()
9047 .h(line_height + BORDER_WIDTH * 2.)
9048 .px_1p5()
9049 .gap_1()
9050 // Workaround: For some reason, there's a gap if we don't do this
9051 .ml(-BORDER_WIDTH)
9052 .shadow(vec![gpui::BoxShadow {
9053 color: gpui::black().opacity(0.05),
9054 offset: point(px(1.), px(1.)),
9055 blur_radius: px(2.),
9056 spread_radius: px(0.),
9057 }])
9058 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9059 .border(BORDER_WIDTH)
9060 .border_color(cx.theme().colors().border)
9061 .rounded_r_lg()
9062 .id("edit_prediction_diff_popover_keybind")
9063 .when(!has_keybind, |el| {
9064 let status_colors = cx.theme().status();
9065
9066 el.bg(status_colors.error_background)
9067 .border_color(status_colors.error.opacity(0.6))
9068 .child(Icon::new(IconName::Info).color(Color::Error))
9069 .cursor_default()
9070 .hoverable_tooltip(move |_window, cx| {
9071 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9072 })
9073 })
9074 .children(keybind),
9075 )
9076 .into_any();
9077
9078 let longest_row =
9079 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9080 let longest_line_width = if visible_row_range.contains(&longest_row) {
9081 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9082 } else {
9083 layout_line(
9084 longest_row,
9085 editor_snapshot,
9086 style,
9087 editor_width,
9088 |_| false,
9089 window,
9090 cx,
9091 )
9092 .width
9093 };
9094
9095 let viewport_bounds =
9096 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9097 right: -right_margin,
9098 ..Default::default()
9099 });
9100
9101 let x_after_longest = Pixels::from(
9102 ScrollPixelOffset::from(
9103 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9104 ) - scroll_pixel_position.x,
9105 );
9106
9107 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9108
9109 // Fully visible if it can be displayed within the window (allow overlapping other
9110 // panes). However, this is only allowed if the popover starts within text_bounds.
9111 let can_position_to_the_right = x_after_longest < text_bounds.right()
9112 && x_after_longest + element_bounds.width < viewport_bounds.right();
9113
9114 let mut origin = if can_position_to_the_right {
9115 point(
9116 x_after_longest,
9117 text_bounds.origin.y
9118 + Pixels::from(
9119 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9120 - scroll_pixel_position.y,
9121 ),
9122 )
9123 } else {
9124 let cursor_row = newest_selection_head.map(|head| head.row());
9125 let above_edit = edit_start
9126 .row()
9127 .0
9128 .checked_sub(line_count as u32)
9129 .map(DisplayRow);
9130 let below_edit = Some(edit_end.row() + 1);
9131 let above_cursor =
9132 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9133 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9134
9135 // Place the edit popover adjacent to the edit if there is a location
9136 // available that is onscreen and does not obscure the cursor. Otherwise,
9137 // place it adjacent to the cursor.
9138 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9139 .into_iter()
9140 .flatten()
9141 .find(|&start_row| {
9142 let end_row = start_row + line_count as u32;
9143 visible_row_range.contains(&start_row)
9144 && visible_row_range.contains(&end_row)
9145 && cursor_row
9146 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9147 })?;
9148
9149 content_origin
9150 + point(
9151 Pixels::from(-scroll_pixel_position.x),
9152 Pixels::from(
9153 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9154 ),
9155 )
9156 };
9157
9158 origin.x -= BORDER_WIDTH;
9159
9160 window.defer_draw(element, origin, 1);
9161
9162 // Do not return an element, since it will already be drawn due to defer_draw.
9163 None
9164 }
9165
9166 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9167 px(30.)
9168 }
9169
9170 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9171 if self.read_only(cx) {
9172 cx.theme().players().read_only()
9173 } else {
9174 self.style.as_ref().unwrap().local_player
9175 }
9176 }
9177
9178 fn render_edit_prediction_accept_keybind(
9179 &self,
9180 window: &mut Window,
9181 cx: &mut App,
9182 ) -> Option<AnyElement> {
9183 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9184 let accept_keystroke = accept_binding.keystroke()?;
9185
9186 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9187
9188 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9189 Color::Accent
9190 } else {
9191 Color::Muted
9192 };
9193
9194 h_flex()
9195 .px_0p5()
9196 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9197 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9198 .text_size(TextSize::XSmall.rems(cx))
9199 .child(h_flex().children(ui::render_modifiers(
9200 accept_keystroke.modifiers(),
9201 PlatformStyle::platform(),
9202 Some(modifiers_color),
9203 Some(IconSize::XSmall.rems().into()),
9204 true,
9205 )))
9206 .when(is_platform_style_mac, |parent| {
9207 parent.child(accept_keystroke.key().to_string())
9208 })
9209 .when(!is_platform_style_mac, |parent| {
9210 parent.child(
9211 Key::new(
9212 util::capitalize(accept_keystroke.key()),
9213 Some(Color::Default),
9214 )
9215 .size(Some(IconSize::XSmall.rems().into())),
9216 )
9217 })
9218 .into_any()
9219 .into()
9220 }
9221
9222 fn render_edit_prediction_line_popover(
9223 &self,
9224 label: impl Into<SharedString>,
9225 icon: Option<IconName>,
9226 window: &mut Window,
9227 cx: &mut App,
9228 ) -> Stateful<Div> {
9229 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9230
9231 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9232 let has_keybind = keybind.is_some();
9233
9234 h_flex()
9235 .id("ep-line-popover")
9236 .py_0p5()
9237 .pl_1()
9238 .pr(padding_right)
9239 .gap_1()
9240 .rounded_md()
9241 .border_1()
9242 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9243 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9244 .shadow_xs()
9245 .when(!has_keybind, |el| {
9246 let status_colors = cx.theme().status();
9247
9248 el.bg(status_colors.error_background)
9249 .border_color(status_colors.error.opacity(0.6))
9250 .pl_2()
9251 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9252 .cursor_default()
9253 .hoverable_tooltip(move |_window, cx| {
9254 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9255 })
9256 })
9257 .children(keybind)
9258 .child(
9259 Label::new(label)
9260 .size(LabelSize::Small)
9261 .when(!has_keybind, |el| {
9262 el.color(cx.theme().status().error.into()).strikethrough()
9263 }),
9264 )
9265 .when(!has_keybind, |el| {
9266 el.child(
9267 h_flex().ml_1().child(
9268 Icon::new(IconName::Info)
9269 .size(IconSize::Small)
9270 .color(cx.theme().status().error.into()),
9271 ),
9272 )
9273 })
9274 .when_some(icon, |element, icon| {
9275 element.child(
9276 div()
9277 .mt(px(1.5))
9278 .child(Icon::new(icon).size(IconSize::Small)),
9279 )
9280 })
9281 }
9282
9283 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9284 let accent_color = cx.theme().colors().text_accent;
9285 let editor_bg_color = cx.theme().colors().editor_background;
9286 editor_bg_color.blend(accent_color.opacity(0.1))
9287 }
9288
9289 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9290 let accent_color = cx.theme().colors().text_accent;
9291 let editor_bg_color = cx.theme().colors().editor_background;
9292 editor_bg_color.blend(accent_color.opacity(0.6))
9293 }
9294 fn get_prediction_provider_icon_name(
9295 provider: &Option<RegisteredEditPredictionProvider>,
9296 ) -> IconName {
9297 match provider {
9298 Some(provider) => match provider.provider.name() {
9299 "copilot" => IconName::Copilot,
9300 "supermaven" => IconName::Supermaven,
9301 _ => IconName::ZedPredict,
9302 },
9303 None => IconName::ZedPredict,
9304 }
9305 }
9306
9307 fn render_edit_prediction_cursor_popover(
9308 &self,
9309 min_width: Pixels,
9310 max_width: Pixels,
9311 cursor_point: Point,
9312 style: &EditorStyle,
9313 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9314 _window: &Window,
9315 cx: &mut Context<Editor>,
9316 ) -> Option<AnyElement> {
9317 let provider = self.edit_prediction_provider.as_ref()?;
9318 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9319
9320 let is_refreshing = provider.provider.is_refreshing(cx);
9321
9322 fn pending_completion_container(icon: IconName) -> Div {
9323 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9324 }
9325
9326 let completion = match &self.active_edit_prediction {
9327 Some(prediction) => {
9328 if !self.has_visible_completions_menu() {
9329 const RADIUS: Pixels = px(6.);
9330 const BORDER_WIDTH: Pixels = px(1.);
9331
9332 return Some(
9333 h_flex()
9334 .elevation_2(cx)
9335 .border(BORDER_WIDTH)
9336 .border_color(cx.theme().colors().border)
9337 .when(accept_keystroke.is_none(), |el| {
9338 el.border_color(cx.theme().status().error)
9339 })
9340 .rounded(RADIUS)
9341 .rounded_tl(px(0.))
9342 .overflow_hidden()
9343 .child(div().px_1p5().child(match &prediction.completion {
9344 EditPrediction::MoveWithin { target, snapshot } => {
9345 use text::ToPoint as _;
9346 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9347 {
9348 Icon::new(IconName::ZedPredictDown)
9349 } else {
9350 Icon::new(IconName::ZedPredictUp)
9351 }
9352 }
9353 EditPrediction::MoveOutside { .. } => {
9354 // TODO [zeta2] custom icon for external jump?
9355 Icon::new(provider_icon)
9356 }
9357 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9358 }))
9359 .child(
9360 h_flex()
9361 .gap_1()
9362 .py_1()
9363 .px_2()
9364 .rounded_r(RADIUS - BORDER_WIDTH)
9365 .border_l_1()
9366 .border_color(cx.theme().colors().border)
9367 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9368 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9369 el.child(
9370 Label::new("Hold")
9371 .size(LabelSize::Small)
9372 .when(accept_keystroke.is_none(), |el| {
9373 el.strikethrough()
9374 })
9375 .line_height_style(LineHeightStyle::UiLabel),
9376 )
9377 })
9378 .id("edit_prediction_cursor_popover_keybind")
9379 .when(accept_keystroke.is_none(), |el| {
9380 let status_colors = cx.theme().status();
9381
9382 el.bg(status_colors.error_background)
9383 .border_color(status_colors.error.opacity(0.6))
9384 .child(Icon::new(IconName::Info).color(Color::Error))
9385 .cursor_default()
9386 .hoverable_tooltip(move |_window, cx| {
9387 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9388 .into()
9389 })
9390 })
9391 .when_some(
9392 accept_keystroke.as_ref(),
9393 |el, accept_keystroke| {
9394 el.child(h_flex().children(ui::render_modifiers(
9395 accept_keystroke.modifiers(),
9396 PlatformStyle::platform(),
9397 Some(Color::Default),
9398 Some(IconSize::XSmall.rems().into()),
9399 false,
9400 )))
9401 },
9402 ),
9403 )
9404 .into_any(),
9405 );
9406 }
9407
9408 self.render_edit_prediction_cursor_popover_preview(
9409 prediction,
9410 cursor_point,
9411 style,
9412 cx,
9413 )?
9414 }
9415
9416 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9417 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9418 stale_completion,
9419 cursor_point,
9420 style,
9421 cx,
9422 )?,
9423
9424 None => pending_completion_container(provider_icon)
9425 .child(Label::new("...").size(LabelSize::Small)),
9426 },
9427
9428 None => pending_completion_container(provider_icon)
9429 .child(Label::new("...").size(LabelSize::Small)),
9430 };
9431
9432 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9433 completion
9434 .with_animation(
9435 "loading-completion",
9436 Animation::new(Duration::from_secs(2))
9437 .repeat()
9438 .with_easing(pulsating_between(0.4, 0.8)),
9439 |label, delta| label.opacity(delta),
9440 )
9441 .into_any_element()
9442 } else {
9443 completion.into_any_element()
9444 };
9445
9446 let has_completion = self.active_edit_prediction.is_some();
9447
9448 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9449 Some(
9450 h_flex()
9451 .min_w(min_width)
9452 .max_w(max_width)
9453 .flex_1()
9454 .elevation_2(cx)
9455 .border_color(cx.theme().colors().border)
9456 .child(
9457 div()
9458 .flex_1()
9459 .py_1()
9460 .px_2()
9461 .overflow_hidden()
9462 .child(completion),
9463 )
9464 .when_some(accept_keystroke, |el, accept_keystroke| {
9465 if !accept_keystroke.modifiers().modified() {
9466 return el;
9467 }
9468
9469 el.child(
9470 h_flex()
9471 .h_full()
9472 .border_l_1()
9473 .rounded_r_lg()
9474 .border_color(cx.theme().colors().border)
9475 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9476 .gap_1()
9477 .py_1()
9478 .px_2()
9479 .child(
9480 h_flex()
9481 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9482 .when(is_platform_style_mac, |parent| parent.gap_1())
9483 .child(h_flex().children(ui::render_modifiers(
9484 accept_keystroke.modifiers(),
9485 PlatformStyle::platform(),
9486 Some(if !has_completion {
9487 Color::Muted
9488 } else {
9489 Color::Default
9490 }),
9491 None,
9492 false,
9493 ))),
9494 )
9495 .child(Label::new("Preview").into_any_element())
9496 .opacity(if has_completion { 1.0 } else { 0.4 }),
9497 )
9498 })
9499 .into_any(),
9500 )
9501 }
9502
9503 fn render_edit_prediction_cursor_popover_preview(
9504 &self,
9505 completion: &EditPredictionState,
9506 cursor_point: Point,
9507 style: &EditorStyle,
9508 cx: &mut Context<Editor>,
9509 ) -> Option<Div> {
9510 use text::ToPoint as _;
9511
9512 fn render_relative_row_jump(
9513 prefix: impl Into<String>,
9514 current_row: u32,
9515 target_row: u32,
9516 ) -> Div {
9517 let (row_diff, arrow) = if target_row < current_row {
9518 (current_row - target_row, IconName::ArrowUp)
9519 } else {
9520 (target_row - current_row, IconName::ArrowDown)
9521 };
9522
9523 h_flex()
9524 .child(
9525 Label::new(format!("{}{}", prefix.into(), row_diff))
9526 .color(Color::Muted)
9527 .size(LabelSize::Small),
9528 )
9529 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9530 }
9531
9532 let supports_jump = self
9533 .edit_prediction_provider
9534 .as_ref()
9535 .map(|provider| provider.provider.supports_jump_to_edit())
9536 .unwrap_or(true);
9537
9538 match &completion.completion {
9539 EditPrediction::MoveWithin {
9540 target, snapshot, ..
9541 } => {
9542 if !supports_jump {
9543 return None;
9544 }
9545
9546 Some(
9547 h_flex()
9548 .px_2()
9549 .gap_2()
9550 .flex_1()
9551 .child(
9552 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9553 Icon::new(IconName::ZedPredictDown)
9554 } else {
9555 Icon::new(IconName::ZedPredictUp)
9556 },
9557 )
9558 .child(Label::new("Jump to Edit")),
9559 )
9560 }
9561 EditPrediction::MoveOutside { snapshot, .. } => {
9562 let file_name = snapshot
9563 .file()
9564 .map(|file| file.file_name(cx))
9565 .unwrap_or("untitled");
9566 Some(
9567 h_flex()
9568 .px_2()
9569 .gap_2()
9570 .flex_1()
9571 .child(Icon::new(IconName::ZedPredict))
9572 .child(Label::new(format!("Jump to {file_name}"))),
9573 )
9574 }
9575 EditPrediction::Edit {
9576 edits,
9577 edit_preview,
9578 snapshot,
9579 display_mode: _,
9580 } => {
9581 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9582
9583 let (highlighted_edits, has_more_lines) =
9584 if let Some(edit_preview) = edit_preview.as_ref() {
9585 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9586 .first_line_preview()
9587 } else {
9588 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9589 };
9590
9591 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9592 .with_default_highlights(&style.text, highlighted_edits.highlights);
9593
9594 let preview = h_flex()
9595 .gap_1()
9596 .min_w_16()
9597 .child(styled_text)
9598 .when(has_more_lines, |parent| parent.child("…"));
9599
9600 let left = if supports_jump && first_edit_row != cursor_point.row {
9601 render_relative_row_jump("", cursor_point.row, first_edit_row)
9602 .into_any_element()
9603 } else {
9604 let icon_name =
9605 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9606 Icon::new(icon_name).into_any_element()
9607 };
9608
9609 Some(
9610 h_flex()
9611 .h_full()
9612 .flex_1()
9613 .gap_2()
9614 .pr_1()
9615 .overflow_x_hidden()
9616 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9617 .child(left)
9618 .child(preview),
9619 )
9620 }
9621 }
9622 }
9623
9624 pub fn render_context_menu(
9625 &self,
9626 style: &EditorStyle,
9627 max_height_in_lines: u32,
9628 window: &mut Window,
9629 cx: &mut Context<Editor>,
9630 ) -> Option<AnyElement> {
9631 let menu = self.context_menu.borrow();
9632 let menu = menu.as_ref()?;
9633 if !menu.visible() {
9634 return None;
9635 };
9636 Some(menu.render(style, max_height_in_lines, window, cx))
9637 }
9638
9639 fn render_context_menu_aside(
9640 &mut self,
9641 max_size: Size<Pixels>,
9642 window: &mut Window,
9643 cx: &mut Context<Editor>,
9644 ) -> Option<AnyElement> {
9645 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9646 if menu.visible() {
9647 menu.render_aside(max_size, window, cx)
9648 } else {
9649 None
9650 }
9651 })
9652 }
9653
9654 fn hide_context_menu(
9655 &mut self,
9656 window: &mut Window,
9657 cx: &mut Context<Self>,
9658 ) -> Option<CodeContextMenu> {
9659 cx.notify();
9660 self.completion_tasks.clear();
9661 let context_menu = self.context_menu.borrow_mut().take();
9662 self.stale_edit_prediction_in_menu.take();
9663 self.update_visible_edit_prediction(window, cx);
9664 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9665 && let Some(completion_provider) = &self.completion_provider
9666 {
9667 completion_provider.selection_changed(None, window, cx);
9668 }
9669 context_menu
9670 }
9671
9672 fn show_snippet_choices(
9673 &mut self,
9674 choices: &Vec<String>,
9675 selection: Range<Anchor>,
9676 cx: &mut Context<Self>,
9677 ) {
9678 let Some((_, buffer, _)) = self
9679 .buffer()
9680 .read(cx)
9681 .excerpt_containing(selection.start, cx)
9682 else {
9683 return;
9684 };
9685 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9686 else {
9687 return;
9688 };
9689 if buffer != end_buffer {
9690 log::error!("expected anchor range to have matching buffer IDs");
9691 return;
9692 }
9693
9694 let id = post_inc(&mut self.next_completion_id);
9695 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9696 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9697 CompletionsMenu::new_snippet_choices(
9698 id,
9699 true,
9700 choices,
9701 selection,
9702 buffer,
9703 snippet_sort_order,
9704 ),
9705 ));
9706 }
9707
9708 pub fn insert_snippet(
9709 &mut self,
9710 insertion_ranges: &[Range<usize>],
9711 snippet: Snippet,
9712 window: &mut Window,
9713 cx: &mut Context<Self>,
9714 ) -> Result<()> {
9715 struct Tabstop<T> {
9716 is_end_tabstop: bool,
9717 ranges: Vec<Range<T>>,
9718 choices: Option<Vec<String>>,
9719 }
9720
9721 let tabstops = self.buffer.update(cx, |buffer, cx| {
9722 let snippet_text: Arc<str> = snippet.text.clone().into();
9723 let edits = insertion_ranges
9724 .iter()
9725 .cloned()
9726 .map(|range| (range, snippet_text.clone()));
9727 let autoindent_mode = AutoindentMode::Block {
9728 original_indent_columns: Vec::new(),
9729 };
9730 buffer.edit(edits, Some(autoindent_mode), cx);
9731
9732 let snapshot = &*buffer.read(cx);
9733 let snippet = &snippet;
9734 snippet
9735 .tabstops
9736 .iter()
9737 .map(|tabstop| {
9738 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9739 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9740 });
9741 let mut tabstop_ranges = tabstop
9742 .ranges
9743 .iter()
9744 .flat_map(|tabstop_range| {
9745 let mut delta = 0_isize;
9746 insertion_ranges.iter().map(move |insertion_range| {
9747 let insertion_start = insertion_range.start as isize + delta;
9748 delta +=
9749 snippet.text.len() as isize - insertion_range.len() as isize;
9750
9751 let start = ((insertion_start + tabstop_range.start) as usize)
9752 .min(snapshot.len());
9753 let end = ((insertion_start + tabstop_range.end) as usize)
9754 .min(snapshot.len());
9755 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9756 })
9757 })
9758 .collect::<Vec<_>>();
9759 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9760
9761 Tabstop {
9762 is_end_tabstop,
9763 ranges: tabstop_ranges,
9764 choices: tabstop.choices.clone(),
9765 }
9766 })
9767 .collect::<Vec<_>>()
9768 });
9769 if let Some(tabstop) = tabstops.first() {
9770 self.change_selections(Default::default(), window, cx, |s| {
9771 // Reverse order so that the first range is the newest created selection.
9772 // Completions will use it and autoscroll will prioritize it.
9773 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9774 });
9775
9776 if let Some(choices) = &tabstop.choices
9777 && let Some(selection) = tabstop.ranges.first()
9778 {
9779 self.show_snippet_choices(choices, selection.clone(), cx)
9780 }
9781
9782 // If we're already at the last tabstop and it's at the end of the snippet,
9783 // we're done, we don't need to keep the state around.
9784 if !tabstop.is_end_tabstop {
9785 let choices = tabstops
9786 .iter()
9787 .map(|tabstop| tabstop.choices.clone())
9788 .collect();
9789
9790 let ranges = tabstops
9791 .into_iter()
9792 .map(|tabstop| tabstop.ranges)
9793 .collect::<Vec<_>>();
9794
9795 self.snippet_stack.push(SnippetState {
9796 active_index: 0,
9797 ranges,
9798 choices,
9799 });
9800 }
9801
9802 // Check whether the just-entered snippet ends with an auto-closable bracket.
9803 if self.autoclose_regions.is_empty() {
9804 let snapshot = self.buffer.read(cx).snapshot(cx);
9805 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9806 let selection_head = selection.head();
9807 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9808 continue;
9809 };
9810
9811 let mut bracket_pair = None;
9812 let max_lookup_length = scope
9813 .brackets()
9814 .map(|(pair, _)| {
9815 pair.start
9816 .as_str()
9817 .chars()
9818 .count()
9819 .max(pair.end.as_str().chars().count())
9820 })
9821 .max();
9822 if let Some(max_lookup_length) = max_lookup_length {
9823 let next_text = snapshot
9824 .chars_at(selection_head)
9825 .take(max_lookup_length)
9826 .collect::<String>();
9827 let prev_text = snapshot
9828 .reversed_chars_at(selection_head)
9829 .take(max_lookup_length)
9830 .collect::<String>();
9831
9832 for (pair, enabled) in scope.brackets() {
9833 if enabled
9834 && pair.close
9835 && prev_text.starts_with(pair.start.as_str())
9836 && next_text.starts_with(pair.end.as_str())
9837 {
9838 bracket_pair = Some(pair.clone());
9839 break;
9840 }
9841 }
9842 }
9843
9844 if let Some(pair) = bracket_pair {
9845 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9846 let autoclose_enabled =
9847 self.use_autoclose && snapshot_settings.use_autoclose;
9848 if autoclose_enabled {
9849 let start = snapshot.anchor_after(selection_head);
9850 let end = snapshot.anchor_after(selection_head);
9851 self.autoclose_regions.push(AutocloseRegion {
9852 selection_id: selection.id,
9853 range: start..end,
9854 pair,
9855 });
9856 }
9857 }
9858 }
9859 }
9860 }
9861 Ok(())
9862 }
9863
9864 pub fn move_to_next_snippet_tabstop(
9865 &mut self,
9866 window: &mut Window,
9867 cx: &mut Context<Self>,
9868 ) -> bool {
9869 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9870 }
9871
9872 pub fn move_to_prev_snippet_tabstop(
9873 &mut self,
9874 window: &mut Window,
9875 cx: &mut Context<Self>,
9876 ) -> bool {
9877 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9878 }
9879
9880 pub fn move_to_snippet_tabstop(
9881 &mut self,
9882 bias: Bias,
9883 window: &mut Window,
9884 cx: &mut Context<Self>,
9885 ) -> bool {
9886 if let Some(mut snippet) = self.snippet_stack.pop() {
9887 match bias {
9888 Bias::Left => {
9889 if snippet.active_index > 0 {
9890 snippet.active_index -= 1;
9891 } else {
9892 self.snippet_stack.push(snippet);
9893 return false;
9894 }
9895 }
9896 Bias::Right => {
9897 if snippet.active_index + 1 < snippet.ranges.len() {
9898 snippet.active_index += 1;
9899 } else {
9900 self.snippet_stack.push(snippet);
9901 return false;
9902 }
9903 }
9904 }
9905 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9906 self.change_selections(Default::default(), window, cx, |s| {
9907 // Reverse order so that the first range is the newest created selection.
9908 // Completions will use it and autoscroll will prioritize it.
9909 s.select_ranges(current_ranges.iter().rev().cloned())
9910 });
9911
9912 if let Some(choices) = &snippet.choices[snippet.active_index]
9913 && let Some(selection) = current_ranges.first()
9914 {
9915 self.show_snippet_choices(choices, selection.clone(), cx);
9916 }
9917
9918 // If snippet state is not at the last tabstop, push it back on the stack
9919 if snippet.active_index + 1 < snippet.ranges.len() {
9920 self.snippet_stack.push(snippet);
9921 }
9922 return true;
9923 }
9924 }
9925
9926 false
9927 }
9928
9929 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9930 self.transact(window, cx, |this, window, cx| {
9931 this.select_all(&SelectAll, window, cx);
9932 this.insert("", window, cx);
9933 });
9934 }
9935
9936 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9937 if self.read_only(cx) {
9938 return;
9939 }
9940 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9941 self.transact(window, cx, |this, window, cx| {
9942 this.select_autoclose_pair(window, cx);
9943
9944 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9945
9946 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9947 if !this.linked_edit_ranges.is_empty() {
9948 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
9949 let snapshot = this.buffer.read(cx).snapshot(cx);
9950
9951 for selection in selections.iter() {
9952 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9953 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9954 if selection_start.buffer_id != selection_end.buffer_id {
9955 continue;
9956 }
9957 if let Some(ranges) =
9958 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9959 {
9960 for (buffer, entries) in ranges {
9961 linked_ranges.entry(buffer).or_default().extend(entries);
9962 }
9963 }
9964 }
9965 }
9966
9967 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
9968 for selection in &mut selections {
9969 if selection.is_empty() {
9970 let old_head = selection.head();
9971 let mut new_head =
9972 movement::left(&display_map, old_head.to_display_point(&display_map))
9973 .to_point(&display_map);
9974 if let Some((buffer, line_buffer_range)) = display_map
9975 .buffer_snapshot()
9976 .buffer_line_for_row(MultiBufferRow(old_head.row))
9977 {
9978 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9979 let indent_len = match indent_size.kind {
9980 IndentKind::Space => {
9981 buffer.settings_at(line_buffer_range.start, cx).tab_size
9982 }
9983 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9984 };
9985 if old_head.column <= indent_size.len && old_head.column > 0 {
9986 let indent_len = indent_len.get();
9987 new_head = cmp::min(
9988 new_head,
9989 MultiBufferPoint::new(
9990 old_head.row,
9991 ((old_head.column - 1) / indent_len) * indent_len,
9992 ),
9993 );
9994 }
9995 }
9996
9997 selection.set_head(new_head, SelectionGoal::None);
9998 }
9999 }
10000
10001 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10002 this.insert("", window, cx);
10003 let empty_str: Arc<str> = Arc::from("");
10004 for (buffer, edits) in linked_ranges {
10005 let snapshot = buffer.read(cx).snapshot();
10006 use text::ToPoint as TP;
10007
10008 let edits = edits
10009 .into_iter()
10010 .map(|range| {
10011 let end_point = TP::to_point(&range.end, &snapshot);
10012 let mut start_point = TP::to_point(&range.start, &snapshot);
10013
10014 if end_point == start_point {
10015 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10016 .saturating_sub(1);
10017 start_point =
10018 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10019 };
10020
10021 (start_point..end_point, empty_str.clone())
10022 })
10023 .sorted_by_key(|(range, _)| range.start)
10024 .collect::<Vec<_>>();
10025 buffer.update(cx, |this, cx| {
10026 this.edit(edits, None, cx);
10027 })
10028 }
10029 this.refresh_edit_prediction(true, false, window, cx);
10030 refresh_linked_ranges(this, window, cx);
10031 });
10032 }
10033
10034 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10035 if self.read_only(cx) {
10036 return;
10037 }
10038 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10039 self.transact(window, cx, |this, window, cx| {
10040 this.change_selections(Default::default(), window, cx, |s| {
10041 s.move_with(|map, selection| {
10042 if selection.is_empty() {
10043 let cursor = movement::right(map, selection.head());
10044 selection.end = cursor;
10045 selection.reversed = true;
10046 selection.goal = SelectionGoal::None;
10047 }
10048 })
10049 });
10050 this.insert("", window, cx);
10051 this.refresh_edit_prediction(true, false, window, cx);
10052 });
10053 }
10054
10055 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10056 if self.mode.is_single_line() {
10057 cx.propagate();
10058 return;
10059 }
10060
10061 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10062 if self.move_to_prev_snippet_tabstop(window, cx) {
10063 return;
10064 }
10065 self.outdent(&Outdent, window, cx);
10066 }
10067
10068 pub fn next_snippet_tabstop(
10069 &mut self,
10070 _: &NextSnippetTabstop,
10071 window: &mut Window,
10072 cx: &mut Context<Self>,
10073 ) {
10074 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10075 cx.propagate();
10076 return;
10077 }
10078
10079 if self.move_to_next_snippet_tabstop(window, cx) {
10080 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10081 return;
10082 }
10083 cx.propagate();
10084 }
10085
10086 pub fn previous_snippet_tabstop(
10087 &mut self,
10088 _: &PreviousSnippetTabstop,
10089 window: &mut Window,
10090 cx: &mut Context<Self>,
10091 ) {
10092 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10093 cx.propagate();
10094 return;
10095 }
10096
10097 if self.move_to_prev_snippet_tabstop(window, cx) {
10098 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10099 return;
10100 }
10101 cx.propagate();
10102 }
10103
10104 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10105 if self.mode.is_single_line() {
10106 cx.propagate();
10107 return;
10108 }
10109
10110 if self.move_to_next_snippet_tabstop(window, cx) {
10111 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10112 return;
10113 }
10114 if self.read_only(cx) {
10115 return;
10116 }
10117 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10118 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10119 let buffer = self.buffer.read(cx);
10120 let snapshot = buffer.snapshot(cx);
10121 let rows_iter = selections.iter().map(|s| s.head().row);
10122 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10123
10124 let has_some_cursor_in_whitespace = selections
10125 .iter()
10126 .filter(|selection| selection.is_empty())
10127 .any(|selection| {
10128 let cursor = selection.head();
10129 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10130 cursor.column < current_indent.len
10131 });
10132
10133 let mut edits = Vec::new();
10134 let mut prev_edited_row = 0;
10135 let mut row_delta = 0;
10136 for selection in &mut selections {
10137 if selection.start.row != prev_edited_row {
10138 row_delta = 0;
10139 }
10140 prev_edited_row = selection.end.row;
10141
10142 // If the selection is non-empty, then increase the indentation of the selected lines.
10143 if !selection.is_empty() {
10144 row_delta =
10145 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10146 continue;
10147 }
10148
10149 let cursor = selection.head();
10150 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10151 if let Some(suggested_indent) =
10152 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10153 {
10154 // Don't do anything if already at suggested indent
10155 // and there is any other cursor which is not
10156 if has_some_cursor_in_whitespace
10157 && cursor.column == current_indent.len
10158 && current_indent.len == suggested_indent.len
10159 {
10160 continue;
10161 }
10162
10163 // Adjust line and move cursor to suggested indent
10164 // if cursor is not at suggested indent
10165 if cursor.column < suggested_indent.len
10166 && cursor.column <= current_indent.len
10167 && current_indent.len <= suggested_indent.len
10168 {
10169 selection.start = Point::new(cursor.row, suggested_indent.len);
10170 selection.end = selection.start;
10171 if row_delta == 0 {
10172 edits.extend(Buffer::edit_for_indent_size_adjustment(
10173 cursor.row,
10174 current_indent,
10175 suggested_indent,
10176 ));
10177 row_delta = suggested_indent.len - current_indent.len;
10178 }
10179 continue;
10180 }
10181
10182 // If current indent is more than suggested indent
10183 // only move cursor to current indent and skip indent
10184 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10185 selection.start = Point::new(cursor.row, current_indent.len);
10186 selection.end = selection.start;
10187 continue;
10188 }
10189 }
10190
10191 // Otherwise, insert a hard or soft tab.
10192 let settings = buffer.language_settings_at(cursor, cx);
10193 let tab_size = if settings.hard_tabs {
10194 IndentSize::tab()
10195 } else {
10196 let tab_size = settings.tab_size.get();
10197 let indent_remainder = snapshot
10198 .text_for_range(Point::new(cursor.row, 0)..cursor)
10199 .flat_map(str::chars)
10200 .fold(row_delta % tab_size, |counter: u32, c| {
10201 if c == '\t' {
10202 0
10203 } else {
10204 (counter + 1) % tab_size
10205 }
10206 });
10207
10208 let chars_to_next_tab_stop = tab_size - indent_remainder;
10209 IndentSize::spaces(chars_to_next_tab_stop)
10210 };
10211 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10212 selection.end = selection.start;
10213 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10214 row_delta += tab_size.len;
10215 }
10216
10217 self.transact(window, cx, |this, window, cx| {
10218 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10219 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10220 this.refresh_edit_prediction(true, false, window, cx);
10221 });
10222 }
10223
10224 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10225 if self.read_only(cx) {
10226 return;
10227 }
10228 if self.mode.is_single_line() {
10229 cx.propagate();
10230 return;
10231 }
10232
10233 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10234 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10235 let mut prev_edited_row = 0;
10236 let mut row_delta = 0;
10237 let mut edits = Vec::new();
10238 let buffer = self.buffer.read(cx);
10239 let snapshot = buffer.snapshot(cx);
10240 for selection in &mut selections {
10241 if selection.start.row != prev_edited_row {
10242 row_delta = 0;
10243 }
10244 prev_edited_row = selection.end.row;
10245
10246 row_delta =
10247 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10248 }
10249
10250 self.transact(window, cx, |this, window, cx| {
10251 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10252 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10253 });
10254 }
10255
10256 fn indent_selection(
10257 buffer: &MultiBuffer,
10258 snapshot: &MultiBufferSnapshot,
10259 selection: &mut Selection<Point>,
10260 edits: &mut Vec<(Range<Point>, String)>,
10261 delta_for_start_row: u32,
10262 cx: &App,
10263 ) -> u32 {
10264 let settings = buffer.language_settings_at(selection.start, cx);
10265 let tab_size = settings.tab_size.get();
10266 let indent_kind = if settings.hard_tabs {
10267 IndentKind::Tab
10268 } else {
10269 IndentKind::Space
10270 };
10271 let mut start_row = selection.start.row;
10272 let mut end_row = selection.end.row + 1;
10273
10274 // If a selection ends at the beginning of a line, don't indent
10275 // that last line.
10276 if selection.end.column == 0 && selection.end.row > selection.start.row {
10277 end_row -= 1;
10278 }
10279
10280 // Avoid re-indenting a row that has already been indented by a
10281 // previous selection, but still update this selection's column
10282 // to reflect that indentation.
10283 if delta_for_start_row > 0 {
10284 start_row += 1;
10285 selection.start.column += delta_for_start_row;
10286 if selection.end.row == selection.start.row {
10287 selection.end.column += delta_for_start_row;
10288 }
10289 }
10290
10291 let mut delta_for_end_row = 0;
10292 let has_multiple_rows = start_row + 1 != end_row;
10293 for row in start_row..end_row {
10294 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10295 let indent_delta = match (current_indent.kind, indent_kind) {
10296 (IndentKind::Space, IndentKind::Space) => {
10297 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10298 IndentSize::spaces(columns_to_next_tab_stop)
10299 }
10300 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10301 (_, IndentKind::Tab) => IndentSize::tab(),
10302 };
10303
10304 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10305 0
10306 } else {
10307 selection.start.column
10308 };
10309 let row_start = Point::new(row, start);
10310 edits.push((
10311 row_start..row_start,
10312 indent_delta.chars().collect::<String>(),
10313 ));
10314
10315 // Update this selection's endpoints to reflect the indentation.
10316 if row == selection.start.row {
10317 selection.start.column += indent_delta.len;
10318 }
10319 if row == selection.end.row {
10320 selection.end.column += indent_delta.len;
10321 delta_for_end_row = indent_delta.len;
10322 }
10323 }
10324
10325 if selection.start.row == selection.end.row {
10326 delta_for_start_row + delta_for_end_row
10327 } else {
10328 delta_for_end_row
10329 }
10330 }
10331
10332 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10333 if self.read_only(cx) {
10334 return;
10335 }
10336 if self.mode.is_single_line() {
10337 cx.propagate();
10338 return;
10339 }
10340
10341 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10342 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10343 let selections = self.selections.all::<Point>(&display_map);
10344 let mut deletion_ranges = Vec::new();
10345 let mut last_outdent = None;
10346 {
10347 let buffer = self.buffer.read(cx);
10348 let snapshot = buffer.snapshot(cx);
10349 for selection in &selections {
10350 let settings = buffer.language_settings_at(selection.start, cx);
10351 let tab_size = settings.tab_size.get();
10352 let mut rows = selection.spanned_rows(false, &display_map);
10353
10354 // Avoid re-outdenting a row that has already been outdented by a
10355 // previous selection.
10356 if let Some(last_row) = last_outdent
10357 && last_row == rows.start
10358 {
10359 rows.start = rows.start.next_row();
10360 }
10361 let has_multiple_rows = rows.len() > 1;
10362 for row in rows.iter_rows() {
10363 let indent_size = snapshot.indent_size_for_line(row);
10364 if indent_size.len > 0 {
10365 let deletion_len = match indent_size.kind {
10366 IndentKind::Space => {
10367 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10368 if columns_to_prev_tab_stop == 0 {
10369 tab_size
10370 } else {
10371 columns_to_prev_tab_stop
10372 }
10373 }
10374 IndentKind::Tab => 1,
10375 };
10376 let start = if has_multiple_rows
10377 || deletion_len > selection.start.column
10378 || indent_size.len < selection.start.column
10379 {
10380 0
10381 } else {
10382 selection.start.column - deletion_len
10383 };
10384 deletion_ranges.push(
10385 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10386 );
10387 last_outdent = Some(row);
10388 }
10389 }
10390 }
10391 }
10392
10393 self.transact(window, cx, |this, window, cx| {
10394 this.buffer.update(cx, |buffer, cx| {
10395 let empty_str: Arc<str> = Arc::default();
10396 buffer.edit(
10397 deletion_ranges
10398 .into_iter()
10399 .map(|range| (range, empty_str.clone())),
10400 None,
10401 cx,
10402 );
10403 });
10404 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10405 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10406 });
10407 }
10408
10409 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10410 if self.read_only(cx) {
10411 return;
10412 }
10413 if self.mode.is_single_line() {
10414 cx.propagate();
10415 return;
10416 }
10417
10418 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10419 let selections = self
10420 .selections
10421 .all::<usize>(&self.display_snapshot(cx))
10422 .into_iter()
10423 .map(|s| s.range());
10424
10425 self.transact(window, cx, |this, window, cx| {
10426 this.buffer.update(cx, |buffer, cx| {
10427 buffer.autoindent_ranges(selections, cx);
10428 });
10429 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10430 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10431 });
10432 }
10433
10434 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10435 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10436 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10437 let selections = self.selections.all::<Point>(&display_map);
10438
10439 let mut new_cursors = Vec::new();
10440 let mut edit_ranges = Vec::new();
10441 let mut selections = selections.iter().peekable();
10442 while let Some(selection) = selections.next() {
10443 let mut rows = selection.spanned_rows(false, &display_map);
10444
10445 // Accumulate contiguous regions of rows that we want to delete.
10446 while let Some(next_selection) = selections.peek() {
10447 let next_rows = next_selection.spanned_rows(false, &display_map);
10448 if next_rows.start <= rows.end {
10449 rows.end = next_rows.end;
10450 selections.next().unwrap();
10451 } else {
10452 break;
10453 }
10454 }
10455
10456 let buffer = display_map.buffer_snapshot();
10457 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10458 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10459 // If there's a line after the range, delete the \n from the end of the row range
10460 (
10461 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10462 rows.end,
10463 )
10464 } else {
10465 // If there isn't a line after the range, delete the \n from the line before the
10466 // start of the row range
10467 edit_start = edit_start.saturating_sub(1);
10468 (buffer.len(), rows.start.previous_row())
10469 };
10470
10471 let text_layout_details = self.text_layout_details(window);
10472 let x = display_map.x_for_display_point(
10473 selection.head().to_display_point(&display_map),
10474 &text_layout_details,
10475 );
10476 let row = Point::new(target_row.0, 0)
10477 .to_display_point(&display_map)
10478 .row();
10479 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10480
10481 new_cursors.push((
10482 selection.id,
10483 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10484 SelectionGoal::None,
10485 ));
10486 edit_ranges.push(edit_start..edit_end);
10487 }
10488
10489 self.transact(window, cx, |this, window, cx| {
10490 let buffer = this.buffer.update(cx, |buffer, cx| {
10491 let empty_str: Arc<str> = Arc::default();
10492 buffer.edit(
10493 edit_ranges
10494 .into_iter()
10495 .map(|range| (range, empty_str.clone())),
10496 None,
10497 cx,
10498 );
10499 buffer.snapshot(cx)
10500 });
10501 let new_selections = new_cursors
10502 .into_iter()
10503 .map(|(id, cursor, goal)| {
10504 let cursor = cursor.to_point(&buffer);
10505 Selection {
10506 id,
10507 start: cursor,
10508 end: cursor,
10509 reversed: false,
10510 goal,
10511 }
10512 })
10513 .collect();
10514
10515 this.change_selections(Default::default(), window, cx, |s| {
10516 s.select(new_selections);
10517 });
10518 });
10519 }
10520
10521 pub fn join_lines_impl(
10522 &mut self,
10523 insert_whitespace: bool,
10524 window: &mut Window,
10525 cx: &mut Context<Self>,
10526 ) {
10527 if self.read_only(cx) {
10528 return;
10529 }
10530 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10531 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10532 let start = MultiBufferRow(selection.start.row);
10533 // Treat single line selections as if they include the next line. Otherwise this action
10534 // would do nothing for single line selections individual cursors.
10535 let end = if selection.start.row == selection.end.row {
10536 MultiBufferRow(selection.start.row + 1)
10537 } else {
10538 MultiBufferRow(selection.end.row)
10539 };
10540
10541 if let Some(last_row_range) = row_ranges.last_mut()
10542 && start <= last_row_range.end
10543 {
10544 last_row_range.end = end;
10545 continue;
10546 }
10547 row_ranges.push(start..end);
10548 }
10549
10550 let snapshot = self.buffer.read(cx).snapshot(cx);
10551 let mut cursor_positions = Vec::new();
10552 for row_range in &row_ranges {
10553 let anchor = snapshot.anchor_before(Point::new(
10554 row_range.end.previous_row().0,
10555 snapshot.line_len(row_range.end.previous_row()),
10556 ));
10557 cursor_positions.push(anchor..anchor);
10558 }
10559
10560 self.transact(window, cx, |this, window, cx| {
10561 for row_range in row_ranges.into_iter().rev() {
10562 for row in row_range.iter_rows().rev() {
10563 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10564 let next_line_row = row.next_row();
10565 let indent = snapshot.indent_size_for_line(next_line_row);
10566 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10567
10568 let replace =
10569 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10570 " "
10571 } else {
10572 ""
10573 };
10574
10575 this.buffer.update(cx, |buffer, cx| {
10576 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10577 });
10578 }
10579 }
10580
10581 this.change_selections(Default::default(), window, cx, |s| {
10582 s.select_anchor_ranges(cursor_positions)
10583 });
10584 });
10585 }
10586
10587 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10588 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10589 self.join_lines_impl(true, window, cx);
10590 }
10591
10592 pub fn sort_lines_case_sensitive(
10593 &mut self,
10594 _: &SortLinesCaseSensitive,
10595 window: &mut Window,
10596 cx: &mut Context<Self>,
10597 ) {
10598 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10599 }
10600
10601 pub fn sort_lines_by_length(
10602 &mut self,
10603 _: &SortLinesByLength,
10604 window: &mut Window,
10605 cx: &mut Context<Self>,
10606 ) {
10607 self.manipulate_immutable_lines(window, cx, |lines| {
10608 lines.sort_by_key(|&line| line.chars().count())
10609 })
10610 }
10611
10612 pub fn sort_lines_case_insensitive(
10613 &mut self,
10614 _: &SortLinesCaseInsensitive,
10615 window: &mut Window,
10616 cx: &mut Context<Self>,
10617 ) {
10618 self.manipulate_immutable_lines(window, cx, |lines| {
10619 lines.sort_by_key(|line| line.to_lowercase())
10620 })
10621 }
10622
10623 pub fn unique_lines_case_insensitive(
10624 &mut self,
10625 _: &UniqueLinesCaseInsensitive,
10626 window: &mut Window,
10627 cx: &mut Context<Self>,
10628 ) {
10629 self.manipulate_immutable_lines(window, cx, |lines| {
10630 let mut seen = HashSet::default();
10631 lines.retain(|line| seen.insert(line.to_lowercase()));
10632 })
10633 }
10634
10635 pub fn unique_lines_case_sensitive(
10636 &mut self,
10637 _: &UniqueLinesCaseSensitive,
10638 window: &mut Window,
10639 cx: &mut Context<Self>,
10640 ) {
10641 self.manipulate_immutable_lines(window, cx, |lines| {
10642 let mut seen = HashSet::default();
10643 lines.retain(|line| seen.insert(*line));
10644 })
10645 }
10646
10647 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10648 let snapshot = self.buffer.read(cx).snapshot(cx);
10649 for selection in self.selections.disjoint_anchors_arc().iter() {
10650 if snapshot
10651 .language_at(selection.start)
10652 .and_then(|lang| lang.config().wrap_characters.as_ref())
10653 .is_some()
10654 {
10655 return true;
10656 }
10657 }
10658 false
10659 }
10660
10661 fn wrap_selections_in_tag(
10662 &mut self,
10663 _: &WrapSelectionsInTag,
10664 window: &mut Window,
10665 cx: &mut Context<Self>,
10666 ) {
10667 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10668
10669 let snapshot = self.buffer.read(cx).snapshot(cx);
10670
10671 let mut edits = Vec::new();
10672 let mut boundaries = Vec::new();
10673
10674 for selection in self
10675 .selections
10676 .all_adjusted(&self.display_snapshot(cx))
10677 .iter()
10678 {
10679 let Some(wrap_config) = snapshot
10680 .language_at(selection.start)
10681 .and_then(|lang| lang.config().wrap_characters.clone())
10682 else {
10683 continue;
10684 };
10685
10686 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10687 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10688
10689 let start_before = snapshot.anchor_before(selection.start);
10690 let end_after = snapshot.anchor_after(selection.end);
10691
10692 edits.push((start_before..start_before, open_tag));
10693 edits.push((end_after..end_after, close_tag));
10694
10695 boundaries.push((
10696 start_before,
10697 end_after,
10698 wrap_config.start_prefix.len(),
10699 wrap_config.end_suffix.len(),
10700 ));
10701 }
10702
10703 if edits.is_empty() {
10704 return;
10705 }
10706
10707 self.transact(window, cx, |this, window, cx| {
10708 let buffer = this.buffer.update(cx, |buffer, cx| {
10709 buffer.edit(edits, None, cx);
10710 buffer.snapshot(cx)
10711 });
10712
10713 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10714 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10715 boundaries.into_iter()
10716 {
10717 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10718 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10719 new_selections.push(open_offset..open_offset);
10720 new_selections.push(close_offset..close_offset);
10721 }
10722
10723 this.change_selections(Default::default(), window, cx, |s| {
10724 s.select_ranges(new_selections);
10725 });
10726
10727 this.request_autoscroll(Autoscroll::fit(), cx);
10728 });
10729 }
10730
10731 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10732 let Some(project) = self.project.clone() else {
10733 return;
10734 };
10735 self.reload(project, window, cx)
10736 .detach_and_notify_err(window, cx);
10737 }
10738
10739 pub fn restore_file(
10740 &mut self,
10741 _: &::git::RestoreFile,
10742 window: &mut Window,
10743 cx: &mut Context<Self>,
10744 ) {
10745 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10746 let mut buffer_ids = HashSet::default();
10747 let snapshot = self.buffer().read(cx).snapshot(cx);
10748 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10749 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10750 }
10751
10752 let buffer = self.buffer().read(cx);
10753 let ranges = buffer_ids
10754 .into_iter()
10755 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10756 .collect::<Vec<_>>();
10757
10758 self.restore_hunks_in_ranges(ranges, window, cx);
10759 }
10760
10761 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10762 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10763 let selections = self
10764 .selections
10765 .all(&self.display_snapshot(cx))
10766 .into_iter()
10767 .map(|s| s.range())
10768 .collect();
10769 self.restore_hunks_in_ranges(selections, window, cx);
10770 }
10771
10772 pub fn restore_hunks_in_ranges(
10773 &mut self,
10774 ranges: Vec<Range<Point>>,
10775 window: &mut Window,
10776 cx: &mut Context<Editor>,
10777 ) {
10778 let mut revert_changes = HashMap::default();
10779 let chunk_by = self
10780 .snapshot(window, cx)
10781 .hunks_for_ranges(ranges)
10782 .into_iter()
10783 .chunk_by(|hunk| hunk.buffer_id);
10784 for (buffer_id, hunks) in &chunk_by {
10785 let hunks = hunks.collect::<Vec<_>>();
10786 for hunk in &hunks {
10787 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10788 }
10789 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10790 }
10791 drop(chunk_by);
10792 if !revert_changes.is_empty() {
10793 self.transact(window, cx, |editor, window, cx| {
10794 editor.restore(revert_changes, window, cx);
10795 });
10796 }
10797 }
10798
10799 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10800 if let Some(status) = self
10801 .addons
10802 .iter()
10803 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10804 {
10805 return Some(status);
10806 }
10807 self.project
10808 .as_ref()?
10809 .read(cx)
10810 .status_for_buffer_id(buffer_id, cx)
10811 }
10812
10813 pub fn open_active_item_in_terminal(
10814 &mut self,
10815 _: &OpenInTerminal,
10816 window: &mut Window,
10817 cx: &mut Context<Self>,
10818 ) {
10819 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10820 let project_path = buffer.read(cx).project_path(cx)?;
10821 let project = self.project()?.read(cx);
10822 let entry = project.entry_for_path(&project_path, cx)?;
10823 let parent = match &entry.canonical_path {
10824 Some(canonical_path) => canonical_path.to_path_buf(),
10825 None => project.absolute_path(&project_path, cx)?,
10826 }
10827 .parent()?
10828 .to_path_buf();
10829 Some(parent)
10830 }) {
10831 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10832 }
10833 }
10834
10835 fn set_breakpoint_context_menu(
10836 &mut self,
10837 display_row: DisplayRow,
10838 position: Option<Anchor>,
10839 clicked_point: gpui::Point<Pixels>,
10840 window: &mut Window,
10841 cx: &mut Context<Self>,
10842 ) {
10843 let source = self
10844 .buffer
10845 .read(cx)
10846 .snapshot(cx)
10847 .anchor_before(Point::new(display_row.0, 0u32));
10848
10849 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10850
10851 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10852 self,
10853 source,
10854 clicked_point,
10855 context_menu,
10856 window,
10857 cx,
10858 );
10859 }
10860
10861 fn add_edit_breakpoint_block(
10862 &mut self,
10863 anchor: Anchor,
10864 breakpoint: &Breakpoint,
10865 edit_action: BreakpointPromptEditAction,
10866 window: &mut Window,
10867 cx: &mut Context<Self>,
10868 ) {
10869 let weak_editor = cx.weak_entity();
10870 let bp_prompt = cx.new(|cx| {
10871 BreakpointPromptEditor::new(
10872 weak_editor,
10873 anchor,
10874 breakpoint.clone(),
10875 edit_action,
10876 window,
10877 cx,
10878 )
10879 });
10880
10881 let height = bp_prompt.update(cx, |this, cx| {
10882 this.prompt
10883 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10884 });
10885 let cloned_prompt = bp_prompt.clone();
10886 let blocks = vec![BlockProperties {
10887 style: BlockStyle::Sticky,
10888 placement: BlockPlacement::Above(anchor),
10889 height: Some(height),
10890 render: Arc::new(move |cx| {
10891 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10892 cloned_prompt.clone().into_any_element()
10893 }),
10894 priority: 0,
10895 }];
10896
10897 let focus_handle = bp_prompt.focus_handle(cx);
10898 window.focus(&focus_handle);
10899
10900 let block_ids = self.insert_blocks(blocks, None, cx);
10901 bp_prompt.update(cx, |prompt, _| {
10902 prompt.add_block_ids(block_ids);
10903 });
10904 }
10905
10906 pub(crate) fn breakpoint_at_row(
10907 &self,
10908 row: u32,
10909 window: &mut Window,
10910 cx: &mut Context<Self>,
10911 ) -> Option<(Anchor, Breakpoint)> {
10912 let snapshot = self.snapshot(window, cx);
10913 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10914
10915 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10916 }
10917
10918 pub(crate) fn breakpoint_at_anchor(
10919 &self,
10920 breakpoint_position: Anchor,
10921 snapshot: &EditorSnapshot,
10922 cx: &mut Context<Self>,
10923 ) -> Option<(Anchor, Breakpoint)> {
10924 let buffer = self
10925 .buffer
10926 .read(cx)
10927 .buffer_for_anchor(breakpoint_position, cx)?;
10928
10929 let enclosing_excerpt = breakpoint_position.excerpt_id;
10930 let buffer_snapshot = buffer.read(cx).snapshot();
10931
10932 let row = buffer_snapshot
10933 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10934 .row;
10935
10936 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10937 let anchor_end = snapshot
10938 .buffer_snapshot()
10939 .anchor_after(Point::new(row, line_len));
10940
10941 self.breakpoint_store
10942 .as_ref()?
10943 .read_with(cx, |breakpoint_store, cx| {
10944 breakpoint_store
10945 .breakpoints(
10946 &buffer,
10947 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10948 &buffer_snapshot,
10949 cx,
10950 )
10951 .next()
10952 .and_then(|(bp, _)| {
10953 let breakpoint_row = buffer_snapshot
10954 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10955 .row;
10956
10957 if breakpoint_row == row {
10958 snapshot
10959 .buffer_snapshot()
10960 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10961 .map(|position| (position, bp.bp.clone()))
10962 } else {
10963 None
10964 }
10965 })
10966 })
10967 }
10968
10969 pub fn edit_log_breakpoint(
10970 &mut self,
10971 _: &EditLogBreakpoint,
10972 window: &mut Window,
10973 cx: &mut Context<Self>,
10974 ) {
10975 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10976 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10977 message: None,
10978 state: BreakpointState::Enabled,
10979 condition: None,
10980 hit_condition: None,
10981 });
10982
10983 self.add_edit_breakpoint_block(
10984 anchor,
10985 &breakpoint,
10986 BreakpointPromptEditAction::Log,
10987 window,
10988 cx,
10989 );
10990 }
10991 }
10992
10993 fn breakpoints_at_cursors(
10994 &self,
10995 window: &mut Window,
10996 cx: &mut Context<Self>,
10997 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10998 let snapshot = self.snapshot(window, cx);
10999 let cursors = self
11000 .selections
11001 .disjoint_anchors_arc()
11002 .iter()
11003 .map(|selection| {
11004 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11005
11006 let breakpoint_position = self
11007 .breakpoint_at_row(cursor_position.row, window, cx)
11008 .map(|bp| bp.0)
11009 .unwrap_or_else(|| {
11010 snapshot
11011 .display_snapshot
11012 .buffer_snapshot()
11013 .anchor_after(Point::new(cursor_position.row, 0))
11014 });
11015
11016 let breakpoint = self
11017 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11018 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11019
11020 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11021 })
11022 // 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.
11023 .collect::<HashMap<Anchor, _>>();
11024
11025 cursors.into_iter().collect()
11026 }
11027
11028 pub fn enable_breakpoint(
11029 &mut self,
11030 _: &crate::actions::EnableBreakpoint,
11031 window: &mut Window,
11032 cx: &mut Context<Self>,
11033 ) {
11034 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11035 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11036 continue;
11037 };
11038 self.edit_breakpoint_at_anchor(
11039 anchor,
11040 breakpoint,
11041 BreakpointEditAction::InvertState,
11042 cx,
11043 );
11044 }
11045 }
11046
11047 pub fn disable_breakpoint(
11048 &mut self,
11049 _: &crate::actions::DisableBreakpoint,
11050 window: &mut Window,
11051 cx: &mut Context<Self>,
11052 ) {
11053 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11054 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11055 continue;
11056 };
11057 self.edit_breakpoint_at_anchor(
11058 anchor,
11059 breakpoint,
11060 BreakpointEditAction::InvertState,
11061 cx,
11062 );
11063 }
11064 }
11065
11066 pub fn toggle_breakpoint(
11067 &mut self,
11068 _: &crate::actions::ToggleBreakpoint,
11069 window: &mut Window,
11070 cx: &mut Context<Self>,
11071 ) {
11072 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11073 if let Some(breakpoint) = breakpoint {
11074 self.edit_breakpoint_at_anchor(
11075 anchor,
11076 breakpoint,
11077 BreakpointEditAction::Toggle,
11078 cx,
11079 );
11080 } else {
11081 self.edit_breakpoint_at_anchor(
11082 anchor,
11083 Breakpoint::new_standard(),
11084 BreakpointEditAction::Toggle,
11085 cx,
11086 );
11087 }
11088 }
11089 }
11090
11091 pub fn edit_breakpoint_at_anchor(
11092 &mut self,
11093 breakpoint_position: Anchor,
11094 breakpoint: Breakpoint,
11095 edit_action: BreakpointEditAction,
11096 cx: &mut Context<Self>,
11097 ) {
11098 let Some(breakpoint_store) = &self.breakpoint_store else {
11099 return;
11100 };
11101
11102 let Some(buffer) = self
11103 .buffer
11104 .read(cx)
11105 .buffer_for_anchor(breakpoint_position, cx)
11106 else {
11107 return;
11108 };
11109
11110 breakpoint_store.update(cx, |breakpoint_store, cx| {
11111 breakpoint_store.toggle_breakpoint(
11112 buffer,
11113 BreakpointWithPosition {
11114 position: breakpoint_position.text_anchor,
11115 bp: breakpoint,
11116 },
11117 edit_action,
11118 cx,
11119 );
11120 });
11121
11122 cx.notify();
11123 }
11124
11125 #[cfg(any(test, feature = "test-support"))]
11126 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11127 self.breakpoint_store.clone()
11128 }
11129
11130 pub fn prepare_restore_change(
11131 &self,
11132 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11133 hunk: &MultiBufferDiffHunk,
11134 cx: &mut App,
11135 ) -> Option<()> {
11136 if hunk.is_created_file() {
11137 return None;
11138 }
11139 let buffer = self.buffer.read(cx);
11140 let diff = buffer.diff_for(hunk.buffer_id)?;
11141 let buffer = buffer.buffer(hunk.buffer_id)?;
11142 let buffer = buffer.read(cx);
11143 let original_text = diff
11144 .read(cx)
11145 .base_text()
11146 .as_rope()
11147 .slice(hunk.diff_base_byte_range.clone());
11148 let buffer_snapshot = buffer.snapshot();
11149 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11150 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11151 probe
11152 .0
11153 .start
11154 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11155 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11156 }) {
11157 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11158 Some(())
11159 } else {
11160 None
11161 }
11162 }
11163
11164 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11165 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11166 }
11167
11168 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11169 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11170 }
11171
11172 fn manipulate_lines<M>(
11173 &mut self,
11174 window: &mut Window,
11175 cx: &mut Context<Self>,
11176 mut manipulate: M,
11177 ) where
11178 M: FnMut(&str) -> LineManipulationResult,
11179 {
11180 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11181
11182 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11183 let buffer = self.buffer.read(cx).snapshot(cx);
11184
11185 let mut edits = Vec::new();
11186
11187 let selections = self.selections.all::<Point>(&display_map);
11188 let mut selections = selections.iter().peekable();
11189 let mut contiguous_row_selections = Vec::new();
11190 let mut new_selections = Vec::new();
11191 let mut added_lines = 0;
11192 let mut removed_lines = 0;
11193
11194 while let Some(selection) = selections.next() {
11195 let (start_row, end_row) = consume_contiguous_rows(
11196 &mut contiguous_row_selections,
11197 selection,
11198 &display_map,
11199 &mut selections,
11200 );
11201
11202 let start_point = Point::new(start_row.0, 0);
11203 let end_point = Point::new(
11204 end_row.previous_row().0,
11205 buffer.line_len(end_row.previous_row()),
11206 );
11207 let text = buffer
11208 .text_for_range(start_point..end_point)
11209 .collect::<String>();
11210
11211 let LineManipulationResult {
11212 new_text,
11213 line_count_before,
11214 line_count_after,
11215 } = manipulate(&text);
11216
11217 edits.push((start_point..end_point, new_text));
11218
11219 // Selections must change based on added and removed line count
11220 let start_row =
11221 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11222 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11223 new_selections.push(Selection {
11224 id: selection.id,
11225 start: start_row,
11226 end: end_row,
11227 goal: SelectionGoal::None,
11228 reversed: selection.reversed,
11229 });
11230
11231 if line_count_after > line_count_before {
11232 added_lines += line_count_after - line_count_before;
11233 } else if line_count_before > line_count_after {
11234 removed_lines += line_count_before - line_count_after;
11235 }
11236 }
11237
11238 self.transact(window, cx, |this, window, cx| {
11239 let buffer = this.buffer.update(cx, |buffer, cx| {
11240 buffer.edit(edits, None, cx);
11241 buffer.snapshot(cx)
11242 });
11243
11244 // Recalculate offsets on newly edited buffer
11245 let new_selections = new_selections
11246 .iter()
11247 .map(|s| {
11248 let start_point = Point::new(s.start.0, 0);
11249 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11250 Selection {
11251 id: s.id,
11252 start: buffer.point_to_offset(start_point),
11253 end: buffer.point_to_offset(end_point),
11254 goal: s.goal,
11255 reversed: s.reversed,
11256 }
11257 })
11258 .collect();
11259
11260 this.change_selections(Default::default(), window, cx, |s| {
11261 s.select(new_selections);
11262 });
11263
11264 this.request_autoscroll(Autoscroll::fit(), cx);
11265 });
11266 }
11267
11268 fn manipulate_immutable_lines<Fn>(
11269 &mut self,
11270 window: &mut Window,
11271 cx: &mut Context<Self>,
11272 mut callback: Fn,
11273 ) where
11274 Fn: FnMut(&mut Vec<&str>),
11275 {
11276 self.manipulate_lines(window, cx, |text| {
11277 let mut lines: Vec<&str> = text.split('\n').collect();
11278 let line_count_before = lines.len();
11279
11280 callback(&mut lines);
11281
11282 LineManipulationResult {
11283 new_text: lines.join("\n"),
11284 line_count_before,
11285 line_count_after: lines.len(),
11286 }
11287 });
11288 }
11289
11290 fn manipulate_mutable_lines<Fn>(
11291 &mut self,
11292 window: &mut Window,
11293 cx: &mut Context<Self>,
11294 mut callback: Fn,
11295 ) where
11296 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11297 {
11298 self.manipulate_lines(window, cx, |text| {
11299 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11300 let line_count_before = lines.len();
11301
11302 callback(&mut lines);
11303
11304 LineManipulationResult {
11305 new_text: lines.join("\n"),
11306 line_count_before,
11307 line_count_after: lines.len(),
11308 }
11309 });
11310 }
11311
11312 pub fn convert_indentation_to_spaces(
11313 &mut self,
11314 _: &ConvertIndentationToSpaces,
11315 window: &mut Window,
11316 cx: &mut Context<Self>,
11317 ) {
11318 let settings = self.buffer.read(cx).language_settings(cx);
11319 let tab_size = settings.tab_size.get() as usize;
11320
11321 self.manipulate_mutable_lines(window, cx, |lines| {
11322 // Allocates a reasonably sized scratch buffer once for the whole loop
11323 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11324 // Avoids recomputing spaces that could be inserted many times
11325 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11326 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11327 .collect();
11328
11329 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11330 let mut chars = line.as_ref().chars();
11331 let mut col = 0;
11332 let mut changed = false;
11333
11334 for ch in chars.by_ref() {
11335 match ch {
11336 ' ' => {
11337 reindented_line.push(' ');
11338 col += 1;
11339 }
11340 '\t' => {
11341 // \t are converted to spaces depending on the current column
11342 let spaces_len = tab_size - (col % tab_size);
11343 reindented_line.extend(&space_cache[spaces_len - 1]);
11344 col += spaces_len;
11345 changed = true;
11346 }
11347 _ => {
11348 // If we dont append before break, the character is consumed
11349 reindented_line.push(ch);
11350 break;
11351 }
11352 }
11353 }
11354
11355 if !changed {
11356 reindented_line.clear();
11357 continue;
11358 }
11359 // Append the rest of the line and replace old reference with new one
11360 reindented_line.extend(chars);
11361 *line = Cow::Owned(reindented_line.clone());
11362 reindented_line.clear();
11363 }
11364 });
11365 }
11366
11367 pub fn convert_indentation_to_tabs(
11368 &mut self,
11369 _: &ConvertIndentationToTabs,
11370 window: &mut Window,
11371 cx: &mut Context<Self>,
11372 ) {
11373 let settings = self.buffer.read(cx).language_settings(cx);
11374 let tab_size = settings.tab_size.get() as usize;
11375
11376 self.manipulate_mutable_lines(window, cx, |lines| {
11377 // Allocates a reasonably sized buffer once for the whole loop
11378 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11379 // Avoids recomputing spaces that could be inserted many times
11380 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11381 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11382 .collect();
11383
11384 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11385 let mut chars = line.chars();
11386 let mut spaces_count = 0;
11387 let mut first_non_indent_char = None;
11388 let mut changed = false;
11389
11390 for ch in chars.by_ref() {
11391 match ch {
11392 ' ' => {
11393 // Keep track of spaces. Append \t when we reach tab_size
11394 spaces_count += 1;
11395 changed = true;
11396 if spaces_count == tab_size {
11397 reindented_line.push('\t');
11398 spaces_count = 0;
11399 }
11400 }
11401 '\t' => {
11402 reindented_line.push('\t');
11403 spaces_count = 0;
11404 }
11405 _ => {
11406 // Dont append it yet, we might have remaining spaces
11407 first_non_indent_char = Some(ch);
11408 break;
11409 }
11410 }
11411 }
11412
11413 if !changed {
11414 reindented_line.clear();
11415 continue;
11416 }
11417 // Remaining spaces that didn't make a full tab stop
11418 if spaces_count > 0 {
11419 reindented_line.extend(&space_cache[spaces_count - 1]);
11420 }
11421 // If we consume an extra character that was not indentation, add it back
11422 if let Some(extra_char) = first_non_indent_char {
11423 reindented_line.push(extra_char);
11424 }
11425 // Append the rest of the line and replace old reference with new one
11426 reindented_line.extend(chars);
11427 *line = Cow::Owned(reindented_line.clone());
11428 reindented_line.clear();
11429 }
11430 });
11431 }
11432
11433 pub fn convert_to_upper_case(
11434 &mut self,
11435 _: &ConvertToUpperCase,
11436 window: &mut Window,
11437 cx: &mut Context<Self>,
11438 ) {
11439 self.manipulate_text(window, cx, |text| text.to_uppercase())
11440 }
11441
11442 pub fn convert_to_lower_case(
11443 &mut self,
11444 _: &ConvertToLowerCase,
11445 window: &mut Window,
11446 cx: &mut Context<Self>,
11447 ) {
11448 self.manipulate_text(window, cx, |text| text.to_lowercase())
11449 }
11450
11451 pub fn convert_to_title_case(
11452 &mut self,
11453 _: &ConvertToTitleCase,
11454 window: &mut Window,
11455 cx: &mut Context<Self>,
11456 ) {
11457 self.manipulate_text(window, cx, |text| {
11458 text.split('\n')
11459 .map(|line| line.to_case(Case::Title))
11460 .join("\n")
11461 })
11462 }
11463
11464 pub fn convert_to_snake_case(
11465 &mut self,
11466 _: &ConvertToSnakeCase,
11467 window: &mut Window,
11468 cx: &mut Context<Self>,
11469 ) {
11470 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11471 }
11472
11473 pub fn convert_to_kebab_case(
11474 &mut self,
11475 _: &ConvertToKebabCase,
11476 window: &mut Window,
11477 cx: &mut Context<Self>,
11478 ) {
11479 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11480 }
11481
11482 pub fn convert_to_upper_camel_case(
11483 &mut self,
11484 _: &ConvertToUpperCamelCase,
11485 window: &mut Window,
11486 cx: &mut Context<Self>,
11487 ) {
11488 self.manipulate_text(window, cx, |text| {
11489 text.split('\n')
11490 .map(|line| line.to_case(Case::UpperCamel))
11491 .join("\n")
11492 })
11493 }
11494
11495 pub fn convert_to_lower_camel_case(
11496 &mut self,
11497 _: &ConvertToLowerCamelCase,
11498 window: &mut Window,
11499 cx: &mut Context<Self>,
11500 ) {
11501 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11502 }
11503
11504 pub fn convert_to_opposite_case(
11505 &mut self,
11506 _: &ConvertToOppositeCase,
11507 window: &mut Window,
11508 cx: &mut Context<Self>,
11509 ) {
11510 self.manipulate_text(window, cx, |text| {
11511 text.chars()
11512 .fold(String::with_capacity(text.len()), |mut t, c| {
11513 if c.is_uppercase() {
11514 t.extend(c.to_lowercase());
11515 } else {
11516 t.extend(c.to_uppercase());
11517 }
11518 t
11519 })
11520 })
11521 }
11522
11523 pub fn convert_to_sentence_case(
11524 &mut self,
11525 _: &ConvertToSentenceCase,
11526 window: &mut Window,
11527 cx: &mut Context<Self>,
11528 ) {
11529 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11530 }
11531
11532 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11533 self.manipulate_text(window, cx, |text| {
11534 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11535 if has_upper_case_characters {
11536 text.to_lowercase()
11537 } else {
11538 text.to_uppercase()
11539 }
11540 })
11541 }
11542
11543 pub fn convert_to_rot13(
11544 &mut self,
11545 _: &ConvertToRot13,
11546 window: &mut Window,
11547 cx: &mut Context<Self>,
11548 ) {
11549 self.manipulate_text(window, cx, |text| {
11550 text.chars()
11551 .map(|c| match c {
11552 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11553 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11554 _ => c,
11555 })
11556 .collect()
11557 })
11558 }
11559
11560 pub fn convert_to_rot47(
11561 &mut self,
11562 _: &ConvertToRot47,
11563 window: &mut Window,
11564 cx: &mut Context<Self>,
11565 ) {
11566 self.manipulate_text(window, cx, |text| {
11567 text.chars()
11568 .map(|c| {
11569 let code_point = c as u32;
11570 if code_point >= 33 && code_point <= 126 {
11571 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11572 }
11573 c
11574 })
11575 .collect()
11576 })
11577 }
11578
11579 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11580 where
11581 Fn: FnMut(&str) -> String,
11582 {
11583 let buffer = self.buffer.read(cx).snapshot(cx);
11584
11585 let mut new_selections = Vec::new();
11586 let mut edits = Vec::new();
11587 let mut selection_adjustment = 0i32;
11588
11589 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11590 let selection_is_empty = selection.is_empty();
11591
11592 let (start, end) = if selection_is_empty {
11593 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11594 (word_range.start, word_range.end)
11595 } else {
11596 (
11597 buffer.point_to_offset(selection.start),
11598 buffer.point_to_offset(selection.end),
11599 )
11600 };
11601
11602 let text = buffer.text_for_range(start..end).collect::<String>();
11603 let old_length = text.len() as i32;
11604 let text = callback(&text);
11605
11606 new_selections.push(Selection {
11607 start: (start as i32 - selection_adjustment) as usize,
11608 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11609 goal: SelectionGoal::None,
11610 id: selection.id,
11611 reversed: selection.reversed,
11612 });
11613
11614 selection_adjustment += old_length - text.len() as i32;
11615
11616 edits.push((start..end, text));
11617 }
11618
11619 self.transact(window, cx, |this, window, cx| {
11620 this.buffer.update(cx, |buffer, cx| {
11621 buffer.edit(edits, None, cx);
11622 });
11623
11624 this.change_selections(Default::default(), window, cx, |s| {
11625 s.select(new_selections);
11626 });
11627
11628 this.request_autoscroll(Autoscroll::fit(), cx);
11629 });
11630 }
11631
11632 pub fn move_selection_on_drop(
11633 &mut self,
11634 selection: &Selection<Anchor>,
11635 target: DisplayPoint,
11636 is_cut: bool,
11637 window: &mut Window,
11638 cx: &mut Context<Self>,
11639 ) {
11640 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11641 let buffer = display_map.buffer_snapshot();
11642 let mut edits = Vec::new();
11643 let insert_point = display_map
11644 .clip_point(target, Bias::Left)
11645 .to_point(&display_map);
11646 let text = buffer
11647 .text_for_range(selection.start..selection.end)
11648 .collect::<String>();
11649 if is_cut {
11650 edits.push(((selection.start..selection.end), String::new()));
11651 }
11652 let insert_anchor = buffer.anchor_before(insert_point);
11653 edits.push(((insert_anchor..insert_anchor), text));
11654 let last_edit_start = insert_anchor.bias_left(buffer);
11655 let last_edit_end = insert_anchor.bias_right(buffer);
11656 self.transact(window, cx, |this, window, cx| {
11657 this.buffer.update(cx, |buffer, cx| {
11658 buffer.edit(edits, None, cx);
11659 });
11660 this.change_selections(Default::default(), window, cx, |s| {
11661 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11662 });
11663 });
11664 }
11665
11666 pub fn clear_selection_drag_state(&mut self) {
11667 self.selection_drag_state = SelectionDragState::None;
11668 }
11669
11670 pub fn duplicate(
11671 &mut self,
11672 upwards: bool,
11673 whole_lines: bool,
11674 window: &mut Window,
11675 cx: &mut Context<Self>,
11676 ) {
11677 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11678
11679 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11680 let buffer = display_map.buffer_snapshot();
11681 let selections = self.selections.all::<Point>(&display_map);
11682
11683 let mut edits = Vec::new();
11684 let mut selections_iter = selections.iter().peekable();
11685 while let Some(selection) = selections_iter.next() {
11686 let mut rows = selection.spanned_rows(false, &display_map);
11687 // duplicate line-wise
11688 if whole_lines || selection.start == selection.end {
11689 // Avoid duplicating the same lines twice.
11690 while let Some(next_selection) = selections_iter.peek() {
11691 let next_rows = next_selection.spanned_rows(false, &display_map);
11692 if next_rows.start < rows.end {
11693 rows.end = next_rows.end;
11694 selections_iter.next().unwrap();
11695 } else {
11696 break;
11697 }
11698 }
11699
11700 // Copy the text from the selected row region and splice it either at the start
11701 // or end of the region.
11702 let start = Point::new(rows.start.0, 0);
11703 let end = Point::new(
11704 rows.end.previous_row().0,
11705 buffer.line_len(rows.end.previous_row()),
11706 );
11707
11708 let mut text = buffer.text_for_range(start..end).collect::<String>();
11709
11710 let insert_location = if upwards {
11711 // When duplicating upward, we need to insert before the current line.
11712 // If we're on the last line and it doesn't end with a newline,
11713 // we need to add a newline before the duplicated content.
11714 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11715 && buffer.max_point().column > 0
11716 && !text.ends_with('\n');
11717
11718 if needs_leading_newline {
11719 text.insert(0, '\n');
11720 end
11721 } else {
11722 text.push('\n');
11723 Point::new(rows.start.0, 0)
11724 }
11725 } else {
11726 text.push('\n');
11727 start
11728 };
11729 edits.push((insert_location..insert_location, text));
11730 } else {
11731 // duplicate character-wise
11732 let start = selection.start;
11733 let end = selection.end;
11734 let text = buffer.text_for_range(start..end).collect::<String>();
11735 edits.push((selection.end..selection.end, text));
11736 }
11737 }
11738
11739 self.transact(window, cx, |this, window, cx| {
11740 this.buffer.update(cx, |buffer, cx| {
11741 buffer.edit(edits, None, cx);
11742 });
11743
11744 // When duplicating upward with whole lines, move the cursor to the duplicated line
11745 if upwards && whole_lines {
11746 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11747
11748 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11749 let mut new_ranges = Vec::new();
11750 let selections = s.all::<Point>(&display_map);
11751 let mut selections_iter = selections.iter().peekable();
11752
11753 while let Some(first_selection) = selections_iter.next() {
11754 // Group contiguous selections together to find the total row span
11755 let mut group_selections = vec![first_selection];
11756 let mut rows = first_selection.spanned_rows(false, &display_map);
11757
11758 while let Some(next_selection) = selections_iter.peek() {
11759 let next_rows = next_selection.spanned_rows(false, &display_map);
11760 if next_rows.start < rows.end {
11761 rows.end = next_rows.end;
11762 group_selections.push(selections_iter.next().unwrap());
11763 } else {
11764 break;
11765 }
11766 }
11767
11768 let row_count = rows.end.0 - rows.start.0;
11769
11770 // Move all selections in this group up by the total number of duplicated rows
11771 for selection in group_selections {
11772 let new_start = Point::new(
11773 selection.start.row.saturating_sub(row_count),
11774 selection.start.column,
11775 );
11776
11777 let new_end = Point::new(
11778 selection.end.row.saturating_sub(row_count),
11779 selection.end.column,
11780 );
11781
11782 new_ranges.push(new_start..new_end);
11783 }
11784 }
11785
11786 s.select_ranges(new_ranges);
11787 });
11788 }
11789
11790 this.request_autoscroll(Autoscroll::fit(), cx);
11791 });
11792 }
11793
11794 pub fn duplicate_line_up(
11795 &mut self,
11796 _: &DuplicateLineUp,
11797 window: &mut Window,
11798 cx: &mut Context<Self>,
11799 ) {
11800 self.duplicate(true, true, window, cx);
11801 }
11802
11803 pub fn duplicate_line_down(
11804 &mut self,
11805 _: &DuplicateLineDown,
11806 window: &mut Window,
11807 cx: &mut Context<Self>,
11808 ) {
11809 self.duplicate(false, true, window, cx);
11810 }
11811
11812 pub fn duplicate_selection(
11813 &mut self,
11814 _: &DuplicateSelection,
11815 window: &mut Window,
11816 cx: &mut Context<Self>,
11817 ) {
11818 self.duplicate(false, false, window, cx);
11819 }
11820
11821 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11822 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11823 if self.mode.is_single_line() {
11824 cx.propagate();
11825 return;
11826 }
11827
11828 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11829 let buffer = self.buffer.read(cx).snapshot(cx);
11830
11831 let mut edits = Vec::new();
11832 let mut unfold_ranges = Vec::new();
11833 let mut refold_creases = Vec::new();
11834
11835 let selections = self.selections.all::<Point>(&display_map);
11836 let mut selections = selections.iter().peekable();
11837 let mut contiguous_row_selections = Vec::new();
11838 let mut new_selections = Vec::new();
11839
11840 while let Some(selection) = selections.next() {
11841 // Find all the selections that span a contiguous row range
11842 let (start_row, end_row) = consume_contiguous_rows(
11843 &mut contiguous_row_selections,
11844 selection,
11845 &display_map,
11846 &mut selections,
11847 );
11848
11849 // Move the text spanned by the row range to be before the line preceding the row range
11850 if start_row.0 > 0 {
11851 let range_to_move = Point::new(
11852 start_row.previous_row().0,
11853 buffer.line_len(start_row.previous_row()),
11854 )
11855 ..Point::new(
11856 end_row.previous_row().0,
11857 buffer.line_len(end_row.previous_row()),
11858 );
11859 let insertion_point = display_map
11860 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11861 .0;
11862
11863 // Don't move lines across excerpts
11864 if buffer
11865 .excerpt_containing(insertion_point..range_to_move.end)
11866 .is_some()
11867 {
11868 let text = buffer
11869 .text_for_range(range_to_move.clone())
11870 .flat_map(|s| s.chars())
11871 .skip(1)
11872 .chain(['\n'])
11873 .collect::<String>();
11874
11875 edits.push((
11876 buffer.anchor_after(range_to_move.start)
11877 ..buffer.anchor_before(range_to_move.end),
11878 String::new(),
11879 ));
11880 let insertion_anchor = buffer.anchor_after(insertion_point);
11881 edits.push((insertion_anchor..insertion_anchor, text));
11882
11883 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11884
11885 // Move selections up
11886 new_selections.extend(contiguous_row_selections.drain(..).map(
11887 |mut selection| {
11888 selection.start.row -= row_delta;
11889 selection.end.row -= row_delta;
11890 selection
11891 },
11892 ));
11893
11894 // Move folds up
11895 unfold_ranges.push(range_to_move.clone());
11896 for fold in display_map.folds_in_range(
11897 buffer.anchor_before(range_to_move.start)
11898 ..buffer.anchor_after(range_to_move.end),
11899 ) {
11900 let mut start = fold.range.start.to_point(&buffer);
11901 let mut end = fold.range.end.to_point(&buffer);
11902 start.row -= row_delta;
11903 end.row -= row_delta;
11904 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11905 }
11906 }
11907 }
11908
11909 // If we didn't move line(s), preserve the existing selections
11910 new_selections.append(&mut contiguous_row_selections);
11911 }
11912
11913 self.transact(window, cx, |this, window, cx| {
11914 this.unfold_ranges(&unfold_ranges, true, true, cx);
11915 this.buffer.update(cx, |buffer, cx| {
11916 for (range, text) in edits {
11917 buffer.edit([(range, text)], None, cx);
11918 }
11919 });
11920 this.fold_creases(refold_creases, true, window, cx);
11921 this.change_selections(Default::default(), window, cx, |s| {
11922 s.select(new_selections);
11923 })
11924 });
11925 }
11926
11927 pub fn move_line_down(
11928 &mut self,
11929 _: &MoveLineDown,
11930 window: &mut Window,
11931 cx: &mut Context<Self>,
11932 ) {
11933 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11934 if self.mode.is_single_line() {
11935 cx.propagate();
11936 return;
11937 }
11938
11939 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11940 let buffer = self.buffer.read(cx).snapshot(cx);
11941
11942 let mut edits = Vec::new();
11943 let mut unfold_ranges = Vec::new();
11944 let mut refold_creases = Vec::new();
11945
11946 let selections = self.selections.all::<Point>(&display_map);
11947 let mut selections = selections.iter().peekable();
11948 let mut contiguous_row_selections = Vec::new();
11949 let mut new_selections = Vec::new();
11950
11951 while let Some(selection) = selections.next() {
11952 // Find all the selections that span a contiguous row range
11953 let (start_row, end_row) = consume_contiguous_rows(
11954 &mut contiguous_row_selections,
11955 selection,
11956 &display_map,
11957 &mut selections,
11958 );
11959
11960 // Move the text spanned by the row range to be after the last line of the row range
11961 if end_row.0 <= buffer.max_point().row {
11962 let range_to_move =
11963 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11964 let insertion_point = display_map
11965 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11966 .0;
11967
11968 // Don't move lines across excerpt boundaries
11969 if buffer
11970 .excerpt_containing(range_to_move.start..insertion_point)
11971 .is_some()
11972 {
11973 let mut text = String::from("\n");
11974 text.extend(buffer.text_for_range(range_to_move.clone()));
11975 text.pop(); // Drop trailing newline
11976 edits.push((
11977 buffer.anchor_after(range_to_move.start)
11978 ..buffer.anchor_before(range_to_move.end),
11979 String::new(),
11980 ));
11981 let insertion_anchor = buffer.anchor_after(insertion_point);
11982 edits.push((insertion_anchor..insertion_anchor, text));
11983
11984 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11985
11986 // Move selections down
11987 new_selections.extend(contiguous_row_selections.drain(..).map(
11988 |mut selection| {
11989 selection.start.row += row_delta;
11990 selection.end.row += row_delta;
11991 selection
11992 },
11993 ));
11994
11995 // Move folds down
11996 unfold_ranges.push(range_to_move.clone());
11997 for fold in display_map.folds_in_range(
11998 buffer.anchor_before(range_to_move.start)
11999 ..buffer.anchor_after(range_to_move.end),
12000 ) {
12001 let mut start = fold.range.start.to_point(&buffer);
12002 let mut end = fold.range.end.to_point(&buffer);
12003 start.row += row_delta;
12004 end.row += row_delta;
12005 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12006 }
12007 }
12008 }
12009
12010 // If we didn't move line(s), preserve the existing selections
12011 new_selections.append(&mut contiguous_row_selections);
12012 }
12013
12014 self.transact(window, cx, |this, window, cx| {
12015 this.unfold_ranges(&unfold_ranges, true, true, cx);
12016 this.buffer.update(cx, |buffer, cx| {
12017 for (range, text) in edits {
12018 buffer.edit([(range, text)], None, cx);
12019 }
12020 });
12021 this.fold_creases(refold_creases, true, window, cx);
12022 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12023 });
12024 }
12025
12026 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12027 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12028 let text_layout_details = &self.text_layout_details(window);
12029 self.transact(window, cx, |this, window, cx| {
12030 let edits = this.change_selections(Default::default(), window, cx, |s| {
12031 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12032 s.move_with(|display_map, selection| {
12033 if !selection.is_empty() {
12034 return;
12035 }
12036
12037 let mut head = selection.head();
12038 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12039 if head.column() == display_map.line_len(head.row()) {
12040 transpose_offset = display_map
12041 .buffer_snapshot()
12042 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12043 }
12044
12045 if transpose_offset == 0 {
12046 return;
12047 }
12048
12049 *head.column_mut() += 1;
12050 head = display_map.clip_point(head, Bias::Right);
12051 let goal = SelectionGoal::HorizontalPosition(
12052 display_map
12053 .x_for_display_point(head, text_layout_details)
12054 .into(),
12055 );
12056 selection.collapse_to(head, goal);
12057
12058 let transpose_start = display_map
12059 .buffer_snapshot()
12060 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12061 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12062 let transpose_end = display_map
12063 .buffer_snapshot()
12064 .clip_offset(transpose_offset + 1, Bias::Right);
12065 if let Some(ch) = display_map
12066 .buffer_snapshot()
12067 .chars_at(transpose_start)
12068 .next()
12069 {
12070 edits.push((transpose_start..transpose_offset, String::new()));
12071 edits.push((transpose_end..transpose_end, ch.to_string()));
12072 }
12073 }
12074 });
12075 edits
12076 });
12077 this.buffer
12078 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12079 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12080 this.change_selections(Default::default(), window, cx, |s| {
12081 s.select(selections);
12082 });
12083 });
12084 }
12085
12086 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12087 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12088 if self.mode.is_single_line() {
12089 cx.propagate();
12090 return;
12091 }
12092
12093 self.rewrap_impl(RewrapOptions::default(), cx)
12094 }
12095
12096 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12097 let buffer = self.buffer.read(cx).snapshot(cx);
12098 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12099
12100 #[derive(Clone, Debug, PartialEq)]
12101 enum CommentFormat {
12102 /// single line comment, with prefix for line
12103 Line(String),
12104 /// single line within a block comment, with prefix for line
12105 BlockLine(String),
12106 /// a single line of a block comment that includes the initial delimiter
12107 BlockCommentWithStart(BlockCommentConfig),
12108 /// a single line of a block comment that includes the ending delimiter
12109 BlockCommentWithEnd(BlockCommentConfig),
12110 }
12111
12112 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12113 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12114 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12115 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12116 .peekable();
12117
12118 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12119 row
12120 } else {
12121 return Vec::new();
12122 };
12123
12124 let language_settings = buffer.language_settings_at(selection.head(), cx);
12125 let language_scope = buffer.language_scope_at(selection.head());
12126
12127 let indent_and_prefix_for_row =
12128 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12129 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12130 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12131 &language_scope
12132 {
12133 let indent_end = Point::new(row, indent.len);
12134 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12135 let line_text_after_indent = buffer
12136 .text_for_range(indent_end..line_end)
12137 .collect::<String>();
12138
12139 let is_within_comment_override = buffer
12140 .language_scope_at(indent_end)
12141 .is_some_and(|scope| scope.override_name() == Some("comment"));
12142 let comment_delimiters = if is_within_comment_override {
12143 // we are within a comment syntax node, but we don't
12144 // yet know what kind of comment: block, doc or line
12145 match (
12146 language_scope.documentation_comment(),
12147 language_scope.block_comment(),
12148 ) {
12149 (Some(config), _) | (_, Some(config))
12150 if buffer.contains_str_at(indent_end, &config.start) =>
12151 {
12152 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12153 }
12154 (Some(config), _) | (_, Some(config))
12155 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12156 {
12157 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12158 }
12159 (Some(config), _) | (_, Some(config))
12160 if buffer.contains_str_at(indent_end, &config.prefix) =>
12161 {
12162 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12163 }
12164 (_, _) => language_scope
12165 .line_comment_prefixes()
12166 .iter()
12167 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12168 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12169 }
12170 } else {
12171 // we not in an overridden comment node, but we may
12172 // be within a non-overridden line comment node
12173 language_scope
12174 .line_comment_prefixes()
12175 .iter()
12176 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12177 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12178 };
12179
12180 let rewrap_prefix = language_scope
12181 .rewrap_prefixes()
12182 .iter()
12183 .find_map(|prefix_regex| {
12184 prefix_regex.find(&line_text_after_indent).map(|mat| {
12185 if mat.start() == 0 {
12186 Some(mat.as_str().to_string())
12187 } else {
12188 None
12189 }
12190 })
12191 })
12192 .flatten();
12193 (comment_delimiters, rewrap_prefix)
12194 } else {
12195 (None, None)
12196 };
12197 (indent, comment_prefix, rewrap_prefix)
12198 };
12199
12200 let mut ranges = Vec::new();
12201 let from_empty_selection = selection.is_empty();
12202
12203 let mut current_range_start = first_row;
12204 let mut prev_row = first_row;
12205 let (
12206 mut current_range_indent,
12207 mut current_range_comment_delimiters,
12208 mut current_range_rewrap_prefix,
12209 ) = indent_and_prefix_for_row(first_row);
12210
12211 for row in non_blank_rows_iter.skip(1) {
12212 let has_paragraph_break = row > prev_row + 1;
12213
12214 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12215 indent_and_prefix_for_row(row);
12216
12217 let has_indent_change = row_indent != current_range_indent;
12218 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12219
12220 let has_boundary_change = has_comment_change
12221 || row_rewrap_prefix.is_some()
12222 || (has_indent_change && current_range_comment_delimiters.is_some());
12223
12224 if has_paragraph_break || has_boundary_change {
12225 ranges.push((
12226 language_settings.clone(),
12227 Point::new(current_range_start, 0)
12228 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12229 current_range_indent,
12230 current_range_comment_delimiters.clone(),
12231 current_range_rewrap_prefix.clone(),
12232 from_empty_selection,
12233 ));
12234 current_range_start = row;
12235 current_range_indent = row_indent;
12236 current_range_comment_delimiters = row_comment_delimiters;
12237 current_range_rewrap_prefix = row_rewrap_prefix;
12238 }
12239 prev_row = row;
12240 }
12241
12242 ranges.push((
12243 language_settings.clone(),
12244 Point::new(current_range_start, 0)
12245 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12246 current_range_indent,
12247 current_range_comment_delimiters,
12248 current_range_rewrap_prefix,
12249 from_empty_selection,
12250 ));
12251
12252 ranges
12253 });
12254
12255 let mut edits = Vec::new();
12256 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12257
12258 for (
12259 language_settings,
12260 wrap_range,
12261 mut indent_size,
12262 comment_prefix,
12263 rewrap_prefix,
12264 from_empty_selection,
12265 ) in wrap_ranges
12266 {
12267 let mut start_row = wrap_range.start.row;
12268 let mut end_row = wrap_range.end.row;
12269
12270 // Skip selections that overlap with a range that has already been rewrapped.
12271 let selection_range = start_row..end_row;
12272 if rewrapped_row_ranges
12273 .iter()
12274 .any(|range| range.overlaps(&selection_range))
12275 {
12276 continue;
12277 }
12278
12279 let tab_size = language_settings.tab_size;
12280
12281 let (line_prefix, inside_comment) = match &comment_prefix {
12282 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12283 (Some(prefix.as_str()), true)
12284 }
12285 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12286 (Some(prefix.as_ref()), true)
12287 }
12288 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12289 start: _,
12290 end: _,
12291 prefix,
12292 tab_size,
12293 })) => {
12294 indent_size.len += tab_size;
12295 (Some(prefix.as_ref()), true)
12296 }
12297 None => (None, false),
12298 };
12299 let indent_prefix = indent_size.chars().collect::<String>();
12300 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12301
12302 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12303 RewrapBehavior::InComments => inside_comment,
12304 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12305 RewrapBehavior::Anywhere => true,
12306 };
12307
12308 let should_rewrap = options.override_language_settings
12309 || allow_rewrap_based_on_language
12310 || self.hard_wrap.is_some();
12311 if !should_rewrap {
12312 continue;
12313 }
12314
12315 if from_empty_selection {
12316 'expand_upwards: while start_row > 0 {
12317 let prev_row = start_row - 1;
12318 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12319 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12320 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12321 {
12322 start_row = prev_row;
12323 } else {
12324 break 'expand_upwards;
12325 }
12326 }
12327
12328 'expand_downwards: while end_row < buffer.max_point().row {
12329 let next_row = end_row + 1;
12330 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12331 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12332 && !buffer.is_line_blank(MultiBufferRow(next_row))
12333 {
12334 end_row = next_row;
12335 } else {
12336 break 'expand_downwards;
12337 }
12338 }
12339 }
12340
12341 let start = Point::new(start_row, 0);
12342 let start_offset = ToOffset::to_offset(&start, &buffer);
12343 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12344 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12345 let mut first_line_delimiter = None;
12346 let mut last_line_delimiter = None;
12347 let Some(lines_without_prefixes) = selection_text
12348 .lines()
12349 .enumerate()
12350 .map(|(ix, line)| {
12351 let line_trimmed = line.trim_start();
12352 if rewrap_prefix.is_some() && ix > 0 {
12353 Ok(line_trimmed)
12354 } else if let Some(
12355 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12356 start,
12357 prefix,
12358 end,
12359 tab_size,
12360 })
12361 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12362 start,
12363 prefix,
12364 end,
12365 tab_size,
12366 }),
12367 ) = &comment_prefix
12368 {
12369 let line_trimmed = line_trimmed
12370 .strip_prefix(start.as_ref())
12371 .map(|s| {
12372 let mut indent_size = indent_size;
12373 indent_size.len -= tab_size;
12374 let indent_prefix: String = indent_size.chars().collect();
12375 first_line_delimiter = Some((indent_prefix, start));
12376 s.trim_start()
12377 })
12378 .unwrap_or(line_trimmed);
12379 let line_trimmed = line_trimmed
12380 .strip_suffix(end.as_ref())
12381 .map(|s| {
12382 last_line_delimiter = Some(end);
12383 s.trim_end()
12384 })
12385 .unwrap_or(line_trimmed);
12386 let line_trimmed = line_trimmed
12387 .strip_prefix(prefix.as_ref())
12388 .unwrap_or(line_trimmed);
12389 Ok(line_trimmed)
12390 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12391 line_trimmed.strip_prefix(prefix).with_context(|| {
12392 format!("line did not start with prefix {prefix:?}: {line:?}")
12393 })
12394 } else {
12395 line_trimmed
12396 .strip_prefix(&line_prefix.trim_start())
12397 .with_context(|| {
12398 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12399 })
12400 }
12401 })
12402 .collect::<Result<Vec<_>, _>>()
12403 .log_err()
12404 else {
12405 continue;
12406 };
12407
12408 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12409 buffer
12410 .language_settings_at(Point::new(start_row, 0), cx)
12411 .preferred_line_length as usize
12412 });
12413
12414 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12415 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12416 } else {
12417 line_prefix.clone()
12418 };
12419
12420 let wrapped_text = {
12421 let mut wrapped_text = wrap_with_prefix(
12422 line_prefix,
12423 subsequent_lines_prefix,
12424 lines_without_prefixes.join("\n"),
12425 wrap_column,
12426 tab_size,
12427 options.preserve_existing_whitespace,
12428 );
12429
12430 if let Some((indent, delimiter)) = first_line_delimiter {
12431 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12432 }
12433 if let Some(last_line) = last_line_delimiter {
12434 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12435 }
12436
12437 wrapped_text
12438 };
12439
12440 // TODO: should always use char-based diff while still supporting cursor behavior that
12441 // matches vim.
12442 let mut diff_options = DiffOptions::default();
12443 if options.override_language_settings {
12444 diff_options.max_word_diff_len = 0;
12445 diff_options.max_word_diff_line_count = 0;
12446 } else {
12447 diff_options.max_word_diff_len = usize::MAX;
12448 diff_options.max_word_diff_line_count = usize::MAX;
12449 }
12450
12451 for (old_range, new_text) in
12452 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12453 {
12454 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12455 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12456 edits.push((edit_start..edit_end, new_text));
12457 }
12458
12459 rewrapped_row_ranges.push(start_row..=end_row);
12460 }
12461
12462 self.buffer
12463 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12464 }
12465
12466 pub fn cut_common(
12467 &mut self,
12468 cut_no_selection_line: bool,
12469 window: &mut Window,
12470 cx: &mut Context<Self>,
12471 ) -> ClipboardItem {
12472 let mut text = String::new();
12473 let buffer = self.buffer.read(cx).snapshot(cx);
12474 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12475 let mut clipboard_selections = Vec::with_capacity(selections.len());
12476 {
12477 let max_point = buffer.max_point();
12478 let mut is_first = true;
12479 for selection in &mut selections {
12480 let is_entire_line =
12481 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12482 if is_entire_line {
12483 selection.start = Point::new(selection.start.row, 0);
12484 if !selection.is_empty() && selection.end.column == 0 {
12485 selection.end = cmp::min(max_point, selection.end);
12486 } else {
12487 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12488 }
12489 selection.goal = SelectionGoal::None;
12490 }
12491 if is_first {
12492 is_first = false;
12493 } else {
12494 text += "\n";
12495 }
12496 let mut len = 0;
12497 for chunk in buffer.text_for_range(selection.start..selection.end) {
12498 text.push_str(chunk);
12499 len += chunk.len();
12500 }
12501 clipboard_selections.push(ClipboardSelection {
12502 len,
12503 is_entire_line,
12504 first_line_indent: buffer
12505 .indent_size_for_line(MultiBufferRow(selection.start.row))
12506 .len,
12507 });
12508 }
12509 }
12510
12511 self.transact(window, cx, |this, window, cx| {
12512 this.change_selections(Default::default(), window, cx, |s| {
12513 s.select(selections);
12514 });
12515 this.insert("", window, cx);
12516 });
12517 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12518 }
12519
12520 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12521 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12522 let item = self.cut_common(true, window, cx);
12523 cx.write_to_clipboard(item);
12524 }
12525
12526 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12527 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12528 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12529 s.move_with(|snapshot, sel| {
12530 if sel.is_empty() {
12531 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12532 }
12533 if sel.is_empty() {
12534 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12535 }
12536 });
12537 });
12538 let item = self.cut_common(false, window, cx);
12539 cx.set_global(KillRing(item))
12540 }
12541
12542 pub fn kill_ring_yank(
12543 &mut self,
12544 _: &KillRingYank,
12545 window: &mut Window,
12546 cx: &mut Context<Self>,
12547 ) {
12548 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12549 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12550 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12551 (kill_ring.text().to_string(), kill_ring.metadata_json())
12552 } else {
12553 return;
12554 }
12555 } else {
12556 return;
12557 };
12558 self.do_paste(&text, metadata, false, window, cx);
12559 }
12560
12561 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12562 self.do_copy(true, cx);
12563 }
12564
12565 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12566 self.do_copy(false, cx);
12567 }
12568
12569 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12570 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12571 let buffer = self.buffer.read(cx).read(cx);
12572 let mut text = String::new();
12573
12574 let mut clipboard_selections = Vec::with_capacity(selections.len());
12575 {
12576 let max_point = buffer.max_point();
12577 let mut is_first = true;
12578 for selection in &selections {
12579 let mut start = selection.start;
12580 let mut end = selection.end;
12581 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12582 let mut add_trailing_newline = false;
12583 if is_entire_line {
12584 start = Point::new(start.row, 0);
12585 let next_line_start = Point::new(end.row + 1, 0);
12586 if next_line_start <= max_point {
12587 end = next_line_start;
12588 } else {
12589 // We're on the last line without a trailing newline.
12590 // Copy to the end of the line and add a newline afterwards.
12591 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12592 add_trailing_newline = true;
12593 }
12594 }
12595
12596 let mut trimmed_selections = Vec::new();
12597 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12598 let row = MultiBufferRow(start.row);
12599 let first_indent = buffer.indent_size_for_line(row);
12600 if first_indent.len == 0 || start.column > first_indent.len {
12601 trimmed_selections.push(start..end);
12602 } else {
12603 trimmed_selections.push(
12604 Point::new(row.0, first_indent.len)
12605 ..Point::new(row.0, buffer.line_len(row)),
12606 );
12607 for row in start.row + 1..=end.row {
12608 let mut line_len = buffer.line_len(MultiBufferRow(row));
12609 if row == end.row {
12610 line_len = end.column;
12611 }
12612 if line_len == 0 {
12613 trimmed_selections
12614 .push(Point::new(row, 0)..Point::new(row, line_len));
12615 continue;
12616 }
12617 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12618 if row_indent_size.len >= first_indent.len {
12619 trimmed_selections.push(
12620 Point::new(row, first_indent.len)..Point::new(row, line_len),
12621 );
12622 } else {
12623 trimmed_selections.clear();
12624 trimmed_selections.push(start..end);
12625 break;
12626 }
12627 }
12628 }
12629 } else {
12630 trimmed_selections.push(start..end);
12631 }
12632
12633 for trimmed_range in trimmed_selections {
12634 if is_first {
12635 is_first = false;
12636 } else {
12637 text += "\n";
12638 }
12639 let mut len = 0;
12640 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12641 text.push_str(chunk);
12642 len += chunk.len();
12643 }
12644 if add_trailing_newline {
12645 text.push('\n');
12646 len += 1;
12647 }
12648 clipboard_selections.push(ClipboardSelection {
12649 len,
12650 is_entire_line,
12651 first_line_indent: buffer
12652 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12653 .len,
12654 });
12655 }
12656 }
12657 }
12658
12659 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12660 text,
12661 clipboard_selections,
12662 ));
12663 }
12664
12665 pub fn do_paste(
12666 &mut self,
12667 text: &String,
12668 clipboard_selections: Option<Vec<ClipboardSelection>>,
12669 handle_entire_lines: bool,
12670 window: &mut Window,
12671 cx: &mut Context<Self>,
12672 ) {
12673 if self.read_only(cx) {
12674 return;
12675 }
12676
12677 let clipboard_text = Cow::Borrowed(text.as_str());
12678
12679 self.transact(window, cx, |this, window, cx| {
12680 let had_active_edit_prediction = this.has_active_edit_prediction();
12681 let display_map = this.display_snapshot(cx);
12682 let old_selections = this.selections.all::<usize>(&display_map);
12683 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12684
12685 if let Some(mut clipboard_selections) = clipboard_selections {
12686 let all_selections_were_entire_line =
12687 clipboard_selections.iter().all(|s| s.is_entire_line);
12688 let first_selection_indent_column =
12689 clipboard_selections.first().map(|s| s.first_line_indent);
12690 if clipboard_selections.len() != old_selections.len() {
12691 clipboard_selections.drain(..);
12692 }
12693 let mut auto_indent_on_paste = true;
12694
12695 this.buffer.update(cx, |buffer, cx| {
12696 let snapshot = buffer.read(cx);
12697 auto_indent_on_paste = snapshot
12698 .language_settings_at(cursor_offset, cx)
12699 .auto_indent_on_paste;
12700
12701 let mut start_offset = 0;
12702 let mut edits = Vec::new();
12703 let mut original_indent_columns = Vec::new();
12704 for (ix, selection) in old_selections.iter().enumerate() {
12705 let to_insert;
12706 let entire_line;
12707 let original_indent_column;
12708 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12709 let end_offset = start_offset + clipboard_selection.len;
12710 to_insert = &clipboard_text[start_offset..end_offset];
12711 entire_line = clipboard_selection.is_entire_line;
12712 start_offset = end_offset + 1;
12713 original_indent_column = Some(clipboard_selection.first_line_indent);
12714 } else {
12715 to_insert = &*clipboard_text;
12716 entire_line = all_selections_were_entire_line;
12717 original_indent_column = first_selection_indent_column
12718 }
12719
12720 let (range, to_insert) =
12721 if selection.is_empty() && handle_entire_lines && entire_line {
12722 // If the corresponding selection was empty when this slice of the
12723 // clipboard text was written, then the entire line containing the
12724 // selection was copied. If this selection is also currently empty,
12725 // then paste the line before the current line of the buffer.
12726 let column = selection.start.to_point(&snapshot).column as usize;
12727 let line_start = selection.start - column;
12728 (line_start..line_start, Cow::Borrowed(to_insert))
12729 } else {
12730 let language = snapshot.language_at(selection.head());
12731 let range = selection.range();
12732 if let Some(language) = language
12733 && language.name() == "Markdown".into()
12734 {
12735 edit_for_markdown_paste(
12736 &snapshot,
12737 range,
12738 to_insert,
12739 url::Url::parse(to_insert).ok(),
12740 )
12741 } else {
12742 (range, Cow::Borrowed(to_insert))
12743 }
12744 };
12745
12746 edits.push((range, to_insert));
12747 original_indent_columns.push(original_indent_column);
12748 }
12749 drop(snapshot);
12750
12751 buffer.edit(
12752 edits,
12753 if auto_indent_on_paste {
12754 Some(AutoindentMode::Block {
12755 original_indent_columns,
12756 })
12757 } else {
12758 None
12759 },
12760 cx,
12761 );
12762 });
12763
12764 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12765 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12766 } else {
12767 let url = url::Url::parse(&clipboard_text).ok();
12768
12769 let auto_indent_mode = if !clipboard_text.is_empty() {
12770 Some(AutoindentMode::Block {
12771 original_indent_columns: Vec::new(),
12772 })
12773 } else {
12774 None
12775 };
12776
12777 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12778 let snapshot = buffer.snapshot(cx);
12779
12780 let anchors = old_selections
12781 .iter()
12782 .map(|s| {
12783 let anchor = snapshot.anchor_after(s.head());
12784 s.map(|_| anchor)
12785 })
12786 .collect::<Vec<_>>();
12787
12788 let mut edits = Vec::new();
12789
12790 for selection in old_selections.iter() {
12791 let language = snapshot.language_at(selection.head());
12792 let range = selection.range();
12793
12794 let (edit_range, edit_text) = if let Some(language) = language
12795 && language.name() == "Markdown".into()
12796 {
12797 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12798 } else {
12799 (range, clipboard_text.clone())
12800 };
12801
12802 edits.push((edit_range, edit_text));
12803 }
12804
12805 drop(snapshot);
12806 buffer.edit(edits, auto_indent_mode, cx);
12807
12808 anchors
12809 });
12810
12811 this.change_selections(Default::default(), window, cx, |s| {
12812 s.select_anchors(selection_anchors);
12813 });
12814 }
12815
12816 let trigger_in_words =
12817 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12818
12819 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12820 });
12821 }
12822
12823 pub fn diff_clipboard_with_selection(
12824 &mut self,
12825 _: &DiffClipboardWithSelection,
12826 window: &mut Window,
12827 cx: &mut Context<Self>,
12828 ) {
12829 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12830
12831 if selections.is_empty() {
12832 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12833 return;
12834 };
12835
12836 let clipboard_text = match cx.read_from_clipboard() {
12837 Some(item) => match item.entries().first() {
12838 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12839 _ => None,
12840 },
12841 None => None,
12842 };
12843
12844 let Some(clipboard_text) = clipboard_text else {
12845 log::warn!("Clipboard doesn't contain text.");
12846 return;
12847 };
12848
12849 window.dispatch_action(
12850 Box::new(DiffClipboardWithSelectionData {
12851 clipboard_text,
12852 editor: cx.entity(),
12853 }),
12854 cx,
12855 );
12856 }
12857
12858 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12859 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12860 if let Some(item) = cx.read_from_clipboard() {
12861 let entries = item.entries();
12862
12863 match entries.first() {
12864 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12865 // of all the pasted entries.
12866 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12867 .do_paste(
12868 clipboard_string.text(),
12869 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12870 true,
12871 window,
12872 cx,
12873 ),
12874 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12875 }
12876 }
12877 }
12878
12879 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12880 if self.read_only(cx) {
12881 return;
12882 }
12883
12884 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12885
12886 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12887 if let Some((selections, _)) =
12888 self.selection_history.transaction(transaction_id).cloned()
12889 {
12890 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12891 s.select_anchors(selections.to_vec());
12892 });
12893 } else {
12894 log::error!(
12895 "No entry in selection_history found for undo. \
12896 This may correspond to a bug where undo does not update the selection. \
12897 If this is occurring, please add details to \
12898 https://github.com/zed-industries/zed/issues/22692"
12899 );
12900 }
12901 self.request_autoscroll(Autoscroll::fit(), cx);
12902 self.unmark_text(window, cx);
12903 self.refresh_edit_prediction(true, false, window, cx);
12904 cx.emit(EditorEvent::Edited { transaction_id });
12905 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12906 }
12907 }
12908
12909 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12910 if self.read_only(cx) {
12911 return;
12912 }
12913
12914 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12915
12916 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12917 if let Some((_, Some(selections))) =
12918 self.selection_history.transaction(transaction_id).cloned()
12919 {
12920 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12921 s.select_anchors(selections.to_vec());
12922 });
12923 } else {
12924 log::error!(
12925 "No entry in selection_history found for redo. \
12926 This may correspond to a bug where undo does not update the selection. \
12927 If this is occurring, please add details to \
12928 https://github.com/zed-industries/zed/issues/22692"
12929 );
12930 }
12931 self.request_autoscroll(Autoscroll::fit(), cx);
12932 self.unmark_text(window, cx);
12933 self.refresh_edit_prediction(true, false, window, cx);
12934 cx.emit(EditorEvent::Edited { transaction_id });
12935 }
12936 }
12937
12938 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12939 self.buffer
12940 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12941 }
12942
12943 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12944 self.buffer
12945 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12946 }
12947
12948 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12949 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12950 self.change_selections(Default::default(), window, cx, |s| {
12951 s.move_with(|map, selection| {
12952 let cursor = if selection.is_empty() {
12953 movement::left(map, selection.start)
12954 } else {
12955 selection.start
12956 };
12957 selection.collapse_to(cursor, SelectionGoal::None);
12958 });
12959 })
12960 }
12961
12962 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12963 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12964 self.change_selections(Default::default(), window, cx, |s| {
12965 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12966 })
12967 }
12968
12969 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12970 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12971 self.change_selections(Default::default(), window, cx, |s| {
12972 s.move_with(|map, selection| {
12973 let cursor = if selection.is_empty() {
12974 movement::right(map, selection.end)
12975 } else {
12976 selection.end
12977 };
12978 selection.collapse_to(cursor, SelectionGoal::None)
12979 });
12980 })
12981 }
12982
12983 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12984 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12985 self.change_selections(Default::default(), window, cx, |s| {
12986 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12987 });
12988 }
12989
12990 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12991 if self.take_rename(true, window, cx).is_some() {
12992 return;
12993 }
12994
12995 if self.mode.is_single_line() {
12996 cx.propagate();
12997 return;
12998 }
12999
13000 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13001
13002 let text_layout_details = &self.text_layout_details(window);
13003 let selection_count = self.selections.count();
13004 let first_selection = self.selections.first_anchor();
13005
13006 self.change_selections(Default::default(), window, cx, |s| {
13007 s.move_with(|map, selection| {
13008 if !selection.is_empty() {
13009 selection.goal = SelectionGoal::None;
13010 }
13011 let (cursor, goal) = movement::up(
13012 map,
13013 selection.start,
13014 selection.goal,
13015 false,
13016 text_layout_details,
13017 );
13018 selection.collapse_to(cursor, goal);
13019 });
13020 });
13021
13022 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13023 {
13024 cx.propagate();
13025 }
13026 }
13027
13028 pub fn move_up_by_lines(
13029 &mut self,
13030 action: &MoveUpByLines,
13031 window: &mut Window,
13032 cx: &mut Context<Self>,
13033 ) {
13034 if self.take_rename(true, window, cx).is_some() {
13035 return;
13036 }
13037
13038 if self.mode.is_single_line() {
13039 cx.propagate();
13040 return;
13041 }
13042
13043 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13044
13045 let text_layout_details = &self.text_layout_details(window);
13046
13047 self.change_selections(Default::default(), window, cx, |s| {
13048 s.move_with(|map, selection| {
13049 if !selection.is_empty() {
13050 selection.goal = SelectionGoal::None;
13051 }
13052 let (cursor, goal) = movement::up_by_rows(
13053 map,
13054 selection.start,
13055 action.lines,
13056 selection.goal,
13057 false,
13058 text_layout_details,
13059 );
13060 selection.collapse_to(cursor, goal);
13061 });
13062 })
13063 }
13064
13065 pub fn move_down_by_lines(
13066 &mut self,
13067 action: &MoveDownByLines,
13068 window: &mut Window,
13069 cx: &mut Context<Self>,
13070 ) {
13071 if self.take_rename(true, window, cx).is_some() {
13072 return;
13073 }
13074
13075 if self.mode.is_single_line() {
13076 cx.propagate();
13077 return;
13078 }
13079
13080 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13081
13082 let text_layout_details = &self.text_layout_details(window);
13083
13084 self.change_selections(Default::default(), window, cx, |s| {
13085 s.move_with(|map, selection| {
13086 if !selection.is_empty() {
13087 selection.goal = SelectionGoal::None;
13088 }
13089 let (cursor, goal) = movement::down_by_rows(
13090 map,
13091 selection.start,
13092 action.lines,
13093 selection.goal,
13094 false,
13095 text_layout_details,
13096 );
13097 selection.collapse_to(cursor, goal);
13098 });
13099 })
13100 }
13101
13102 pub fn select_down_by_lines(
13103 &mut self,
13104 action: &SelectDownByLines,
13105 window: &mut Window,
13106 cx: &mut Context<Self>,
13107 ) {
13108 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13109 let text_layout_details = &self.text_layout_details(window);
13110 self.change_selections(Default::default(), window, cx, |s| {
13111 s.move_heads_with(|map, head, goal| {
13112 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13113 })
13114 })
13115 }
13116
13117 pub fn select_up_by_lines(
13118 &mut self,
13119 action: &SelectUpByLines,
13120 window: &mut Window,
13121 cx: &mut Context<Self>,
13122 ) {
13123 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13124 let text_layout_details = &self.text_layout_details(window);
13125 self.change_selections(Default::default(), window, cx, |s| {
13126 s.move_heads_with(|map, head, goal| {
13127 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13128 })
13129 })
13130 }
13131
13132 pub fn select_page_up(
13133 &mut self,
13134 _: &SelectPageUp,
13135 window: &mut Window,
13136 cx: &mut Context<Self>,
13137 ) {
13138 let Some(row_count) = self.visible_row_count() else {
13139 return;
13140 };
13141
13142 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13143
13144 let text_layout_details = &self.text_layout_details(window);
13145
13146 self.change_selections(Default::default(), window, cx, |s| {
13147 s.move_heads_with(|map, head, goal| {
13148 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13149 })
13150 })
13151 }
13152
13153 pub fn move_page_up(
13154 &mut self,
13155 action: &MovePageUp,
13156 window: &mut Window,
13157 cx: &mut Context<Self>,
13158 ) {
13159 if self.take_rename(true, window, cx).is_some() {
13160 return;
13161 }
13162
13163 if self
13164 .context_menu
13165 .borrow_mut()
13166 .as_mut()
13167 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13168 .unwrap_or(false)
13169 {
13170 return;
13171 }
13172
13173 if matches!(self.mode, EditorMode::SingleLine) {
13174 cx.propagate();
13175 return;
13176 }
13177
13178 let Some(row_count) = self.visible_row_count() else {
13179 return;
13180 };
13181
13182 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13183
13184 let effects = if action.center_cursor {
13185 SelectionEffects::scroll(Autoscroll::center())
13186 } else {
13187 SelectionEffects::default()
13188 };
13189
13190 let text_layout_details = &self.text_layout_details(window);
13191
13192 self.change_selections(effects, window, cx, |s| {
13193 s.move_with(|map, selection| {
13194 if !selection.is_empty() {
13195 selection.goal = SelectionGoal::None;
13196 }
13197 let (cursor, goal) = movement::up_by_rows(
13198 map,
13199 selection.end,
13200 row_count,
13201 selection.goal,
13202 false,
13203 text_layout_details,
13204 );
13205 selection.collapse_to(cursor, goal);
13206 });
13207 });
13208 }
13209
13210 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13211 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13212 let text_layout_details = &self.text_layout_details(window);
13213 self.change_selections(Default::default(), window, cx, |s| {
13214 s.move_heads_with(|map, head, goal| {
13215 movement::up(map, head, goal, false, text_layout_details)
13216 })
13217 })
13218 }
13219
13220 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13221 self.take_rename(true, window, cx);
13222
13223 if self.mode.is_single_line() {
13224 cx.propagate();
13225 return;
13226 }
13227
13228 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13229
13230 let text_layout_details = &self.text_layout_details(window);
13231 let selection_count = self.selections.count();
13232 let first_selection = self.selections.first_anchor();
13233
13234 self.change_selections(Default::default(), window, cx, |s| {
13235 s.move_with(|map, selection| {
13236 if !selection.is_empty() {
13237 selection.goal = SelectionGoal::None;
13238 }
13239 let (cursor, goal) = movement::down(
13240 map,
13241 selection.end,
13242 selection.goal,
13243 false,
13244 text_layout_details,
13245 );
13246 selection.collapse_to(cursor, goal);
13247 });
13248 });
13249
13250 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13251 {
13252 cx.propagate();
13253 }
13254 }
13255
13256 pub fn select_page_down(
13257 &mut self,
13258 _: &SelectPageDown,
13259 window: &mut Window,
13260 cx: &mut Context<Self>,
13261 ) {
13262 let Some(row_count) = self.visible_row_count() else {
13263 return;
13264 };
13265
13266 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13267
13268 let text_layout_details = &self.text_layout_details(window);
13269
13270 self.change_selections(Default::default(), window, cx, |s| {
13271 s.move_heads_with(|map, head, goal| {
13272 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13273 })
13274 })
13275 }
13276
13277 pub fn move_page_down(
13278 &mut self,
13279 action: &MovePageDown,
13280 window: &mut Window,
13281 cx: &mut Context<Self>,
13282 ) {
13283 if self.take_rename(true, window, cx).is_some() {
13284 return;
13285 }
13286
13287 if self
13288 .context_menu
13289 .borrow_mut()
13290 .as_mut()
13291 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13292 .unwrap_or(false)
13293 {
13294 return;
13295 }
13296
13297 if matches!(self.mode, EditorMode::SingleLine) {
13298 cx.propagate();
13299 return;
13300 }
13301
13302 let Some(row_count) = self.visible_row_count() else {
13303 return;
13304 };
13305
13306 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13307
13308 let effects = if action.center_cursor {
13309 SelectionEffects::scroll(Autoscroll::center())
13310 } else {
13311 SelectionEffects::default()
13312 };
13313
13314 let text_layout_details = &self.text_layout_details(window);
13315 self.change_selections(effects, window, cx, |s| {
13316 s.move_with(|map, selection| {
13317 if !selection.is_empty() {
13318 selection.goal = SelectionGoal::None;
13319 }
13320 let (cursor, goal) = movement::down_by_rows(
13321 map,
13322 selection.end,
13323 row_count,
13324 selection.goal,
13325 false,
13326 text_layout_details,
13327 );
13328 selection.collapse_to(cursor, goal);
13329 });
13330 });
13331 }
13332
13333 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13334 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13335 let text_layout_details = &self.text_layout_details(window);
13336 self.change_selections(Default::default(), window, cx, |s| {
13337 s.move_heads_with(|map, head, goal| {
13338 movement::down(map, head, goal, false, text_layout_details)
13339 })
13340 });
13341 }
13342
13343 pub fn context_menu_first(
13344 &mut self,
13345 _: &ContextMenuFirst,
13346 window: &mut Window,
13347 cx: &mut Context<Self>,
13348 ) {
13349 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13350 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13351 }
13352 }
13353
13354 pub fn context_menu_prev(
13355 &mut self,
13356 _: &ContextMenuPrevious,
13357 window: &mut Window,
13358 cx: &mut Context<Self>,
13359 ) {
13360 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13361 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13362 }
13363 }
13364
13365 pub fn context_menu_next(
13366 &mut self,
13367 _: &ContextMenuNext,
13368 window: &mut Window,
13369 cx: &mut Context<Self>,
13370 ) {
13371 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13372 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13373 }
13374 }
13375
13376 pub fn context_menu_last(
13377 &mut self,
13378 _: &ContextMenuLast,
13379 window: &mut Window,
13380 cx: &mut Context<Self>,
13381 ) {
13382 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13383 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13384 }
13385 }
13386
13387 pub fn signature_help_prev(
13388 &mut self,
13389 _: &SignatureHelpPrevious,
13390 _: &mut Window,
13391 cx: &mut Context<Self>,
13392 ) {
13393 if let Some(popover) = self.signature_help_state.popover_mut() {
13394 if popover.current_signature == 0 {
13395 popover.current_signature = popover.signatures.len() - 1;
13396 } else {
13397 popover.current_signature -= 1;
13398 }
13399 cx.notify();
13400 }
13401 }
13402
13403 pub fn signature_help_next(
13404 &mut self,
13405 _: &SignatureHelpNext,
13406 _: &mut Window,
13407 cx: &mut Context<Self>,
13408 ) {
13409 if let Some(popover) = self.signature_help_state.popover_mut() {
13410 if popover.current_signature + 1 == popover.signatures.len() {
13411 popover.current_signature = 0;
13412 } else {
13413 popover.current_signature += 1;
13414 }
13415 cx.notify();
13416 }
13417 }
13418
13419 pub fn move_to_previous_word_start(
13420 &mut self,
13421 _: &MoveToPreviousWordStart,
13422 window: &mut Window,
13423 cx: &mut Context<Self>,
13424 ) {
13425 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13426 self.change_selections(Default::default(), window, cx, |s| {
13427 s.move_cursors_with(|map, head, _| {
13428 (
13429 movement::previous_word_start(map, head),
13430 SelectionGoal::None,
13431 )
13432 });
13433 })
13434 }
13435
13436 pub fn move_to_previous_subword_start(
13437 &mut self,
13438 _: &MoveToPreviousSubwordStart,
13439 window: &mut Window,
13440 cx: &mut Context<Self>,
13441 ) {
13442 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13443 self.change_selections(Default::default(), window, cx, |s| {
13444 s.move_cursors_with(|map, head, _| {
13445 (
13446 movement::previous_subword_start(map, head),
13447 SelectionGoal::None,
13448 )
13449 });
13450 })
13451 }
13452
13453 pub fn select_to_previous_word_start(
13454 &mut self,
13455 _: &SelectToPreviousWordStart,
13456 window: &mut Window,
13457 cx: &mut Context<Self>,
13458 ) {
13459 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13460 self.change_selections(Default::default(), window, cx, |s| {
13461 s.move_heads_with(|map, head, _| {
13462 (
13463 movement::previous_word_start(map, head),
13464 SelectionGoal::None,
13465 )
13466 });
13467 })
13468 }
13469
13470 pub fn select_to_previous_subword_start(
13471 &mut self,
13472 _: &SelectToPreviousSubwordStart,
13473 window: &mut Window,
13474 cx: &mut Context<Self>,
13475 ) {
13476 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13477 self.change_selections(Default::default(), window, cx, |s| {
13478 s.move_heads_with(|map, head, _| {
13479 (
13480 movement::previous_subword_start(map, head),
13481 SelectionGoal::None,
13482 )
13483 });
13484 })
13485 }
13486
13487 pub fn delete_to_previous_word_start(
13488 &mut self,
13489 action: &DeleteToPreviousWordStart,
13490 window: &mut Window,
13491 cx: &mut Context<Self>,
13492 ) {
13493 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13494 self.transact(window, cx, |this, window, cx| {
13495 this.select_autoclose_pair(window, cx);
13496 this.change_selections(Default::default(), window, cx, |s| {
13497 s.move_with(|map, selection| {
13498 if selection.is_empty() {
13499 let mut cursor = if action.ignore_newlines {
13500 movement::previous_word_start(map, selection.head())
13501 } else {
13502 movement::previous_word_start_or_newline(map, selection.head())
13503 };
13504 cursor = movement::adjust_greedy_deletion(
13505 map,
13506 selection.head(),
13507 cursor,
13508 action.ignore_brackets,
13509 );
13510 selection.set_head(cursor, SelectionGoal::None);
13511 }
13512 });
13513 });
13514 this.insert("", window, cx);
13515 });
13516 }
13517
13518 pub fn delete_to_previous_subword_start(
13519 &mut self,
13520 _: &DeleteToPreviousSubwordStart,
13521 window: &mut Window,
13522 cx: &mut Context<Self>,
13523 ) {
13524 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13525 self.transact(window, cx, |this, window, cx| {
13526 this.select_autoclose_pair(window, cx);
13527 this.change_selections(Default::default(), window, cx, |s| {
13528 s.move_with(|map, selection| {
13529 if selection.is_empty() {
13530 let mut cursor = movement::previous_subword_start(map, selection.head());
13531 cursor =
13532 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13533 selection.set_head(cursor, SelectionGoal::None);
13534 }
13535 });
13536 });
13537 this.insert("", window, cx);
13538 });
13539 }
13540
13541 pub fn move_to_next_word_end(
13542 &mut self,
13543 _: &MoveToNextWordEnd,
13544 window: &mut Window,
13545 cx: &mut Context<Self>,
13546 ) {
13547 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13548 self.change_selections(Default::default(), window, cx, |s| {
13549 s.move_cursors_with(|map, head, _| {
13550 (movement::next_word_end(map, head), SelectionGoal::None)
13551 });
13552 })
13553 }
13554
13555 pub fn move_to_next_subword_end(
13556 &mut self,
13557 _: &MoveToNextSubwordEnd,
13558 window: &mut Window,
13559 cx: &mut Context<Self>,
13560 ) {
13561 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13562 self.change_selections(Default::default(), window, cx, |s| {
13563 s.move_cursors_with(|map, head, _| {
13564 (movement::next_subword_end(map, head), SelectionGoal::None)
13565 });
13566 })
13567 }
13568
13569 pub fn select_to_next_word_end(
13570 &mut self,
13571 _: &SelectToNextWordEnd,
13572 window: &mut Window,
13573 cx: &mut Context<Self>,
13574 ) {
13575 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13576 self.change_selections(Default::default(), window, cx, |s| {
13577 s.move_heads_with(|map, head, _| {
13578 (movement::next_word_end(map, head), SelectionGoal::None)
13579 });
13580 })
13581 }
13582
13583 pub fn select_to_next_subword_end(
13584 &mut self,
13585 _: &SelectToNextSubwordEnd,
13586 window: &mut Window,
13587 cx: &mut Context<Self>,
13588 ) {
13589 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13590 self.change_selections(Default::default(), window, cx, |s| {
13591 s.move_heads_with(|map, head, _| {
13592 (movement::next_subword_end(map, head), SelectionGoal::None)
13593 });
13594 })
13595 }
13596
13597 pub fn delete_to_next_word_end(
13598 &mut self,
13599 action: &DeleteToNextWordEnd,
13600 window: &mut Window,
13601 cx: &mut Context<Self>,
13602 ) {
13603 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13604 self.transact(window, cx, |this, window, cx| {
13605 this.change_selections(Default::default(), window, cx, |s| {
13606 s.move_with(|map, selection| {
13607 if selection.is_empty() {
13608 let mut cursor = if action.ignore_newlines {
13609 movement::next_word_end(map, selection.head())
13610 } else {
13611 movement::next_word_end_or_newline(map, selection.head())
13612 };
13613 cursor = movement::adjust_greedy_deletion(
13614 map,
13615 selection.head(),
13616 cursor,
13617 action.ignore_brackets,
13618 );
13619 selection.set_head(cursor, SelectionGoal::None);
13620 }
13621 });
13622 });
13623 this.insert("", window, cx);
13624 });
13625 }
13626
13627 pub fn delete_to_next_subword_end(
13628 &mut self,
13629 _: &DeleteToNextSubwordEnd,
13630 window: &mut Window,
13631 cx: &mut Context<Self>,
13632 ) {
13633 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13634 self.transact(window, cx, |this, window, cx| {
13635 this.change_selections(Default::default(), window, cx, |s| {
13636 s.move_with(|map, selection| {
13637 if selection.is_empty() {
13638 let mut cursor = movement::next_subword_end(map, selection.head());
13639 cursor =
13640 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13641 selection.set_head(cursor, SelectionGoal::None);
13642 }
13643 });
13644 });
13645 this.insert("", window, cx);
13646 });
13647 }
13648
13649 pub fn move_to_beginning_of_line(
13650 &mut self,
13651 action: &MoveToBeginningOfLine,
13652 window: &mut Window,
13653 cx: &mut Context<Self>,
13654 ) {
13655 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13656 self.change_selections(Default::default(), window, cx, |s| {
13657 s.move_cursors_with(|map, head, _| {
13658 (
13659 movement::indented_line_beginning(
13660 map,
13661 head,
13662 action.stop_at_soft_wraps,
13663 action.stop_at_indent,
13664 ),
13665 SelectionGoal::None,
13666 )
13667 });
13668 })
13669 }
13670
13671 pub fn select_to_beginning_of_line(
13672 &mut self,
13673 action: &SelectToBeginningOfLine,
13674 window: &mut Window,
13675 cx: &mut Context<Self>,
13676 ) {
13677 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13678 self.change_selections(Default::default(), window, cx, |s| {
13679 s.move_heads_with(|map, head, _| {
13680 (
13681 movement::indented_line_beginning(
13682 map,
13683 head,
13684 action.stop_at_soft_wraps,
13685 action.stop_at_indent,
13686 ),
13687 SelectionGoal::None,
13688 )
13689 });
13690 });
13691 }
13692
13693 pub fn delete_to_beginning_of_line(
13694 &mut self,
13695 action: &DeleteToBeginningOfLine,
13696 window: &mut Window,
13697 cx: &mut Context<Self>,
13698 ) {
13699 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13700 self.transact(window, cx, |this, window, cx| {
13701 this.change_selections(Default::default(), window, cx, |s| {
13702 s.move_with(|_, selection| {
13703 selection.reversed = true;
13704 });
13705 });
13706
13707 this.select_to_beginning_of_line(
13708 &SelectToBeginningOfLine {
13709 stop_at_soft_wraps: false,
13710 stop_at_indent: action.stop_at_indent,
13711 },
13712 window,
13713 cx,
13714 );
13715 this.backspace(&Backspace, window, cx);
13716 });
13717 }
13718
13719 pub fn move_to_end_of_line(
13720 &mut self,
13721 action: &MoveToEndOfLine,
13722 window: &mut Window,
13723 cx: &mut Context<Self>,
13724 ) {
13725 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13726 self.change_selections(Default::default(), window, cx, |s| {
13727 s.move_cursors_with(|map, head, _| {
13728 (
13729 movement::line_end(map, head, action.stop_at_soft_wraps),
13730 SelectionGoal::None,
13731 )
13732 });
13733 })
13734 }
13735
13736 pub fn select_to_end_of_line(
13737 &mut self,
13738 action: &SelectToEndOfLine,
13739 window: &mut Window,
13740 cx: &mut Context<Self>,
13741 ) {
13742 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13743 self.change_selections(Default::default(), window, cx, |s| {
13744 s.move_heads_with(|map, head, _| {
13745 (
13746 movement::line_end(map, head, action.stop_at_soft_wraps),
13747 SelectionGoal::None,
13748 )
13749 });
13750 })
13751 }
13752
13753 pub fn delete_to_end_of_line(
13754 &mut self,
13755 _: &DeleteToEndOfLine,
13756 window: &mut Window,
13757 cx: &mut Context<Self>,
13758 ) {
13759 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13760 self.transact(window, cx, |this, window, cx| {
13761 this.select_to_end_of_line(
13762 &SelectToEndOfLine {
13763 stop_at_soft_wraps: false,
13764 },
13765 window,
13766 cx,
13767 );
13768 this.delete(&Delete, window, cx);
13769 });
13770 }
13771
13772 pub fn cut_to_end_of_line(
13773 &mut self,
13774 action: &CutToEndOfLine,
13775 window: &mut Window,
13776 cx: &mut Context<Self>,
13777 ) {
13778 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13779 self.transact(window, cx, |this, window, cx| {
13780 this.select_to_end_of_line(
13781 &SelectToEndOfLine {
13782 stop_at_soft_wraps: false,
13783 },
13784 window,
13785 cx,
13786 );
13787 if !action.stop_at_newlines {
13788 this.change_selections(Default::default(), window, cx, |s| {
13789 s.move_with(|_, sel| {
13790 if sel.is_empty() {
13791 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13792 }
13793 });
13794 });
13795 }
13796 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13797 let item = this.cut_common(false, window, cx);
13798 cx.write_to_clipboard(item);
13799 });
13800 }
13801
13802 pub fn move_to_start_of_paragraph(
13803 &mut self,
13804 _: &MoveToStartOfParagraph,
13805 window: &mut Window,
13806 cx: &mut Context<Self>,
13807 ) {
13808 if matches!(self.mode, EditorMode::SingleLine) {
13809 cx.propagate();
13810 return;
13811 }
13812 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13813 self.change_selections(Default::default(), window, cx, |s| {
13814 s.move_with(|map, selection| {
13815 selection.collapse_to(
13816 movement::start_of_paragraph(map, selection.head(), 1),
13817 SelectionGoal::None,
13818 )
13819 });
13820 })
13821 }
13822
13823 pub fn move_to_end_of_paragraph(
13824 &mut self,
13825 _: &MoveToEndOfParagraph,
13826 window: &mut Window,
13827 cx: &mut Context<Self>,
13828 ) {
13829 if matches!(self.mode, EditorMode::SingleLine) {
13830 cx.propagate();
13831 return;
13832 }
13833 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13834 self.change_selections(Default::default(), window, cx, |s| {
13835 s.move_with(|map, selection| {
13836 selection.collapse_to(
13837 movement::end_of_paragraph(map, selection.head(), 1),
13838 SelectionGoal::None,
13839 )
13840 });
13841 })
13842 }
13843
13844 pub fn select_to_start_of_paragraph(
13845 &mut self,
13846 _: &SelectToStartOfParagraph,
13847 window: &mut Window,
13848 cx: &mut Context<Self>,
13849 ) {
13850 if matches!(self.mode, EditorMode::SingleLine) {
13851 cx.propagate();
13852 return;
13853 }
13854 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13855 self.change_selections(Default::default(), window, cx, |s| {
13856 s.move_heads_with(|map, head, _| {
13857 (
13858 movement::start_of_paragraph(map, head, 1),
13859 SelectionGoal::None,
13860 )
13861 });
13862 })
13863 }
13864
13865 pub fn select_to_end_of_paragraph(
13866 &mut self,
13867 _: &SelectToEndOfParagraph,
13868 window: &mut Window,
13869 cx: &mut Context<Self>,
13870 ) {
13871 if matches!(self.mode, EditorMode::SingleLine) {
13872 cx.propagate();
13873 return;
13874 }
13875 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13876 self.change_selections(Default::default(), window, cx, |s| {
13877 s.move_heads_with(|map, head, _| {
13878 (
13879 movement::end_of_paragraph(map, head, 1),
13880 SelectionGoal::None,
13881 )
13882 });
13883 })
13884 }
13885
13886 pub fn move_to_start_of_excerpt(
13887 &mut self,
13888 _: &MoveToStartOfExcerpt,
13889 window: &mut Window,
13890 cx: &mut Context<Self>,
13891 ) {
13892 if matches!(self.mode, EditorMode::SingleLine) {
13893 cx.propagate();
13894 return;
13895 }
13896 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13897 self.change_selections(Default::default(), window, cx, |s| {
13898 s.move_with(|map, selection| {
13899 selection.collapse_to(
13900 movement::start_of_excerpt(
13901 map,
13902 selection.head(),
13903 workspace::searchable::Direction::Prev,
13904 ),
13905 SelectionGoal::None,
13906 )
13907 });
13908 })
13909 }
13910
13911 pub fn move_to_start_of_next_excerpt(
13912 &mut self,
13913 _: &MoveToStartOfNextExcerpt,
13914 window: &mut Window,
13915 cx: &mut Context<Self>,
13916 ) {
13917 if matches!(self.mode, EditorMode::SingleLine) {
13918 cx.propagate();
13919 return;
13920 }
13921
13922 self.change_selections(Default::default(), window, cx, |s| {
13923 s.move_with(|map, selection| {
13924 selection.collapse_to(
13925 movement::start_of_excerpt(
13926 map,
13927 selection.head(),
13928 workspace::searchable::Direction::Next,
13929 ),
13930 SelectionGoal::None,
13931 )
13932 });
13933 })
13934 }
13935
13936 pub fn move_to_end_of_excerpt(
13937 &mut self,
13938 _: &MoveToEndOfExcerpt,
13939 window: &mut Window,
13940 cx: &mut Context<Self>,
13941 ) {
13942 if matches!(self.mode, EditorMode::SingleLine) {
13943 cx.propagate();
13944 return;
13945 }
13946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13947 self.change_selections(Default::default(), window, cx, |s| {
13948 s.move_with(|map, selection| {
13949 selection.collapse_to(
13950 movement::end_of_excerpt(
13951 map,
13952 selection.head(),
13953 workspace::searchable::Direction::Next,
13954 ),
13955 SelectionGoal::None,
13956 )
13957 });
13958 })
13959 }
13960
13961 pub fn move_to_end_of_previous_excerpt(
13962 &mut self,
13963 _: &MoveToEndOfPreviousExcerpt,
13964 window: &mut Window,
13965 cx: &mut Context<Self>,
13966 ) {
13967 if matches!(self.mode, EditorMode::SingleLine) {
13968 cx.propagate();
13969 return;
13970 }
13971 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13972 self.change_selections(Default::default(), window, cx, |s| {
13973 s.move_with(|map, selection| {
13974 selection.collapse_to(
13975 movement::end_of_excerpt(
13976 map,
13977 selection.head(),
13978 workspace::searchable::Direction::Prev,
13979 ),
13980 SelectionGoal::None,
13981 )
13982 });
13983 })
13984 }
13985
13986 pub fn select_to_start_of_excerpt(
13987 &mut self,
13988 _: &SelectToStartOfExcerpt,
13989 window: &mut Window,
13990 cx: &mut Context<Self>,
13991 ) {
13992 if matches!(self.mode, EditorMode::SingleLine) {
13993 cx.propagate();
13994 return;
13995 }
13996 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13997 self.change_selections(Default::default(), window, cx, |s| {
13998 s.move_heads_with(|map, head, _| {
13999 (
14000 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14001 SelectionGoal::None,
14002 )
14003 });
14004 })
14005 }
14006
14007 pub fn select_to_start_of_next_excerpt(
14008 &mut self,
14009 _: &SelectToStartOfNextExcerpt,
14010 window: &mut Window,
14011 cx: &mut Context<Self>,
14012 ) {
14013 if matches!(self.mode, EditorMode::SingleLine) {
14014 cx.propagate();
14015 return;
14016 }
14017 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14018 self.change_selections(Default::default(), window, cx, |s| {
14019 s.move_heads_with(|map, head, _| {
14020 (
14021 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14022 SelectionGoal::None,
14023 )
14024 });
14025 })
14026 }
14027
14028 pub fn select_to_end_of_excerpt(
14029 &mut self,
14030 _: &SelectToEndOfExcerpt,
14031 window: &mut Window,
14032 cx: &mut Context<Self>,
14033 ) {
14034 if matches!(self.mode, EditorMode::SingleLine) {
14035 cx.propagate();
14036 return;
14037 }
14038 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14039 self.change_selections(Default::default(), window, cx, |s| {
14040 s.move_heads_with(|map, head, _| {
14041 (
14042 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14043 SelectionGoal::None,
14044 )
14045 });
14046 })
14047 }
14048
14049 pub fn select_to_end_of_previous_excerpt(
14050 &mut self,
14051 _: &SelectToEndOfPreviousExcerpt,
14052 window: &mut Window,
14053 cx: &mut Context<Self>,
14054 ) {
14055 if matches!(self.mode, EditorMode::SingleLine) {
14056 cx.propagate();
14057 return;
14058 }
14059 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14060 self.change_selections(Default::default(), window, cx, |s| {
14061 s.move_heads_with(|map, head, _| {
14062 (
14063 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14064 SelectionGoal::None,
14065 )
14066 });
14067 })
14068 }
14069
14070 pub fn move_to_beginning(
14071 &mut self,
14072 _: &MoveToBeginning,
14073 window: &mut Window,
14074 cx: &mut Context<Self>,
14075 ) {
14076 if matches!(self.mode, EditorMode::SingleLine) {
14077 cx.propagate();
14078 return;
14079 }
14080 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14081 self.change_selections(Default::default(), window, cx, |s| {
14082 s.select_ranges(vec![0..0]);
14083 });
14084 }
14085
14086 pub fn select_to_beginning(
14087 &mut self,
14088 _: &SelectToBeginning,
14089 window: &mut Window,
14090 cx: &mut Context<Self>,
14091 ) {
14092 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14093 selection.set_head(Point::zero(), SelectionGoal::None);
14094 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14095 self.change_selections(Default::default(), window, cx, |s| {
14096 s.select(vec![selection]);
14097 });
14098 }
14099
14100 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14101 if matches!(self.mode, EditorMode::SingleLine) {
14102 cx.propagate();
14103 return;
14104 }
14105 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14106 let cursor = self.buffer.read(cx).read(cx).len();
14107 self.change_selections(Default::default(), window, cx, |s| {
14108 s.select_ranges(vec![cursor..cursor])
14109 });
14110 }
14111
14112 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14113 self.nav_history = nav_history;
14114 }
14115
14116 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14117 self.nav_history.as_ref()
14118 }
14119
14120 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14121 self.push_to_nav_history(
14122 self.selections.newest_anchor().head(),
14123 None,
14124 false,
14125 true,
14126 cx,
14127 );
14128 }
14129
14130 fn push_to_nav_history(
14131 &mut self,
14132 cursor_anchor: Anchor,
14133 new_position: Option<Point>,
14134 is_deactivate: bool,
14135 always: bool,
14136 cx: &mut Context<Self>,
14137 ) {
14138 if let Some(nav_history) = self.nav_history.as_mut() {
14139 let buffer = self.buffer.read(cx).read(cx);
14140 let cursor_position = cursor_anchor.to_point(&buffer);
14141 let scroll_state = self.scroll_manager.anchor();
14142 let scroll_top_row = scroll_state.top_row(&buffer);
14143 drop(buffer);
14144
14145 if let Some(new_position) = new_position {
14146 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14147 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14148 return;
14149 }
14150 }
14151
14152 nav_history.push(
14153 Some(NavigationData {
14154 cursor_anchor,
14155 cursor_position,
14156 scroll_anchor: scroll_state,
14157 scroll_top_row,
14158 }),
14159 cx,
14160 );
14161 cx.emit(EditorEvent::PushedToNavHistory {
14162 anchor: cursor_anchor,
14163 is_deactivate,
14164 })
14165 }
14166 }
14167
14168 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14169 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14170 let buffer = self.buffer.read(cx).snapshot(cx);
14171 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14172 selection.set_head(buffer.len(), SelectionGoal::None);
14173 self.change_selections(Default::default(), window, cx, |s| {
14174 s.select(vec![selection]);
14175 });
14176 }
14177
14178 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14179 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14180 let end = self.buffer.read(cx).read(cx).len();
14181 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14182 s.select_ranges(vec![0..end]);
14183 });
14184 }
14185
14186 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14187 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14188 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14189 let mut selections = self.selections.all::<Point>(&display_map);
14190 let max_point = display_map.buffer_snapshot().max_point();
14191 for selection in &mut selections {
14192 let rows = selection.spanned_rows(true, &display_map);
14193 selection.start = Point::new(rows.start.0, 0);
14194 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14195 selection.reversed = false;
14196 }
14197 self.change_selections(Default::default(), window, cx, |s| {
14198 s.select(selections);
14199 });
14200 }
14201
14202 pub fn split_selection_into_lines(
14203 &mut self,
14204 action: &SplitSelectionIntoLines,
14205 window: &mut Window,
14206 cx: &mut Context<Self>,
14207 ) {
14208 let selections = self
14209 .selections
14210 .all::<Point>(&self.display_snapshot(cx))
14211 .into_iter()
14212 .map(|selection| selection.start..selection.end)
14213 .collect::<Vec<_>>();
14214 self.unfold_ranges(&selections, true, true, cx);
14215
14216 let mut new_selection_ranges = Vec::new();
14217 {
14218 let buffer = self.buffer.read(cx).read(cx);
14219 for selection in selections {
14220 for row in selection.start.row..selection.end.row {
14221 let line_start = Point::new(row, 0);
14222 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14223
14224 if action.keep_selections {
14225 // Keep the selection range for each line
14226 let selection_start = if row == selection.start.row {
14227 selection.start
14228 } else {
14229 line_start
14230 };
14231 new_selection_ranges.push(selection_start..line_end);
14232 } else {
14233 // Collapse to cursor at end of line
14234 new_selection_ranges.push(line_end..line_end);
14235 }
14236 }
14237
14238 let is_multiline_selection = selection.start.row != selection.end.row;
14239 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14240 // so this action feels more ergonomic when paired with other selection operations
14241 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14242 if !should_skip_last {
14243 if action.keep_selections {
14244 if is_multiline_selection {
14245 let line_start = Point::new(selection.end.row, 0);
14246 new_selection_ranges.push(line_start..selection.end);
14247 } else {
14248 new_selection_ranges.push(selection.start..selection.end);
14249 }
14250 } else {
14251 new_selection_ranges.push(selection.end..selection.end);
14252 }
14253 }
14254 }
14255 }
14256 self.change_selections(Default::default(), window, cx, |s| {
14257 s.select_ranges(new_selection_ranges);
14258 });
14259 }
14260
14261 pub fn add_selection_above(
14262 &mut self,
14263 action: &AddSelectionAbove,
14264 window: &mut Window,
14265 cx: &mut Context<Self>,
14266 ) {
14267 self.add_selection(true, action.skip_soft_wrap, window, cx);
14268 }
14269
14270 pub fn add_selection_below(
14271 &mut self,
14272 action: &AddSelectionBelow,
14273 window: &mut Window,
14274 cx: &mut Context<Self>,
14275 ) {
14276 self.add_selection(false, action.skip_soft_wrap, window, cx);
14277 }
14278
14279 fn add_selection(
14280 &mut self,
14281 above: bool,
14282 skip_soft_wrap: bool,
14283 window: &mut Window,
14284 cx: &mut Context<Self>,
14285 ) {
14286 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14287
14288 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14289 let all_selections = self.selections.all::<Point>(&display_map);
14290 let text_layout_details = self.text_layout_details(window);
14291
14292 let (mut columnar_selections, new_selections_to_columnarize) = {
14293 if let Some(state) = self.add_selections_state.as_ref() {
14294 let columnar_selection_ids: HashSet<_> = state
14295 .groups
14296 .iter()
14297 .flat_map(|group| group.stack.iter())
14298 .copied()
14299 .collect();
14300
14301 all_selections
14302 .into_iter()
14303 .partition(|s| columnar_selection_ids.contains(&s.id))
14304 } else {
14305 (Vec::new(), all_selections)
14306 }
14307 };
14308
14309 let mut state = self
14310 .add_selections_state
14311 .take()
14312 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14313
14314 for selection in new_selections_to_columnarize {
14315 let range = selection.display_range(&display_map).sorted();
14316 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14317 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14318 let positions = start_x.min(end_x)..start_x.max(end_x);
14319 let mut stack = Vec::new();
14320 for row in range.start.row().0..=range.end.row().0 {
14321 if let Some(selection) = self.selections.build_columnar_selection(
14322 &display_map,
14323 DisplayRow(row),
14324 &positions,
14325 selection.reversed,
14326 &text_layout_details,
14327 ) {
14328 stack.push(selection.id);
14329 columnar_selections.push(selection);
14330 }
14331 }
14332 if !stack.is_empty() {
14333 if above {
14334 stack.reverse();
14335 }
14336 state.groups.push(AddSelectionsGroup { above, stack });
14337 }
14338 }
14339
14340 let mut final_selections = Vec::new();
14341 let end_row = if above {
14342 DisplayRow(0)
14343 } else {
14344 display_map.max_point().row()
14345 };
14346
14347 let mut last_added_item_per_group = HashMap::default();
14348 for group in state.groups.iter_mut() {
14349 if let Some(last_id) = group.stack.last() {
14350 last_added_item_per_group.insert(*last_id, group);
14351 }
14352 }
14353
14354 for selection in columnar_selections {
14355 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14356 if above == group.above {
14357 let range = selection.display_range(&display_map).sorted();
14358 debug_assert_eq!(range.start.row(), range.end.row());
14359 let mut row = range.start.row();
14360 let positions =
14361 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14362 Pixels::from(start)..Pixels::from(end)
14363 } else {
14364 let start_x =
14365 display_map.x_for_display_point(range.start, &text_layout_details);
14366 let end_x =
14367 display_map.x_for_display_point(range.end, &text_layout_details);
14368 start_x.min(end_x)..start_x.max(end_x)
14369 };
14370
14371 let mut maybe_new_selection = None;
14372 let direction = if above { -1 } else { 1 };
14373
14374 while row != end_row {
14375 if skip_soft_wrap {
14376 row = display_map
14377 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14378 .row();
14379 } else if above {
14380 row.0 -= 1;
14381 } else {
14382 row.0 += 1;
14383 }
14384
14385 if let Some(new_selection) = self.selections.build_columnar_selection(
14386 &display_map,
14387 row,
14388 &positions,
14389 selection.reversed,
14390 &text_layout_details,
14391 ) {
14392 maybe_new_selection = Some(new_selection);
14393 break;
14394 }
14395 }
14396
14397 if let Some(new_selection) = maybe_new_selection {
14398 group.stack.push(new_selection.id);
14399 if above {
14400 final_selections.push(new_selection);
14401 final_selections.push(selection);
14402 } else {
14403 final_selections.push(selection);
14404 final_selections.push(new_selection);
14405 }
14406 } else {
14407 final_selections.push(selection);
14408 }
14409 } else {
14410 group.stack.pop();
14411 }
14412 } else {
14413 final_selections.push(selection);
14414 }
14415 }
14416
14417 self.change_selections(Default::default(), window, cx, |s| {
14418 s.select(final_selections);
14419 });
14420
14421 let final_selection_ids: HashSet<_> = self
14422 .selections
14423 .all::<Point>(&display_map)
14424 .iter()
14425 .map(|s| s.id)
14426 .collect();
14427 state.groups.retain_mut(|group| {
14428 // selections might get merged above so we remove invalid items from stacks
14429 group.stack.retain(|id| final_selection_ids.contains(id));
14430
14431 // single selection in stack can be treated as initial state
14432 group.stack.len() > 1
14433 });
14434
14435 if !state.groups.is_empty() {
14436 self.add_selections_state = Some(state);
14437 }
14438 }
14439
14440 fn select_match_ranges(
14441 &mut self,
14442 range: Range<usize>,
14443 reversed: bool,
14444 replace_newest: bool,
14445 auto_scroll: Option<Autoscroll>,
14446 window: &mut Window,
14447 cx: &mut Context<Editor>,
14448 ) {
14449 self.unfold_ranges(
14450 std::slice::from_ref(&range),
14451 false,
14452 auto_scroll.is_some(),
14453 cx,
14454 );
14455 let effects = if let Some(scroll) = auto_scroll {
14456 SelectionEffects::scroll(scroll)
14457 } else {
14458 SelectionEffects::no_scroll()
14459 };
14460 self.change_selections(effects, window, cx, |s| {
14461 if replace_newest {
14462 s.delete(s.newest_anchor().id);
14463 }
14464 if reversed {
14465 s.insert_range(range.end..range.start);
14466 } else {
14467 s.insert_range(range);
14468 }
14469 });
14470 }
14471
14472 pub fn select_next_match_internal(
14473 &mut self,
14474 display_map: &DisplaySnapshot,
14475 replace_newest: bool,
14476 autoscroll: Option<Autoscroll>,
14477 window: &mut Window,
14478 cx: &mut Context<Self>,
14479 ) -> Result<()> {
14480 let buffer = display_map.buffer_snapshot();
14481 let mut selections = self.selections.all::<usize>(&display_map);
14482 if let Some(mut select_next_state) = self.select_next_state.take() {
14483 let query = &select_next_state.query;
14484 if !select_next_state.done {
14485 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14486 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14487 let mut next_selected_range = None;
14488
14489 let bytes_after_last_selection =
14490 buffer.bytes_in_range(last_selection.end..buffer.len());
14491 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14492 let query_matches = query
14493 .stream_find_iter(bytes_after_last_selection)
14494 .map(|result| (last_selection.end, result))
14495 .chain(
14496 query
14497 .stream_find_iter(bytes_before_first_selection)
14498 .map(|result| (0, result)),
14499 );
14500
14501 for (start_offset, query_match) in query_matches {
14502 let query_match = query_match.unwrap(); // can only fail due to I/O
14503 let offset_range =
14504 start_offset + query_match.start()..start_offset + query_match.end();
14505
14506 if !select_next_state.wordwise
14507 || (!buffer.is_inside_word(offset_range.start, None)
14508 && !buffer.is_inside_word(offset_range.end, None))
14509 {
14510 let idx = selections
14511 .partition_point(|selection| selection.end <= offset_range.start);
14512 let overlaps = selections
14513 .get(idx)
14514 .map_or(false, |selection| selection.start < offset_range.end);
14515
14516 if !overlaps {
14517 next_selected_range = Some(offset_range);
14518 break;
14519 }
14520 }
14521 }
14522
14523 if let Some(next_selected_range) = next_selected_range {
14524 self.select_match_ranges(
14525 next_selected_range,
14526 last_selection.reversed,
14527 replace_newest,
14528 autoscroll,
14529 window,
14530 cx,
14531 );
14532 } else {
14533 select_next_state.done = true;
14534 }
14535 }
14536
14537 self.select_next_state = Some(select_next_state);
14538 } else {
14539 let mut only_carets = true;
14540 let mut same_text_selected = true;
14541 let mut selected_text = None;
14542
14543 let mut selections_iter = selections.iter().peekable();
14544 while let Some(selection) = selections_iter.next() {
14545 if selection.start != selection.end {
14546 only_carets = false;
14547 }
14548
14549 if same_text_selected {
14550 if selected_text.is_none() {
14551 selected_text =
14552 Some(buffer.text_for_range(selection.range()).collect::<String>());
14553 }
14554
14555 if let Some(next_selection) = selections_iter.peek() {
14556 if next_selection.range().len() == selection.range().len() {
14557 let next_selected_text = buffer
14558 .text_for_range(next_selection.range())
14559 .collect::<String>();
14560 if Some(next_selected_text) != selected_text {
14561 same_text_selected = false;
14562 selected_text = None;
14563 }
14564 } else {
14565 same_text_selected = false;
14566 selected_text = None;
14567 }
14568 }
14569 }
14570 }
14571
14572 if only_carets {
14573 for selection in &mut selections {
14574 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14575 selection.start = word_range.start;
14576 selection.end = word_range.end;
14577 selection.goal = SelectionGoal::None;
14578 selection.reversed = false;
14579 self.select_match_ranges(
14580 selection.start..selection.end,
14581 selection.reversed,
14582 replace_newest,
14583 autoscroll,
14584 window,
14585 cx,
14586 );
14587 }
14588
14589 if selections.len() == 1 {
14590 let selection = selections
14591 .last()
14592 .expect("ensured that there's only one selection");
14593 let query = buffer
14594 .text_for_range(selection.start..selection.end)
14595 .collect::<String>();
14596 let is_empty = query.is_empty();
14597 let select_state = SelectNextState {
14598 query: AhoCorasick::new(&[query])?,
14599 wordwise: true,
14600 done: is_empty,
14601 };
14602 self.select_next_state = Some(select_state);
14603 } else {
14604 self.select_next_state = None;
14605 }
14606 } else if let Some(selected_text) = selected_text {
14607 self.select_next_state = Some(SelectNextState {
14608 query: AhoCorasick::new(&[selected_text])?,
14609 wordwise: false,
14610 done: false,
14611 });
14612 self.select_next_match_internal(
14613 display_map,
14614 replace_newest,
14615 autoscroll,
14616 window,
14617 cx,
14618 )?;
14619 }
14620 }
14621 Ok(())
14622 }
14623
14624 pub fn select_all_matches(
14625 &mut self,
14626 _action: &SelectAllMatches,
14627 window: &mut Window,
14628 cx: &mut Context<Self>,
14629 ) -> Result<()> {
14630 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14631
14632 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14633
14634 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14635 let Some(select_next_state) = self.select_next_state.as_mut() else {
14636 return Ok(());
14637 };
14638 if select_next_state.done {
14639 return Ok(());
14640 }
14641
14642 let mut new_selections = Vec::new();
14643
14644 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14645 let buffer = display_map.buffer_snapshot();
14646 let query_matches = select_next_state
14647 .query
14648 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14649
14650 for query_match in query_matches.into_iter() {
14651 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14652 let offset_range = if reversed {
14653 query_match.end()..query_match.start()
14654 } else {
14655 query_match.start()..query_match.end()
14656 };
14657
14658 if !select_next_state.wordwise
14659 || (!buffer.is_inside_word(offset_range.start, None)
14660 && !buffer.is_inside_word(offset_range.end, None))
14661 {
14662 new_selections.push(offset_range.start..offset_range.end);
14663 }
14664 }
14665
14666 select_next_state.done = true;
14667
14668 if new_selections.is_empty() {
14669 log::error!("bug: new_selections is empty in select_all_matches");
14670 return Ok(());
14671 }
14672
14673 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14674 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14675 selections.select_ranges(new_selections)
14676 });
14677
14678 Ok(())
14679 }
14680
14681 pub fn select_next(
14682 &mut self,
14683 action: &SelectNext,
14684 window: &mut Window,
14685 cx: &mut Context<Self>,
14686 ) -> Result<()> {
14687 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14688 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14689 self.select_next_match_internal(
14690 &display_map,
14691 action.replace_newest,
14692 Some(Autoscroll::newest()),
14693 window,
14694 cx,
14695 )?;
14696 Ok(())
14697 }
14698
14699 pub fn select_previous(
14700 &mut self,
14701 action: &SelectPrevious,
14702 window: &mut Window,
14703 cx: &mut Context<Self>,
14704 ) -> Result<()> {
14705 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14706 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14707 let buffer = display_map.buffer_snapshot();
14708 let mut selections = self.selections.all::<usize>(&display_map);
14709 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14710 let query = &select_prev_state.query;
14711 if !select_prev_state.done {
14712 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14713 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14714 let mut next_selected_range = None;
14715 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14716 let bytes_before_last_selection =
14717 buffer.reversed_bytes_in_range(0..last_selection.start);
14718 let bytes_after_first_selection =
14719 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14720 let query_matches = query
14721 .stream_find_iter(bytes_before_last_selection)
14722 .map(|result| (last_selection.start, result))
14723 .chain(
14724 query
14725 .stream_find_iter(bytes_after_first_selection)
14726 .map(|result| (buffer.len(), result)),
14727 );
14728 for (end_offset, query_match) in query_matches {
14729 let query_match = query_match.unwrap(); // can only fail due to I/O
14730 let offset_range =
14731 end_offset - query_match.end()..end_offset - query_match.start();
14732
14733 if !select_prev_state.wordwise
14734 || (!buffer.is_inside_word(offset_range.start, None)
14735 && !buffer.is_inside_word(offset_range.end, None))
14736 {
14737 next_selected_range = Some(offset_range);
14738 break;
14739 }
14740 }
14741
14742 if let Some(next_selected_range) = next_selected_range {
14743 self.select_match_ranges(
14744 next_selected_range,
14745 last_selection.reversed,
14746 action.replace_newest,
14747 Some(Autoscroll::newest()),
14748 window,
14749 cx,
14750 );
14751 } else {
14752 select_prev_state.done = true;
14753 }
14754 }
14755
14756 self.select_prev_state = Some(select_prev_state);
14757 } else {
14758 let mut only_carets = true;
14759 let mut same_text_selected = true;
14760 let mut selected_text = None;
14761
14762 let mut selections_iter = selections.iter().peekable();
14763 while let Some(selection) = selections_iter.next() {
14764 if selection.start != selection.end {
14765 only_carets = false;
14766 }
14767
14768 if same_text_selected {
14769 if selected_text.is_none() {
14770 selected_text =
14771 Some(buffer.text_for_range(selection.range()).collect::<String>());
14772 }
14773
14774 if let Some(next_selection) = selections_iter.peek() {
14775 if next_selection.range().len() == selection.range().len() {
14776 let next_selected_text = buffer
14777 .text_for_range(next_selection.range())
14778 .collect::<String>();
14779 if Some(next_selected_text) != selected_text {
14780 same_text_selected = false;
14781 selected_text = None;
14782 }
14783 } else {
14784 same_text_selected = false;
14785 selected_text = None;
14786 }
14787 }
14788 }
14789 }
14790
14791 if only_carets {
14792 for selection in &mut selections {
14793 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14794 selection.start = word_range.start;
14795 selection.end = word_range.end;
14796 selection.goal = SelectionGoal::None;
14797 selection.reversed = false;
14798 self.select_match_ranges(
14799 selection.start..selection.end,
14800 selection.reversed,
14801 action.replace_newest,
14802 Some(Autoscroll::newest()),
14803 window,
14804 cx,
14805 );
14806 }
14807 if selections.len() == 1 {
14808 let selection = selections
14809 .last()
14810 .expect("ensured that there's only one selection");
14811 let query = buffer
14812 .text_for_range(selection.start..selection.end)
14813 .collect::<String>();
14814 let is_empty = query.is_empty();
14815 let select_state = SelectNextState {
14816 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14817 wordwise: true,
14818 done: is_empty,
14819 };
14820 self.select_prev_state = Some(select_state);
14821 } else {
14822 self.select_prev_state = None;
14823 }
14824 } else if let Some(selected_text) = selected_text {
14825 self.select_prev_state = Some(SelectNextState {
14826 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14827 wordwise: false,
14828 done: false,
14829 });
14830 self.select_previous(action, window, cx)?;
14831 }
14832 }
14833 Ok(())
14834 }
14835
14836 pub fn find_next_match(
14837 &mut self,
14838 _: &FindNextMatch,
14839 window: &mut Window,
14840 cx: &mut Context<Self>,
14841 ) -> Result<()> {
14842 let selections = self.selections.disjoint_anchors_arc();
14843 match selections.first() {
14844 Some(first) if selections.len() >= 2 => {
14845 self.change_selections(Default::default(), window, cx, |s| {
14846 s.select_ranges([first.range()]);
14847 });
14848 }
14849 _ => self.select_next(
14850 &SelectNext {
14851 replace_newest: true,
14852 },
14853 window,
14854 cx,
14855 )?,
14856 }
14857 Ok(())
14858 }
14859
14860 pub fn find_previous_match(
14861 &mut self,
14862 _: &FindPreviousMatch,
14863 window: &mut Window,
14864 cx: &mut Context<Self>,
14865 ) -> Result<()> {
14866 let selections = self.selections.disjoint_anchors_arc();
14867 match selections.last() {
14868 Some(last) if selections.len() >= 2 => {
14869 self.change_selections(Default::default(), window, cx, |s| {
14870 s.select_ranges([last.range()]);
14871 });
14872 }
14873 _ => self.select_previous(
14874 &SelectPrevious {
14875 replace_newest: true,
14876 },
14877 window,
14878 cx,
14879 )?,
14880 }
14881 Ok(())
14882 }
14883
14884 pub fn toggle_comments(
14885 &mut self,
14886 action: &ToggleComments,
14887 window: &mut Window,
14888 cx: &mut Context<Self>,
14889 ) {
14890 if self.read_only(cx) {
14891 return;
14892 }
14893 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14894 let text_layout_details = &self.text_layout_details(window);
14895 self.transact(window, cx, |this, window, cx| {
14896 let mut selections = this
14897 .selections
14898 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14899 let mut edits = Vec::new();
14900 let mut selection_edit_ranges = Vec::new();
14901 let mut last_toggled_row = None;
14902 let snapshot = this.buffer.read(cx).read(cx);
14903 let empty_str: Arc<str> = Arc::default();
14904 let mut suffixes_inserted = Vec::new();
14905 let ignore_indent = action.ignore_indent;
14906
14907 fn comment_prefix_range(
14908 snapshot: &MultiBufferSnapshot,
14909 row: MultiBufferRow,
14910 comment_prefix: &str,
14911 comment_prefix_whitespace: &str,
14912 ignore_indent: bool,
14913 ) -> Range<Point> {
14914 let indent_size = if ignore_indent {
14915 0
14916 } else {
14917 snapshot.indent_size_for_line(row).len
14918 };
14919
14920 let start = Point::new(row.0, indent_size);
14921
14922 let mut line_bytes = snapshot
14923 .bytes_in_range(start..snapshot.max_point())
14924 .flatten()
14925 .copied();
14926
14927 // If this line currently begins with the line comment prefix, then record
14928 // the range containing the prefix.
14929 if line_bytes
14930 .by_ref()
14931 .take(comment_prefix.len())
14932 .eq(comment_prefix.bytes())
14933 {
14934 // Include any whitespace that matches the comment prefix.
14935 let matching_whitespace_len = line_bytes
14936 .zip(comment_prefix_whitespace.bytes())
14937 .take_while(|(a, b)| a == b)
14938 .count() as u32;
14939 let end = Point::new(
14940 start.row,
14941 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14942 );
14943 start..end
14944 } else {
14945 start..start
14946 }
14947 }
14948
14949 fn comment_suffix_range(
14950 snapshot: &MultiBufferSnapshot,
14951 row: MultiBufferRow,
14952 comment_suffix: &str,
14953 comment_suffix_has_leading_space: bool,
14954 ) -> Range<Point> {
14955 let end = Point::new(row.0, snapshot.line_len(row));
14956 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14957
14958 let mut line_end_bytes = snapshot
14959 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14960 .flatten()
14961 .copied();
14962
14963 let leading_space_len = if suffix_start_column > 0
14964 && line_end_bytes.next() == Some(b' ')
14965 && comment_suffix_has_leading_space
14966 {
14967 1
14968 } else {
14969 0
14970 };
14971
14972 // If this line currently begins with the line comment prefix, then record
14973 // the range containing the prefix.
14974 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14975 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14976 start..end
14977 } else {
14978 end..end
14979 }
14980 }
14981
14982 // TODO: Handle selections that cross excerpts
14983 for selection in &mut selections {
14984 let start_column = snapshot
14985 .indent_size_for_line(MultiBufferRow(selection.start.row))
14986 .len;
14987 let language = if let Some(language) =
14988 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14989 {
14990 language
14991 } else {
14992 continue;
14993 };
14994
14995 selection_edit_ranges.clear();
14996
14997 // If multiple selections contain a given row, avoid processing that
14998 // row more than once.
14999 let mut start_row = MultiBufferRow(selection.start.row);
15000 if last_toggled_row == Some(start_row) {
15001 start_row = start_row.next_row();
15002 }
15003 let end_row =
15004 if selection.end.row > selection.start.row && selection.end.column == 0 {
15005 MultiBufferRow(selection.end.row - 1)
15006 } else {
15007 MultiBufferRow(selection.end.row)
15008 };
15009 last_toggled_row = Some(end_row);
15010
15011 if start_row > end_row {
15012 continue;
15013 }
15014
15015 // If the language has line comments, toggle those.
15016 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15017
15018 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15019 if ignore_indent {
15020 full_comment_prefixes = full_comment_prefixes
15021 .into_iter()
15022 .map(|s| Arc::from(s.trim_end()))
15023 .collect();
15024 }
15025
15026 if !full_comment_prefixes.is_empty() {
15027 let first_prefix = full_comment_prefixes
15028 .first()
15029 .expect("prefixes is non-empty");
15030 let prefix_trimmed_lengths = full_comment_prefixes
15031 .iter()
15032 .map(|p| p.trim_end_matches(' ').len())
15033 .collect::<SmallVec<[usize; 4]>>();
15034
15035 let mut all_selection_lines_are_comments = true;
15036
15037 for row in start_row.0..=end_row.0 {
15038 let row = MultiBufferRow(row);
15039 if start_row < end_row && snapshot.is_line_blank(row) {
15040 continue;
15041 }
15042
15043 let prefix_range = full_comment_prefixes
15044 .iter()
15045 .zip(prefix_trimmed_lengths.iter().copied())
15046 .map(|(prefix, trimmed_prefix_len)| {
15047 comment_prefix_range(
15048 snapshot.deref(),
15049 row,
15050 &prefix[..trimmed_prefix_len],
15051 &prefix[trimmed_prefix_len..],
15052 ignore_indent,
15053 )
15054 })
15055 .max_by_key(|range| range.end.column - range.start.column)
15056 .expect("prefixes is non-empty");
15057
15058 if prefix_range.is_empty() {
15059 all_selection_lines_are_comments = false;
15060 }
15061
15062 selection_edit_ranges.push(prefix_range);
15063 }
15064
15065 if all_selection_lines_are_comments {
15066 edits.extend(
15067 selection_edit_ranges
15068 .iter()
15069 .cloned()
15070 .map(|range| (range, empty_str.clone())),
15071 );
15072 } else {
15073 let min_column = selection_edit_ranges
15074 .iter()
15075 .map(|range| range.start.column)
15076 .min()
15077 .unwrap_or(0);
15078 edits.extend(selection_edit_ranges.iter().map(|range| {
15079 let position = Point::new(range.start.row, min_column);
15080 (position..position, first_prefix.clone())
15081 }));
15082 }
15083 } else if let Some(BlockCommentConfig {
15084 start: full_comment_prefix,
15085 end: comment_suffix,
15086 ..
15087 }) = language.block_comment()
15088 {
15089 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15090 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15091 let prefix_range = comment_prefix_range(
15092 snapshot.deref(),
15093 start_row,
15094 comment_prefix,
15095 comment_prefix_whitespace,
15096 ignore_indent,
15097 );
15098 let suffix_range = comment_suffix_range(
15099 snapshot.deref(),
15100 end_row,
15101 comment_suffix.trim_start_matches(' '),
15102 comment_suffix.starts_with(' '),
15103 );
15104
15105 if prefix_range.is_empty() || suffix_range.is_empty() {
15106 edits.push((
15107 prefix_range.start..prefix_range.start,
15108 full_comment_prefix.clone(),
15109 ));
15110 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15111 suffixes_inserted.push((end_row, comment_suffix.len()));
15112 } else {
15113 edits.push((prefix_range, empty_str.clone()));
15114 edits.push((suffix_range, empty_str.clone()));
15115 }
15116 } else {
15117 continue;
15118 }
15119 }
15120
15121 drop(snapshot);
15122 this.buffer.update(cx, |buffer, cx| {
15123 buffer.edit(edits, None, cx);
15124 });
15125
15126 // Adjust selections so that they end before any comment suffixes that
15127 // were inserted.
15128 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15129 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15130 let snapshot = this.buffer.read(cx).read(cx);
15131 for selection in &mut selections {
15132 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15133 match row.cmp(&MultiBufferRow(selection.end.row)) {
15134 Ordering::Less => {
15135 suffixes_inserted.next();
15136 continue;
15137 }
15138 Ordering::Greater => break,
15139 Ordering::Equal => {
15140 if selection.end.column == snapshot.line_len(row) {
15141 if selection.is_empty() {
15142 selection.start.column -= suffix_len as u32;
15143 }
15144 selection.end.column -= suffix_len as u32;
15145 }
15146 break;
15147 }
15148 }
15149 }
15150 }
15151
15152 drop(snapshot);
15153 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15154
15155 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15156 let selections_on_single_row = selections.windows(2).all(|selections| {
15157 selections[0].start.row == selections[1].start.row
15158 && selections[0].end.row == selections[1].end.row
15159 && selections[0].start.row == selections[0].end.row
15160 });
15161 let selections_selecting = selections
15162 .iter()
15163 .any(|selection| selection.start != selection.end);
15164 let advance_downwards = action.advance_downwards
15165 && selections_on_single_row
15166 && !selections_selecting
15167 && !matches!(this.mode, EditorMode::SingleLine);
15168
15169 if advance_downwards {
15170 let snapshot = this.buffer.read(cx).snapshot(cx);
15171
15172 this.change_selections(Default::default(), window, cx, |s| {
15173 s.move_cursors_with(|display_snapshot, display_point, _| {
15174 let mut point = display_point.to_point(display_snapshot);
15175 point.row += 1;
15176 point = snapshot.clip_point(point, Bias::Left);
15177 let display_point = point.to_display_point(display_snapshot);
15178 let goal = SelectionGoal::HorizontalPosition(
15179 display_snapshot
15180 .x_for_display_point(display_point, text_layout_details)
15181 .into(),
15182 );
15183 (display_point, goal)
15184 })
15185 });
15186 }
15187 });
15188 }
15189
15190 pub fn select_enclosing_symbol(
15191 &mut self,
15192 _: &SelectEnclosingSymbol,
15193 window: &mut Window,
15194 cx: &mut Context<Self>,
15195 ) {
15196 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15197
15198 let buffer = self.buffer.read(cx).snapshot(cx);
15199 let old_selections = self
15200 .selections
15201 .all::<usize>(&self.display_snapshot(cx))
15202 .into_boxed_slice();
15203
15204 fn update_selection(
15205 selection: &Selection<usize>,
15206 buffer_snap: &MultiBufferSnapshot,
15207 ) -> Option<Selection<usize>> {
15208 let cursor = selection.head();
15209 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15210 for symbol in symbols.iter().rev() {
15211 let start = symbol.range.start.to_offset(buffer_snap);
15212 let end = symbol.range.end.to_offset(buffer_snap);
15213 let new_range = start..end;
15214 if start < selection.start || end > selection.end {
15215 return Some(Selection {
15216 id: selection.id,
15217 start: new_range.start,
15218 end: new_range.end,
15219 goal: SelectionGoal::None,
15220 reversed: selection.reversed,
15221 });
15222 }
15223 }
15224 None
15225 }
15226
15227 let mut selected_larger_symbol = false;
15228 let new_selections = old_selections
15229 .iter()
15230 .map(|selection| match update_selection(selection, &buffer) {
15231 Some(new_selection) => {
15232 if new_selection.range() != selection.range() {
15233 selected_larger_symbol = true;
15234 }
15235 new_selection
15236 }
15237 None => selection.clone(),
15238 })
15239 .collect::<Vec<_>>();
15240
15241 if selected_larger_symbol {
15242 self.change_selections(Default::default(), window, cx, |s| {
15243 s.select(new_selections);
15244 });
15245 }
15246 }
15247
15248 pub fn select_larger_syntax_node(
15249 &mut self,
15250 _: &SelectLargerSyntaxNode,
15251 window: &mut Window,
15252 cx: &mut Context<Self>,
15253 ) {
15254 let Some(visible_row_count) = self.visible_row_count() else {
15255 return;
15256 };
15257 let old_selections: Box<[_]> = self
15258 .selections
15259 .all::<usize>(&self.display_snapshot(cx))
15260 .into();
15261 if old_selections.is_empty() {
15262 return;
15263 }
15264
15265 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15266
15267 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15268 let buffer = self.buffer.read(cx).snapshot(cx);
15269
15270 let mut selected_larger_node = false;
15271 let mut new_selections = old_selections
15272 .iter()
15273 .map(|selection| {
15274 let old_range = selection.start..selection.end;
15275
15276 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15277 // manually select word at selection
15278 if ["string_content", "inline"].contains(&node.kind()) {
15279 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15280 // ignore if word is already selected
15281 if !word_range.is_empty() && old_range != word_range {
15282 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15283 // only select word if start and end point belongs to same word
15284 if word_range == last_word_range {
15285 selected_larger_node = true;
15286 return Selection {
15287 id: selection.id,
15288 start: word_range.start,
15289 end: word_range.end,
15290 goal: SelectionGoal::None,
15291 reversed: selection.reversed,
15292 };
15293 }
15294 }
15295 }
15296 }
15297
15298 let mut new_range = old_range.clone();
15299 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15300 new_range = range;
15301 if !node.is_named() {
15302 continue;
15303 }
15304 if !display_map.intersects_fold(new_range.start)
15305 && !display_map.intersects_fold(new_range.end)
15306 {
15307 break;
15308 }
15309 }
15310
15311 selected_larger_node |= new_range != old_range;
15312 Selection {
15313 id: selection.id,
15314 start: new_range.start,
15315 end: new_range.end,
15316 goal: SelectionGoal::None,
15317 reversed: selection.reversed,
15318 }
15319 })
15320 .collect::<Vec<_>>();
15321
15322 if !selected_larger_node {
15323 return; // don't put this call in the history
15324 }
15325
15326 // scroll based on transformation done to the last selection created by the user
15327 let (last_old, last_new) = old_selections
15328 .last()
15329 .zip(new_selections.last().cloned())
15330 .expect("old_selections isn't empty");
15331
15332 // revert selection
15333 let is_selection_reversed = {
15334 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15335 new_selections.last_mut().expect("checked above").reversed =
15336 should_newest_selection_be_reversed;
15337 should_newest_selection_be_reversed
15338 };
15339
15340 if selected_larger_node {
15341 self.select_syntax_node_history.disable_clearing = true;
15342 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15343 s.select(new_selections.clone());
15344 });
15345 self.select_syntax_node_history.disable_clearing = false;
15346 }
15347
15348 let start_row = last_new.start.to_display_point(&display_map).row().0;
15349 let end_row = last_new.end.to_display_point(&display_map).row().0;
15350 let selection_height = end_row - start_row + 1;
15351 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15352
15353 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15354 let scroll_behavior = if fits_on_the_screen {
15355 self.request_autoscroll(Autoscroll::fit(), cx);
15356 SelectSyntaxNodeScrollBehavior::FitSelection
15357 } else if is_selection_reversed {
15358 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15359 SelectSyntaxNodeScrollBehavior::CursorTop
15360 } else {
15361 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15362 SelectSyntaxNodeScrollBehavior::CursorBottom
15363 };
15364
15365 self.select_syntax_node_history.push((
15366 old_selections,
15367 scroll_behavior,
15368 is_selection_reversed,
15369 ));
15370 }
15371
15372 pub fn select_smaller_syntax_node(
15373 &mut self,
15374 _: &SelectSmallerSyntaxNode,
15375 window: &mut Window,
15376 cx: &mut Context<Self>,
15377 ) {
15378 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15379
15380 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15381 self.select_syntax_node_history.pop()
15382 {
15383 if let Some(selection) = selections.last_mut() {
15384 selection.reversed = is_selection_reversed;
15385 }
15386
15387 self.select_syntax_node_history.disable_clearing = true;
15388 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15389 s.select(selections.to_vec());
15390 });
15391 self.select_syntax_node_history.disable_clearing = false;
15392
15393 match scroll_behavior {
15394 SelectSyntaxNodeScrollBehavior::CursorTop => {
15395 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15396 }
15397 SelectSyntaxNodeScrollBehavior::FitSelection => {
15398 self.request_autoscroll(Autoscroll::fit(), cx);
15399 }
15400 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15401 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15402 }
15403 }
15404 }
15405 }
15406
15407 pub fn unwrap_syntax_node(
15408 &mut self,
15409 _: &UnwrapSyntaxNode,
15410 window: &mut Window,
15411 cx: &mut Context<Self>,
15412 ) {
15413 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15414
15415 let buffer = self.buffer.read(cx).snapshot(cx);
15416 let selections = self
15417 .selections
15418 .all::<usize>(&self.display_snapshot(cx))
15419 .into_iter()
15420 // subtracting the offset requires sorting
15421 .sorted_by_key(|i| i.start);
15422
15423 let full_edits = selections
15424 .into_iter()
15425 .filter_map(|selection| {
15426 let child = if selection.is_empty()
15427 && let Some((_, ancestor_range)) =
15428 buffer.syntax_ancestor(selection.start..selection.end)
15429 {
15430 ancestor_range
15431 } else {
15432 selection.range()
15433 };
15434
15435 let mut parent = child.clone();
15436 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15437 parent = ancestor_range;
15438 if parent.start < child.start || parent.end > child.end {
15439 break;
15440 }
15441 }
15442
15443 if parent == child {
15444 return None;
15445 }
15446 let text = buffer.text_for_range(child).collect::<String>();
15447 Some((selection.id, parent, text))
15448 })
15449 .collect::<Vec<_>>();
15450 if full_edits.is_empty() {
15451 return;
15452 }
15453
15454 self.transact(window, cx, |this, window, cx| {
15455 this.buffer.update(cx, |buffer, cx| {
15456 buffer.edit(
15457 full_edits
15458 .iter()
15459 .map(|(_, p, t)| (p.clone(), t.clone()))
15460 .collect::<Vec<_>>(),
15461 None,
15462 cx,
15463 );
15464 });
15465 this.change_selections(Default::default(), window, cx, |s| {
15466 let mut offset = 0;
15467 let mut selections = vec![];
15468 for (id, parent, text) in full_edits {
15469 let start = parent.start - offset;
15470 offset += parent.len() - text.len();
15471 selections.push(Selection {
15472 id,
15473 start,
15474 end: start + text.len(),
15475 reversed: false,
15476 goal: Default::default(),
15477 });
15478 }
15479 s.select(selections);
15480 });
15481 });
15482 }
15483
15484 pub fn select_next_syntax_node(
15485 &mut self,
15486 _: &SelectNextSyntaxNode,
15487 window: &mut Window,
15488 cx: &mut Context<Self>,
15489 ) {
15490 let old_selections: Box<[_]> = self
15491 .selections
15492 .all::<usize>(&self.display_snapshot(cx))
15493 .into();
15494 if old_selections.is_empty() {
15495 return;
15496 }
15497
15498 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15499
15500 let buffer = self.buffer.read(cx).snapshot(cx);
15501 let mut selected_sibling = false;
15502
15503 let new_selections = old_selections
15504 .iter()
15505 .map(|selection| {
15506 let old_range = selection.start..selection.end;
15507
15508 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15509 let new_range = node.byte_range();
15510 selected_sibling = true;
15511 Selection {
15512 id: selection.id,
15513 start: new_range.start,
15514 end: new_range.end,
15515 goal: SelectionGoal::None,
15516 reversed: selection.reversed,
15517 }
15518 } else {
15519 selection.clone()
15520 }
15521 })
15522 .collect::<Vec<_>>();
15523
15524 if selected_sibling {
15525 self.change_selections(
15526 SelectionEffects::scroll(Autoscroll::fit()),
15527 window,
15528 cx,
15529 |s| {
15530 s.select(new_selections);
15531 },
15532 );
15533 }
15534 }
15535
15536 pub fn select_prev_syntax_node(
15537 &mut self,
15538 _: &SelectPreviousSyntaxNode,
15539 window: &mut Window,
15540 cx: &mut Context<Self>,
15541 ) {
15542 let old_selections: Box<[_]> = self
15543 .selections
15544 .all::<usize>(&self.display_snapshot(cx))
15545 .into();
15546 if old_selections.is_empty() {
15547 return;
15548 }
15549
15550 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15551
15552 let buffer = self.buffer.read(cx).snapshot(cx);
15553 let mut selected_sibling = false;
15554
15555 let new_selections = old_selections
15556 .iter()
15557 .map(|selection| {
15558 let old_range = selection.start..selection.end;
15559
15560 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15561 let new_range = node.byte_range();
15562 selected_sibling = true;
15563 Selection {
15564 id: selection.id,
15565 start: new_range.start,
15566 end: new_range.end,
15567 goal: SelectionGoal::None,
15568 reversed: selection.reversed,
15569 }
15570 } else {
15571 selection.clone()
15572 }
15573 })
15574 .collect::<Vec<_>>();
15575
15576 if selected_sibling {
15577 self.change_selections(
15578 SelectionEffects::scroll(Autoscroll::fit()),
15579 window,
15580 cx,
15581 |s| {
15582 s.select(new_selections);
15583 },
15584 );
15585 }
15586 }
15587
15588 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15589 if !EditorSettings::get_global(cx).gutter.runnables {
15590 self.clear_tasks();
15591 return Task::ready(());
15592 }
15593 let project = self.project().map(Entity::downgrade);
15594 let task_sources = self.lsp_task_sources(cx);
15595 let multi_buffer = self.buffer.downgrade();
15596 cx.spawn_in(window, async move |editor, cx| {
15597 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15598 let Some(project) = project.and_then(|p| p.upgrade()) else {
15599 return;
15600 };
15601 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15602 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15603 }) else {
15604 return;
15605 };
15606
15607 let hide_runnables = project
15608 .update(cx, |project, _| project.is_via_collab())
15609 .unwrap_or(true);
15610 if hide_runnables {
15611 return;
15612 }
15613 let new_rows =
15614 cx.background_spawn({
15615 let snapshot = display_snapshot.clone();
15616 async move {
15617 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15618 }
15619 })
15620 .await;
15621 let Ok(lsp_tasks) =
15622 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15623 else {
15624 return;
15625 };
15626 let lsp_tasks = lsp_tasks.await;
15627
15628 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15629 lsp_tasks
15630 .into_iter()
15631 .flat_map(|(kind, tasks)| {
15632 tasks.into_iter().filter_map(move |(location, task)| {
15633 Some((kind.clone(), location?, task))
15634 })
15635 })
15636 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15637 let buffer = location.target.buffer;
15638 let buffer_snapshot = buffer.read(cx).snapshot();
15639 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15640 |(excerpt_id, snapshot, _)| {
15641 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15642 display_snapshot
15643 .buffer_snapshot()
15644 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15645 } else {
15646 None
15647 }
15648 },
15649 );
15650 if let Some(offset) = offset {
15651 let task_buffer_range =
15652 location.target.range.to_point(&buffer_snapshot);
15653 let context_buffer_range =
15654 task_buffer_range.to_offset(&buffer_snapshot);
15655 let context_range = BufferOffset(context_buffer_range.start)
15656 ..BufferOffset(context_buffer_range.end);
15657
15658 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15659 .or_insert_with(|| RunnableTasks {
15660 templates: Vec::new(),
15661 offset,
15662 column: task_buffer_range.start.column,
15663 extra_variables: HashMap::default(),
15664 context_range,
15665 })
15666 .templates
15667 .push((kind, task.original_task().clone()));
15668 }
15669
15670 acc
15671 })
15672 }) else {
15673 return;
15674 };
15675
15676 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15677 buffer.language_settings(cx).tasks.prefer_lsp
15678 }) else {
15679 return;
15680 };
15681
15682 let rows = Self::runnable_rows(
15683 project,
15684 display_snapshot,
15685 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15686 new_rows,
15687 cx.clone(),
15688 )
15689 .await;
15690 editor
15691 .update(cx, |editor, _| {
15692 editor.clear_tasks();
15693 for (key, mut value) in rows {
15694 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15695 value.templates.extend(lsp_tasks.templates);
15696 }
15697
15698 editor.insert_tasks(key, value);
15699 }
15700 for (key, value) in lsp_tasks_by_rows {
15701 editor.insert_tasks(key, value);
15702 }
15703 })
15704 .ok();
15705 })
15706 }
15707 fn fetch_runnable_ranges(
15708 snapshot: &DisplaySnapshot,
15709 range: Range<Anchor>,
15710 ) -> Vec<language::RunnableRange> {
15711 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15712 }
15713
15714 fn runnable_rows(
15715 project: Entity<Project>,
15716 snapshot: DisplaySnapshot,
15717 prefer_lsp: bool,
15718 runnable_ranges: Vec<RunnableRange>,
15719 cx: AsyncWindowContext,
15720 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15721 cx.spawn(async move |cx| {
15722 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15723 for mut runnable in runnable_ranges {
15724 let Some(tasks) = cx
15725 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15726 .ok()
15727 else {
15728 continue;
15729 };
15730 let mut tasks = tasks.await;
15731
15732 if prefer_lsp {
15733 tasks.retain(|(task_kind, _)| {
15734 !matches!(task_kind, TaskSourceKind::Language { .. })
15735 });
15736 }
15737 if tasks.is_empty() {
15738 continue;
15739 }
15740
15741 let point = runnable
15742 .run_range
15743 .start
15744 .to_point(&snapshot.buffer_snapshot());
15745 let Some(row) = snapshot
15746 .buffer_snapshot()
15747 .buffer_line_for_row(MultiBufferRow(point.row))
15748 .map(|(_, range)| range.start.row)
15749 else {
15750 continue;
15751 };
15752
15753 let context_range =
15754 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15755 runnable_rows.push((
15756 (runnable.buffer_id, row),
15757 RunnableTasks {
15758 templates: tasks,
15759 offset: snapshot
15760 .buffer_snapshot()
15761 .anchor_before(runnable.run_range.start),
15762 context_range,
15763 column: point.column,
15764 extra_variables: runnable.extra_captures,
15765 },
15766 ));
15767 }
15768 runnable_rows
15769 })
15770 }
15771
15772 fn templates_with_tags(
15773 project: &Entity<Project>,
15774 runnable: &mut Runnable,
15775 cx: &mut App,
15776 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15777 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15778 let (worktree_id, file) = project
15779 .buffer_for_id(runnable.buffer, cx)
15780 .and_then(|buffer| buffer.read(cx).file())
15781 .map(|file| (file.worktree_id(cx), file.clone()))
15782 .unzip();
15783
15784 (
15785 project.task_store().read(cx).task_inventory().cloned(),
15786 worktree_id,
15787 file,
15788 )
15789 });
15790
15791 let tags = mem::take(&mut runnable.tags);
15792 let language = runnable.language.clone();
15793 cx.spawn(async move |cx| {
15794 let mut templates_with_tags = Vec::new();
15795 if let Some(inventory) = inventory {
15796 for RunnableTag(tag) in tags {
15797 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15798 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15799 }) else {
15800 return templates_with_tags;
15801 };
15802 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15803 move |(_, template)| {
15804 template.tags.iter().any(|source_tag| source_tag == &tag)
15805 },
15806 ));
15807 }
15808 }
15809 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15810
15811 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15812 // Strongest source wins; if we have worktree tag binding, prefer that to
15813 // global and language bindings;
15814 // if we have a global binding, prefer that to language binding.
15815 let first_mismatch = templates_with_tags
15816 .iter()
15817 .position(|(tag_source, _)| tag_source != leading_tag_source);
15818 if let Some(index) = first_mismatch {
15819 templates_with_tags.truncate(index);
15820 }
15821 }
15822
15823 templates_with_tags
15824 })
15825 }
15826
15827 pub fn move_to_enclosing_bracket(
15828 &mut self,
15829 _: &MoveToEnclosingBracket,
15830 window: &mut Window,
15831 cx: &mut Context<Self>,
15832 ) {
15833 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15834 self.change_selections(Default::default(), window, cx, |s| {
15835 s.move_offsets_with(|snapshot, selection| {
15836 let Some(enclosing_bracket_ranges) =
15837 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15838 else {
15839 return;
15840 };
15841
15842 let mut best_length = usize::MAX;
15843 let mut best_inside = false;
15844 let mut best_in_bracket_range = false;
15845 let mut best_destination = None;
15846 for (open, close) in enclosing_bracket_ranges {
15847 let close = close.to_inclusive();
15848 let length = close.end() - open.start;
15849 let inside = selection.start >= open.end && selection.end <= *close.start();
15850 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15851 || close.contains(&selection.head());
15852
15853 // If best is next to a bracket and current isn't, skip
15854 if !in_bracket_range && best_in_bracket_range {
15855 continue;
15856 }
15857
15858 // Prefer smaller lengths unless best is inside and current isn't
15859 if length > best_length && (best_inside || !inside) {
15860 continue;
15861 }
15862
15863 best_length = length;
15864 best_inside = inside;
15865 best_in_bracket_range = in_bracket_range;
15866 best_destination = Some(
15867 if close.contains(&selection.start) && close.contains(&selection.end) {
15868 if inside { open.end } else { open.start }
15869 } else if inside {
15870 *close.start()
15871 } else {
15872 *close.end()
15873 },
15874 );
15875 }
15876
15877 if let Some(destination) = best_destination {
15878 selection.collapse_to(destination, SelectionGoal::None);
15879 }
15880 })
15881 });
15882 }
15883
15884 pub fn undo_selection(
15885 &mut self,
15886 _: &UndoSelection,
15887 window: &mut Window,
15888 cx: &mut Context<Self>,
15889 ) {
15890 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15891 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15892 self.selection_history.mode = SelectionHistoryMode::Undoing;
15893 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15894 this.end_selection(window, cx);
15895 this.change_selections(
15896 SelectionEffects::scroll(Autoscroll::newest()),
15897 window,
15898 cx,
15899 |s| s.select_anchors(entry.selections.to_vec()),
15900 );
15901 });
15902 self.selection_history.mode = SelectionHistoryMode::Normal;
15903
15904 self.select_next_state = entry.select_next_state;
15905 self.select_prev_state = entry.select_prev_state;
15906 self.add_selections_state = entry.add_selections_state;
15907 }
15908 }
15909
15910 pub fn redo_selection(
15911 &mut self,
15912 _: &RedoSelection,
15913 window: &mut Window,
15914 cx: &mut Context<Self>,
15915 ) {
15916 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15917 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15918 self.selection_history.mode = SelectionHistoryMode::Redoing;
15919 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15920 this.end_selection(window, cx);
15921 this.change_selections(
15922 SelectionEffects::scroll(Autoscroll::newest()),
15923 window,
15924 cx,
15925 |s| s.select_anchors(entry.selections.to_vec()),
15926 );
15927 });
15928 self.selection_history.mode = SelectionHistoryMode::Normal;
15929
15930 self.select_next_state = entry.select_next_state;
15931 self.select_prev_state = entry.select_prev_state;
15932 self.add_selections_state = entry.add_selections_state;
15933 }
15934 }
15935
15936 pub fn expand_excerpts(
15937 &mut self,
15938 action: &ExpandExcerpts,
15939 _: &mut Window,
15940 cx: &mut Context<Self>,
15941 ) {
15942 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15943 }
15944
15945 pub fn expand_excerpts_down(
15946 &mut self,
15947 action: &ExpandExcerptsDown,
15948 _: &mut Window,
15949 cx: &mut Context<Self>,
15950 ) {
15951 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15952 }
15953
15954 pub fn expand_excerpts_up(
15955 &mut self,
15956 action: &ExpandExcerptsUp,
15957 _: &mut Window,
15958 cx: &mut Context<Self>,
15959 ) {
15960 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15961 }
15962
15963 pub fn expand_excerpts_for_direction(
15964 &mut self,
15965 lines: u32,
15966 direction: ExpandExcerptDirection,
15967
15968 cx: &mut Context<Self>,
15969 ) {
15970 let selections = self.selections.disjoint_anchors_arc();
15971
15972 let lines = if lines == 0 {
15973 EditorSettings::get_global(cx).expand_excerpt_lines
15974 } else {
15975 lines
15976 };
15977
15978 self.buffer.update(cx, |buffer, cx| {
15979 let snapshot = buffer.snapshot(cx);
15980 let mut excerpt_ids = selections
15981 .iter()
15982 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15983 .collect::<Vec<_>>();
15984 excerpt_ids.sort();
15985 excerpt_ids.dedup();
15986 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15987 })
15988 }
15989
15990 pub fn expand_excerpt(
15991 &mut self,
15992 excerpt: ExcerptId,
15993 direction: ExpandExcerptDirection,
15994 window: &mut Window,
15995 cx: &mut Context<Self>,
15996 ) {
15997 let current_scroll_position = self.scroll_position(cx);
15998 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15999 let mut scroll = None;
16000
16001 if direction == ExpandExcerptDirection::Down {
16002 let multi_buffer = self.buffer.read(cx);
16003 let snapshot = multi_buffer.snapshot(cx);
16004 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16005 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16006 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16007 {
16008 let buffer_snapshot = buffer.read(cx).snapshot();
16009 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16010 let last_row = buffer_snapshot.max_point().row;
16011 let lines_below = last_row.saturating_sub(excerpt_end_row);
16012 if lines_below >= lines_to_expand {
16013 scroll = Some(
16014 current_scroll_position
16015 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16016 );
16017 }
16018 }
16019 }
16020 if direction == ExpandExcerptDirection::Up
16021 && self
16022 .buffer
16023 .read(cx)
16024 .snapshot(cx)
16025 .excerpt_before(excerpt)
16026 .is_none()
16027 {
16028 scroll = Some(current_scroll_position);
16029 }
16030
16031 self.buffer.update(cx, |buffer, cx| {
16032 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16033 });
16034
16035 if let Some(new_scroll_position) = scroll {
16036 self.set_scroll_position(new_scroll_position, window, cx);
16037 }
16038 }
16039
16040 pub fn go_to_singleton_buffer_point(
16041 &mut self,
16042 point: Point,
16043 window: &mut Window,
16044 cx: &mut Context<Self>,
16045 ) {
16046 self.go_to_singleton_buffer_range(point..point, window, cx);
16047 }
16048
16049 pub fn go_to_singleton_buffer_range(
16050 &mut self,
16051 range: Range<Point>,
16052 window: &mut Window,
16053 cx: &mut Context<Self>,
16054 ) {
16055 let multibuffer = self.buffer().read(cx);
16056 let Some(buffer) = multibuffer.as_singleton() else {
16057 return;
16058 };
16059 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16060 return;
16061 };
16062 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16063 return;
16064 };
16065 self.change_selections(
16066 SelectionEffects::default().nav_history(true),
16067 window,
16068 cx,
16069 |s| s.select_anchor_ranges([start..end]),
16070 );
16071 }
16072
16073 pub fn go_to_diagnostic(
16074 &mut self,
16075 action: &GoToDiagnostic,
16076 window: &mut Window,
16077 cx: &mut Context<Self>,
16078 ) {
16079 if !self.diagnostics_enabled() {
16080 return;
16081 }
16082 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16083 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16084 }
16085
16086 pub fn go_to_prev_diagnostic(
16087 &mut self,
16088 action: &GoToPreviousDiagnostic,
16089 window: &mut Window,
16090 cx: &mut Context<Self>,
16091 ) {
16092 if !self.diagnostics_enabled() {
16093 return;
16094 }
16095 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16096 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16097 }
16098
16099 pub fn go_to_diagnostic_impl(
16100 &mut self,
16101 direction: Direction,
16102 severity: GoToDiagnosticSeverityFilter,
16103 window: &mut Window,
16104 cx: &mut Context<Self>,
16105 ) {
16106 let buffer = self.buffer.read(cx).snapshot(cx);
16107 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16108
16109 let mut active_group_id = None;
16110 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16111 && active_group.active_range.start.to_offset(&buffer) == selection.start
16112 {
16113 active_group_id = Some(active_group.group_id);
16114 }
16115
16116 fn filtered<'a>(
16117 severity: GoToDiagnosticSeverityFilter,
16118 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16119 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16120 diagnostics
16121 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16122 .filter(|entry| entry.range.start != entry.range.end)
16123 .filter(|entry| !entry.diagnostic.is_unnecessary)
16124 }
16125
16126 let before = filtered(
16127 severity,
16128 buffer
16129 .diagnostics_in_range(0..selection.start)
16130 .filter(|entry| entry.range.start <= selection.start),
16131 );
16132 let after = filtered(
16133 severity,
16134 buffer
16135 .diagnostics_in_range(selection.start..buffer.len())
16136 .filter(|entry| entry.range.start >= selection.start),
16137 );
16138
16139 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16140 if direction == Direction::Prev {
16141 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16142 {
16143 for diagnostic in prev_diagnostics.into_iter().rev() {
16144 if diagnostic.range.start != selection.start
16145 || active_group_id
16146 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16147 {
16148 found = Some(diagnostic);
16149 break 'outer;
16150 }
16151 }
16152 }
16153 } else {
16154 for diagnostic in after.chain(before) {
16155 if diagnostic.range.start != selection.start
16156 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16157 {
16158 found = Some(diagnostic);
16159 break;
16160 }
16161 }
16162 }
16163 let Some(next_diagnostic) = found else {
16164 return;
16165 };
16166
16167 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16168 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16169 return;
16170 };
16171 let snapshot = self.snapshot(window, cx);
16172 if snapshot.intersects_fold(next_diagnostic.range.start) {
16173 self.unfold_ranges(
16174 std::slice::from_ref(&next_diagnostic.range),
16175 true,
16176 false,
16177 cx,
16178 );
16179 }
16180 self.change_selections(Default::default(), window, cx, |s| {
16181 s.select_ranges(vec![
16182 next_diagnostic.range.start..next_diagnostic.range.start,
16183 ])
16184 });
16185 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16186 self.refresh_edit_prediction(false, true, window, cx);
16187 }
16188
16189 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16190 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16191 let snapshot = self.snapshot(window, cx);
16192 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16193 self.go_to_hunk_before_or_after_position(
16194 &snapshot,
16195 selection.head(),
16196 Direction::Next,
16197 window,
16198 cx,
16199 );
16200 }
16201
16202 pub fn go_to_hunk_before_or_after_position(
16203 &mut self,
16204 snapshot: &EditorSnapshot,
16205 position: Point,
16206 direction: Direction,
16207 window: &mut Window,
16208 cx: &mut Context<Editor>,
16209 ) {
16210 let row = if direction == Direction::Next {
16211 self.hunk_after_position(snapshot, position)
16212 .map(|hunk| hunk.row_range.start)
16213 } else {
16214 self.hunk_before_position(snapshot, position)
16215 };
16216
16217 if let Some(row) = row {
16218 let destination = Point::new(row.0, 0);
16219 let autoscroll = Autoscroll::center();
16220
16221 self.unfold_ranges(&[destination..destination], false, false, cx);
16222 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16223 s.select_ranges([destination..destination]);
16224 });
16225 }
16226 }
16227
16228 fn hunk_after_position(
16229 &mut self,
16230 snapshot: &EditorSnapshot,
16231 position: Point,
16232 ) -> Option<MultiBufferDiffHunk> {
16233 snapshot
16234 .buffer_snapshot()
16235 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16236 .find(|hunk| hunk.row_range.start.0 > position.row)
16237 .or_else(|| {
16238 snapshot
16239 .buffer_snapshot()
16240 .diff_hunks_in_range(Point::zero()..position)
16241 .find(|hunk| hunk.row_range.end.0 < position.row)
16242 })
16243 }
16244
16245 fn go_to_prev_hunk(
16246 &mut self,
16247 _: &GoToPreviousHunk,
16248 window: &mut Window,
16249 cx: &mut Context<Self>,
16250 ) {
16251 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16252 let snapshot = self.snapshot(window, cx);
16253 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16254 self.go_to_hunk_before_or_after_position(
16255 &snapshot,
16256 selection.head(),
16257 Direction::Prev,
16258 window,
16259 cx,
16260 );
16261 }
16262
16263 fn hunk_before_position(
16264 &mut self,
16265 snapshot: &EditorSnapshot,
16266 position: Point,
16267 ) -> Option<MultiBufferRow> {
16268 snapshot
16269 .buffer_snapshot()
16270 .diff_hunk_before(position)
16271 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16272 }
16273
16274 fn go_to_next_change(
16275 &mut self,
16276 _: &GoToNextChange,
16277 window: &mut Window,
16278 cx: &mut Context<Self>,
16279 ) {
16280 if let Some(selections) = self
16281 .change_list
16282 .next_change(1, Direction::Next)
16283 .map(|s| s.to_vec())
16284 {
16285 self.change_selections(Default::default(), window, cx, |s| {
16286 let map = s.display_snapshot();
16287 s.select_display_ranges(selections.iter().map(|a| {
16288 let point = a.to_display_point(&map);
16289 point..point
16290 }))
16291 })
16292 }
16293 }
16294
16295 fn go_to_previous_change(
16296 &mut self,
16297 _: &GoToPreviousChange,
16298 window: &mut Window,
16299 cx: &mut Context<Self>,
16300 ) {
16301 if let Some(selections) = self
16302 .change_list
16303 .next_change(1, Direction::Prev)
16304 .map(|s| s.to_vec())
16305 {
16306 self.change_selections(Default::default(), window, cx, |s| {
16307 let map = s.display_snapshot();
16308 s.select_display_ranges(selections.iter().map(|a| {
16309 let point = a.to_display_point(&map);
16310 point..point
16311 }))
16312 })
16313 }
16314 }
16315
16316 pub fn go_to_next_document_highlight(
16317 &mut self,
16318 _: &GoToNextDocumentHighlight,
16319 window: &mut Window,
16320 cx: &mut Context<Self>,
16321 ) {
16322 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16323 }
16324
16325 pub fn go_to_prev_document_highlight(
16326 &mut self,
16327 _: &GoToPreviousDocumentHighlight,
16328 window: &mut Window,
16329 cx: &mut Context<Self>,
16330 ) {
16331 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16332 }
16333
16334 pub fn go_to_document_highlight_before_or_after_position(
16335 &mut self,
16336 direction: Direction,
16337 window: &mut Window,
16338 cx: &mut Context<Editor>,
16339 ) {
16340 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16341 let snapshot = self.snapshot(window, cx);
16342 let buffer = &snapshot.buffer_snapshot();
16343 let position = self
16344 .selections
16345 .newest::<Point>(&snapshot.display_snapshot)
16346 .head();
16347 let anchor_position = buffer.anchor_after(position);
16348
16349 // Get all document highlights (both read and write)
16350 let mut all_highlights = Vec::new();
16351
16352 if let Some((_, read_highlights)) = self
16353 .background_highlights
16354 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16355 {
16356 all_highlights.extend(read_highlights.iter());
16357 }
16358
16359 if let Some((_, write_highlights)) = self
16360 .background_highlights
16361 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16362 {
16363 all_highlights.extend(write_highlights.iter());
16364 }
16365
16366 if all_highlights.is_empty() {
16367 return;
16368 }
16369
16370 // Sort highlights by position
16371 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16372
16373 let target_highlight = match direction {
16374 Direction::Next => {
16375 // Find the first highlight after the current position
16376 all_highlights
16377 .iter()
16378 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16379 }
16380 Direction::Prev => {
16381 // Find the last highlight before the current position
16382 all_highlights
16383 .iter()
16384 .rev()
16385 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16386 }
16387 };
16388
16389 if let Some(highlight) = target_highlight {
16390 let destination = highlight.start.to_point(buffer);
16391 let autoscroll = Autoscroll::center();
16392
16393 self.unfold_ranges(&[destination..destination], false, false, cx);
16394 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16395 s.select_ranges([destination..destination]);
16396 });
16397 }
16398 }
16399
16400 fn go_to_line<T: 'static>(
16401 &mut self,
16402 position: Anchor,
16403 highlight_color: Option<Hsla>,
16404 window: &mut Window,
16405 cx: &mut Context<Self>,
16406 ) {
16407 let snapshot = self.snapshot(window, cx).display_snapshot;
16408 let position = position.to_point(&snapshot.buffer_snapshot());
16409 let start = snapshot
16410 .buffer_snapshot()
16411 .clip_point(Point::new(position.row, 0), Bias::Left);
16412 let end = start + Point::new(1, 0);
16413 let start = snapshot.buffer_snapshot().anchor_before(start);
16414 let end = snapshot.buffer_snapshot().anchor_before(end);
16415
16416 self.highlight_rows::<T>(
16417 start..end,
16418 highlight_color
16419 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16420 Default::default(),
16421 cx,
16422 );
16423
16424 if self.buffer.read(cx).is_singleton() {
16425 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16426 }
16427 }
16428
16429 pub fn go_to_definition(
16430 &mut self,
16431 _: &GoToDefinition,
16432 window: &mut Window,
16433 cx: &mut Context<Self>,
16434 ) -> Task<Result<Navigated>> {
16435 let definition =
16436 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16437 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16438 cx.spawn_in(window, async move |editor, cx| {
16439 if definition.await? == Navigated::Yes {
16440 return Ok(Navigated::Yes);
16441 }
16442 match fallback_strategy {
16443 GoToDefinitionFallback::None => Ok(Navigated::No),
16444 GoToDefinitionFallback::FindAllReferences => {
16445 match editor.update_in(cx, |editor, window, cx| {
16446 editor.find_all_references(&FindAllReferences, window, cx)
16447 })? {
16448 Some(references) => references.await,
16449 None => Ok(Navigated::No),
16450 }
16451 }
16452 }
16453 })
16454 }
16455
16456 pub fn go_to_declaration(
16457 &mut self,
16458 _: &GoToDeclaration,
16459 window: &mut Window,
16460 cx: &mut Context<Self>,
16461 ) -> Task<Result<Navigated>> {
16462 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16463 }
16464
16465 pub fn go_to_declaration_split(
16466 &mut self,
16467 _: &GoToDeclaration,
16468 window: &mut Window,
16469 cx: &mut Context<Self>,
16470 ) -> Task<Result<Navigated>> {
16471 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16472 }
16473
16474 pub fn go_to_implementation(
16475 &mut self,
16476 _: &GoToImplementation,
16477 window: &mut Window,
16478 cx: &mut Context<Self>,
16479 ) -> Task<Result<Navigated>> {
16480 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16481 }
16482
16483 pub fn go_to_implementation_split(
16484 &mut self,
16485 _: &GoToImplementationSplit,
16486 window: &mut Window,
16487 cx: &mut Context<Self>,
16488 ) -> Task<Result<Navigated>> {
16489 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16490 }
16491
16492 pub fn go_to_type_definition(
16493 &mut self,
16494 _: &GoToTypeDefinition,
16495 window: &mut Window,
16496 cx: &mut Context<Self>,
16497 ) -> Task<Result<Navigated>> {
16498 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16499 }
16500
16501 pub fn go_to_definition_split(
16502 &mut self,
16503 _: &GoToDefinitionSplit,
16504 window: &mut Window,
16505 cx: &mut Context<Self>,
16506 ) -> Task<Result<Navigated>> {
16507 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16508 }
16509
16510 pub fn go_to_type_definition_split(
16511 &mut self,
16512 _: &GoToTypeDefinitionSplit,
16513 window: &mut Window,
16514 cx: &mut Context<Self>,
16515 ) -> Task<Result<Navigated>> {
16516 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16517 }
16518
16519 fn go_to_definition_of_kind(
16520 &mut self,
16521 kind: GotoDefinitionKind,
16522 split: bool,
16523 window: &mut Window,
16524 cx: &mut Context<Self>,
16525 ) -> Task<Result<Navigated>> {
16526 let Some(provider) = self.semantics_provider.clone() else {
16527 return Task::ready(Ok(Navigated::No));
16528 };
16529 let head = self
16530 .selections
16531 .newest::<usize>(&self.display_snapshot(cx))
16532 .head();
16533 let buffer = self.buffer.read(cx);
16534 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16535 return Task::ready(Ok(Navigated::No));
16536 };
16537 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16538 return Task::ready(Ok(Navigated::No));
16539 };
16540
16541 cx.spawn_in(window, async move |editor, cx| {
16542 let Some(definitions) = definitions.await? else {
16543 return Ok(Navigated::No);
16544 };
16545 let navigated = editor
16546 .update_in(cx, |editor, window, cx| {
16547 editor.navigate_to_hover_links(
16548 Some(kind),
16549 definitions
16550 .into_iter()
16551 .filter(|location| {
16552 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16553 })
16554 .map(HoverLink::Text)
16555 .collect::<Vec<_>>(),
16556 split,
16557 window,
16558 cx,
16559 )
16560 })?
16561 .await?;
16562 anyhow::Ok(navigated)
16563 })
16564 }
16565
16566 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16567 let selection = self.selections.newest_anchor();
16568 let head = selection.head();
16569 let tail = selection.tail();
16570
16571 let Some((buffer, start_position)) =
16572 self.buffer.read(cx).text_anchor_for_position(head, cx)
16573 else {
16574 return;
16575 };
16576
16577 let end_position = if head != tail {
16578 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16579 return;
16580 };
16581 Some(pos)
16582 } else {
16583 None
16584 };
16585
16586 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16587 let url = if let Some(end_pos) = end_position {
16588 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16589 } else {
16590 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16591 };
16592
16593 if let Some(url) = url {
16594 cx.update(|window, cx| {
16595 if parse_zed_link(&url, cx).is_some() {
16596 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16597 } else {
16598 cx.open_url(&url);
16599 }
16600 })?;
16601 }
16602
16603 anyhow::Ok(())
16604 });
16605
16606 url_finder.detach();
16607 }
16608
16609 pub fn open_selected_filename(
16610 &mut self,
16611 _: &OpenSelectedFilename,
16612 window: &mut Window,
16613 cx: &mut Context<Self>,
16614 ) {
16615 let Some(workspace) = self.workspace() else {
16616 return;
16617 };
16618
16619 let position = self.selections.newest_anchor().head();
16620
16621 let Some((buffer, buffer_position)) =
16622 self.buffer.read(cx).text_anchor_for_position(position, cx)
16623 else {
16624 return;
16625 };
16626
16627 let project = self.project.clone();
16628
16629 cx.spawn_in(window, async move |_, cx| {
16630 let result = find_file(&buffer, project, buffer_position, cx).await;
16631
16632 if let Some((_, path)) = result {
16633 workspace
16634 .update_in(cx, |workspace, window, cx| {
16635 workspace.open_resolved_path(path, window, cx)
16636 })?
16637 .await?;
16638 }
16639 anyhow::Ok(())
16640 })
16641 .detach();
16642 }
16643
16644 pub(crate) fn navigate_to_hover_links(
16645 &mut self,
16646 kind: Option<GotoDefinitionKind>,
16647 definitions: Vec<HoverLink>,
16648 split: bool,
16649 window: &mut Window,
16650 cx: &mut Context<Editor>,
16651 ) -> Task<Result<Navigated>> {
16652 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16653 let mut first_url_or_file = None;
16654 let definitions: Vec<_> = definitions
16655 .into_iter()
16656 .filter_map(|def| match def {
16657 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16658 HoverLink::InlayHint(lsp_location, server_id) => {
16659 let computation =
16660 self.compute_target_location(lsp_location, server_id, window, cx);
16661 Some(cx.background_spawn(computation))
16662 }
16663 HoverLink::Url(url) => {
16664 first_url_or_file = Some(Either::Left(url));
16665 None
16666 }
16667 HoverLink::File(path) => {
16668 first_url_or_file = Some(Either::Right(path));
16669 None
16670 }
16671 })
16672 .collect();
16673
16674 let workspace = self.workspace();
16675
16676 cx.spawn_in(window, async move |editor, cx| {
16677 let locations: Vec<Location> = future::join_all(definitions)
16678 .await
16679 .into_iter()
16680 .filter_map(|location| location.transpose())
16681 .collect::<Result<_>>()
16682 .context("location tasks")?;
16683 let mut locations = cx.update(|_, cx| {
16684 locations
16685 .into_iter()
16686 .map(|location| {
16687 let buffer = location.buffer.read(cx);
16688 (location.buffer, location.range.to_point(buffer))
16689 })
16690 .into_group_map()
16691 })?;
16692 let mut num_locations = 0;
16693 for ranges in locations.values_mut() {
16694 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16695 ranges.dedup();
16696 num_locations += ranges.len();
16697 }
16698
16699 if num_locations > 1 {
16700 let Some(workspace) = workspace else {
16701 return Ok(Navigated::No);
16702 };
16703
16704 let tab_kind = match kind {
16705 Some(GotoDefinitionKind::Implementation) => "Implementations",
16706 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16707 Some(GotoDefinitionKind::Declaration) => "Declarations",
16708 Some(GotoDefinitionKind::Type) => "Types",
16709 };
16710 let title = editor
16711 .update_in(cx, |_, _, cx| {
16712 let target = locations
16713 .iter()
16714 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16715 .map(|(buffer, location)| {
16716 buffer
16717 .read(cx)
16718 .text_for_range(location.clone())
16719 .collect::<String>()
16720 })
16721 .filter(|text| !text.contains('\n'))
16722 .unique()
16723 .take(3)
16724 .join(", ");
16725 if target.is_empty() {
16726 tab_kind.to_owned()
16727 } else {
16728 format!("{tab_kind} for {target}")
16729 }
16730 })
16731 .context("buffer title")?;
16732
16733 let opened = workspace
16734 .update_in(cx, |workspace, window, cx| {
16735 Self::open_locations_in_multibuffer(
16736 workspace,
16737 locations,
16738 title,
16739 split,
16740 MultibufferSelectionMode::First,
16741 window,
16742 cx,
16743 )
16744 })
16745 .is_ok();
16746
16747 anyhow::Ok(Navigated::from_bool(opened))
16748 } else if num_locations == 0 {
16749 // If there is one url or file, open it directly
16750 match first_url_or_file {
16751 Some(Either::Left(url)) => {
16752 cx.update(|_, cx| cx.open_url(&url))?;
16753 Ok(Navigated::Yes)
16754 }
16755 Some(Either::Right(path)) => {
16756 let Some(workspace) = workspace else {
16757 return Ok(Navigated::No);
16758 };
16759
16760 workspace
16761 .update_in(cx, |workspace, window, cx| {
16762 workspace.open_resolved_path(path, window, cx)
16763 })?
16764 .await?;
16765 Ok(Navigated::Yes)
16766 }
16767 None => Ok(Navigated::No),
16768 }
16769 } else {
16770 let Some(workspace) = workspace else {
16771 return Ok(Navigated::No);
16772 };
16773
16774 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16775 let target_range = target_ranges.first().unwrap().clone();
16776
16777 editor.update_in(cx, |editor, window, cx| {
16778 let range = target_range.to_point(target_buffer.read(cx));
16779 let range = editor.range_for_match(&range, false);
16780 let range = collapse_multiline_range(range);
16781
16782 if !split
16783 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16784 {
16785 editor.go_to_singleton_buffer_range(range, window, cx);
16786 } else {
16787 let pane = workspace.read(cx).active_pane().clone();
16788 window.defer(cx, move |window, cx| {
16789 let target_editor: Entity<Self> =
16790 workspace.update(cx, |workspace, cx| {
16791 let pane = if split {
16792 workspace.adjacent_pane(window, cx)
16793 } else {
16794 workspace.active_pane().clone()
16795 };
16796
16797 workspace.open_project_item(
16798 pane,
16799 target_buffer.clone(),
16800 true,
16801 true,
16802 window,
16803 cx,
16804 )
16805 });
16806 target_editor.update(cx, |target_editor, cx| {
16807 // When selecting a definition in a different buffer, disable the nav history
16808 // to avoid creating a history entry at the previous cursor location.
16809 pane.update(cx, |pane, _| pane.disable_history());
16810 target_editor.go_to_singleton_buffer_range(range, window, cx);
16811 pane.update(cx, |pane, _| pane.enable_history());
16812 });
16813 });
16814 }
16815 Navigated::Yes
16816 })
16817 }
16818 })
16819 }
16820
16821 fn compute_target_location(
16822 &self,
16823 lsp_location: lsp::Location,
16824 server_id: LanguageServerId,
16825 window: &mut Window,
16826 cx: &mut Context<Self>,
16827 ) -> Task<anyhow::Result<Option<Location>>> {
16828 let Some(project) = self.project.clone() else {
16829 return Task::ready(Ok(None));
16830 };
16831
16832 cx.spawn_in(window, async move |editor, cx| {
16833 let location_task = editor.update(cx, |_, cx| {
16834 project.update(cx, |project, cx| {
16835 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16836 })
16837 })?;
16838 let location = Some({
16839 let target_buffer_handle = location_task.await.context("open local buffer")?;
16840 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16841 let target_start = target_buffer
16842 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16843 let target_end = target_buffer
16844 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16845 target_buffer.anchor_after(target_start)
16846 ..target_buffer.anchor_before(target_end)
16847 })?;
16848 Location {
16849 buffer: target_buffer_handle,
16850 range,
16851 }
16852 });
16853 Ok(location)
16854 })
16855 }
16856
16857 fn go_to_next_reference(
16858 &mut self,
16859 _: &GoToNextReference,
16860 window: &mut Window,
16861 cx: &mut Context<Self>,
16862 ) {
16863 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
16864 if let Some(task) = task {
16865 task.detach();
16866 };
16867 }
16868
16869 fn go_to_prev_reference(
16870 &mut self,
16871 _: &GoToPreviousReference,
16872 window: &mut Window,
16873 cx: &mut Context<Self>,
16874 ) {
16875 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
16876 if let Some(task) = task {
16877 task.detach();
16878 };
16879 }
16880
16881 pub fn go_to_reference_before_or_after_position(
16882 &mut self,
16883 direction: Direction,
16884 count: usize,
16885 window: &mut Window,
16886 cx: &mut Context<Self>,
16887 ) -> Option<Task<Result<()>>> {
16888 let selection = self.selections.newest_anchor();
16889 let head = selection.head();
16890
16891 let multi_buffer = self.buffer.read(cx);
16892
16893 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
16894 let workspace = self.workspace()?;
16895 let project = workspace.read(cx).project().clone();
16896 let references =
16897 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
16898 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
16899 let Some(locations) = references.await? else {
16900 return Ok(());
16901 };
16902
16903 if locations.is_empty() {
16904 // totally normal - the cursor may be on something which is not
16905 // a symbol (e.g. a keyword)
16906 log::info!("no references found under cursor");
16907 return Ok(());
16908 }
16909
16910 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
16911
16912 let multi_buffer_snapshot =
16913 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
16914
16915 let (locations, current_location_index) =
16916 multi_buffer.update(cx, |multi_buffer, cx| {
16917 let mut locations = locations
16918 .into_iter()
16919 .filter_map(|loc| {
16920 let start = multi_buffer.buffer_anchor_to_anchor(
16921 &loc.buffer,
16922 loc.range.start,
16923 cx,
16924 )?;
16925 let end = multi_buffer.buffer_anchor_to_anchor(
16926 &loc.buffer,
16927 loc.range.end,
16928 cx,
16929 )?;
16930 Some(start..end)
16931 })
16932 .collect::<Vec<_>>();
16933
16934 // There is an O(n) implementation, but given this list will be
16935 // small (usually <100 items), the extra O(log(n)) factor isn't
16936 // worth the (surprisingly large amount of) extra complexity.
16937 locations
16938 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
16939
16940 let head_offset = head.to_offset(&multi_buffer_snapshot);
16941
16942 let current_location_index = locations.iter().position(|loc| {
16943 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
16944 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
16945 });
16946
16947 (locations, current_location_index)
16948 })?;
16949
16950 let Some(current_location_index) = current_location_index else {
16951 // This indicates something has gone wrong, because we already
16952 // handle the "no references" case above
16953 log::error!(
16954 "failed to find current reference under cursor. Total references: {}",
16955 locations.len()
16956 );
16957 return Ok(());
16958 };
16959
16960 let destination_location_index = match direction {
16961 Direction::Next => (current_location_index + count) % locations.len(),
16962 Direction::Prev => {
16963 (current_location_index + locations.len() - count % locations.len())
16964 % locations.len()
16965 }
16966 };
16967
16968 // TODO(cameron): is this needed?
16969 // the thinking is to avoid "jumping to the current location" (avoid
16970 // polluting "jumplist" in vim terms)
16971 if current_location_index == destination_location_index {
16972 return Ok(());
16973 }
16974
16975 let Range { start, end } = locations[destination_location_index];
16976
16977 editor.update_in(cx, |editor, window, cx| {
16978 let effects = SelectionEffects::default();
16979
16980 editor.unfold_ranges(&[start..end], false, false, cx);
16981 editor.change_selections(effects, window, cx, |s| {
16982 s.select_ranges([start..start]);
16983 });
16984 })?;
16985
16986 Ok(())
16987 }))
16988 }
16989
16990 pub fn find_all_references(
16991 &mut self,
16992 _: &FindAllReferences,
16993 window: &mut Window,
16994 cx: &mut Context<Self>,
16995 ) -> Option<Task<Result<Navigated>>> {
16996 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16997 let multi_buffer = self.buffer.read(cx);
16998 let head = selection.head();
16999
17000 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17001 let head_anchor = multi_buffer_snapshot.anchor_at(
17002 head,
17003 if head < selection.tail() {
17004 Bias::Right
17005 } else {
17006 Bias::Left
17007 },
17008 );
17009
17010 match self
17011 .find_all_references_task_sources
17012 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17013 {
17014 Ok(_) => {
17015 log::info!(
17016 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17017 );
17018 return None;
17019 }
17020 Err(i) => {
17021 self.find_all_references_task_sources.insert(i, head_anchor);
17022 }
17023 }
17024
17025 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17026 let workspace = self.workspace()?;
17027 let project = workspace.read(cx).project().clone();
17028 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17029 Some(cx.spawn_in(window, async move |editor, cx| {
17030 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17031 if let Ok(i) = editor
17032 .find_all_references_task_sources
17033 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17034 {
17035 editor.find_all_references_task_sources.remove(i);
17036 }
17037 });
17038
17039 let Some(locations) = references.await? else {
17040 return anyhow::Ok(Navigated::No);
17041 };
17042 let mut locations = cx.update(|_, cx| {
17043 locations
17044 .into_iter()
17045 .map(|location| {
17046 let buffer = location.buffer.read(cx);
17047 (location.buffer, location.range.to_point(buffer))
17048 })
17049 .into_group_map()
17050 })?;
17051 if locations.is_empty() {
17052 return anyhow::Ok(Navigated::No);
17053 }
17054 for ranges in locations.values_mut() {
17055 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17056 ranges.dedup();
17057 }
17058
17059 workspace.update_in(cx, |workspace, window, cx| {
17060 let target = locations
17061 .iter()
17062 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17063 .map(|(buffer, location)| {
17064 buffer
17065 .read(cx)
17066 .text_for_range(location.clone())
17067 .collect::<String>()
17068 })
17069 .filter(|text| !text.contains('\n'))
17070 .unique()
17071 .take(3)
17072 .join(", ");
17073 let title = if target.is_empty() {
17074 "References".to_owned()
17075 } else {
17076 format!("References to {target}")
17077 };
17078 Self::open_locations_in_multibuffer(
17079 workspace,
17080 locations,
17081 title,
17082 false,
17083 MultibufferSelectionMode::First,
17084 window,
17085 cx,
17086 );
17087 Navigated::Yes
17088 })
17089 }))
17090 }
17091
17092 /// Opens a multibuffer with the given project locations in it
17093 pub fn open_locations_in_multibuffer(
17094 workspace: &mut Workspace,
17095 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17096 title: String,
17097 split: bool,
17098 multibuffer_selection_mode: MultibufferSelectionMode,
17099 window: &mut Window,
17100 cx: &mut Context<Workspace>,
17101 ) {
17102 if locations.is_empty() {
17103 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17104 return;
17105 }
17106
17107 let capability = workspace.project().read(cx).capability();
17108 let mut ranges = <Vec<Range<Anchor>>>::new();
17109
17110 // a key to find existing multibuffer editors with the same set of locations
17111 // to prevent us from opening more and more multibuffer tabs for searches and the like
17112 let mut key = (title.clone(), vec![]);
17113 let excerpt_buffer = cx.new(|cx| {
17114 let key = &mut key.1;
17115 let mut multibuffer = MultiBuffer::new(capability);
17116 for (buffer, mut ranges_for_buffer) in locations {
17117 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17118 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17119 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17120 PathKey::for_buffer(&buffer, cx),
17121 buffer.clone(),
17122 ranges_for_buffer,
17123 multibuffer_context_lines(cx),
17124 cx,
17125 );
17126 ranges.extend(new_ranges)
17127 }
17128
17129 multibuffer.with_title(title)
17130 });
17131 let existing = workspace.active_pane().update(cx, |pane, cx| {
17132 pane.items()
17133 .filter_map(|item| item.downcast::<Editor>())
17134 .find(|editor| {
17135 editor
17136 .read(cx)
17137 .lookup_key
17138 .as_ref()
17139 .and_then(|it| {
17140 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17141 })
17142 .is_some_and(|it| *it == key)
17143 })
17144 });
17145 let editor = existing.unwrap_or_else(|| {
17146 cx.new(|cx| {
17147 let mut editor = Editor::for_multibuffer(
17148 excerpt_buffer,
17149 Some(workspace.project().clone()),
17150 window,
17151 cx,
17152 );
17153 editor.lookup_key = Some(Box::new(key));
17154 editor
17155 })
17156 });
17157 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17158 MultibufferSelectionMode::First => {
17159 if let Some(first_range) = ranges.first() {
17160 editor.change_selections(
17161 SelectionEffects::no_scroll(),
17162 window,
17163 cx,
17164 |selections| {
17165 selections.clear_disjoint();
17166 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17167 },
17168 );
17169 }
17170 editor.highlight_background::<Self>(
17171 &ranges,
17172 |theme| theme.colors().editor_highlighted_line_background,
17173 cx,
17174 );
17175 }
17176 MultibufferSelectionMode::All => {
17177 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17178 selections.clear_disjoint();
17179 selections.select_anchor_ranges(ranges);
17180 });
17181 }
17182 });
17183
17184 let item = Box::new(editor);
17185 let item_id = item.item_id();
17186
17187 if split {
17188 let pane = workspace.adjacent_pane(window, cx);
17189 workspace.add_item(pane, item, None, true, true, window, cx);
17190 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17191 let (preview_item_id, preview_item_idx) =
17192 workspace.active_pane().read_with(cx, |pane, _| {
17193 (pane.preview_item_id(), pane.preview_item_idx())
17194 });
17195
17196 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17197
17198 if let Some(preview_item_id) = preview_item_id {
17199 workspace.active_pane().update(cx, |pane, cx| {
17200 pane.remove_item(preview_item_id, false, false, window, cx);
17201 });
17202 }
17203 } else {
17204 workspace.add_item_to_active_pane(item, None, true, window, cx);
17205 }
17206 workspace.active_pane().update(cx, |pane, cx| {
17207 pane.set_preview_item_id(Some(item_id), cx);
17208 });
17209 }
17210
17211 pub fn rename(
17212 &mut self,
17213 _: &Rename,
17214 window: &mut Window,
17215 cx: &mut Context<Self>,
17216 ) -> Option<Task<Result<()>>> {
17217 use language::ToOffset as _;
17218
17219 let provider = self.semantics_provider.clone()?;
17220 let selection = self.selections.newest_anchor().clone();
17221 let (cursor_buffer, cursor_buffer_position) = self
17222 .buffer
17223 .read(cx)
17224 .text_anchor_for_position(selection.head(), cx)?;
17225 let (tail_buffer, cursor_buffer_position_end) = self
17226 .buffer
17227 .read(cx)
17228 .text_anchor_for_position(selection.tail(), cx)?;
17229 if tail_buffer != cursor_buffer {
17230 return None;
17231 }
17232
17233 let snapshot = cursor_buffer.read(cx).snapshot();
17234 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17235 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17236 let prepare_rename = provider
17237 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17238 .unwrap_or_else(|| Task::ready(Ok(None)));
17239 drop(snapshot);
17240
17241 Some(cx.spawn_in(window, async move |this, cx| {
17242 let rename_range = if let Some(range) = prepare_rename.await? {
17243 Some(range)
17244 } else {
17245 this.update(cx, |this, cx| {
17246 let buffer = this.buffer.read(cx).snapshot(cx);
17247 let mut buffer_highlights = this
17248 .document_highlights_for_position(selection.head(), &buffer)
17249 .filter(|highlight| {
17250 highlight.start.excerpt_id == selection.head().excerpt_id
17251 && highlight.end.excerpt_id == selection.head().excerpt_id
17252 });
17253 buffer_highlights
17254 .next()
17255 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17256 })?
17257 };
17258 if let Some(rename_range) = rename_range {
17259 this.update_in(cx, |this, window, cx| {
17260 let snapshot = cursor_buffer.read(cx).snapshot();
17261 let rename_buffer_range = rename_range.to_offset(&snapshot);
17262 let cursor_offset_in_rename_range =
17263 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17264 let cursor_offset_in_rename_range_end =
17265 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17266
17267 this.take_rename(false, window, cx);
17268 let buffer = this.buffer.read(cx).read(cx);
17269 let cursor_offset = selection.head().to_offset(&buffer);
17270 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17271 let rename_end = rename_start + rename_buffer_range.len();
17272 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17273 let mut old_highlight_id = None;
17274 let old_name: Arc<str> = buffer
17275 .chunks(rename_start..rename_end, true)
17276 .map(|chunk| {
17277 if old_highlight_id.is_none() {
17278 old_highlight_id = chunk.syntax_highlight_id;
17279 }
17280 chunk.text
17281 })
17282 .collect::<String>()
17283 .into();
17284
17285 drop(buffer);
17286
17287 // Position the selection in the rename editor so that it matches the current selection.
17288 this.show_local_selections = false;
17289 let rename_editor = cx.new(|cx| {
17290 let mut editor = Editor::single_line(window, cx);
17291 editor.buffer.update(cx, |buffer, cx| {
17292 buffer.edit([(0..0, old_name.clone())], None, cx)
17293 });
17294 let rename_selection_range = match cursor_offset_in_rename_range
17295 .cmp(&cursor_offset_in_rename_range_end)
17296 {
17297 Ordering::Equal => {
17298 editor.select_all(&SelectAll, window, cx);
17299 return editor;
17300 }
17301 Ordering::Less => {
17302 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17303 }
17304 Ordering::Greater => {
17305 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17306 }
17307 };
17308 if rename_selection_range.end > old_name.len() {
17309 editor.select_all(&SelectAll, window, cx);
17310 } else {
17311 editor.change_selections(Default::default(), window, cx, |s| {
17312 s.select_ranges([rename_selection_range]);
17313 });
17314 }
17315 editor
17316 });
17317 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17318 if e == &EditorEvent::Focused {
17319 cx.emit(EditorEvent::FocusedIn)
17320 }
17321 })
17322 .detach();
17323
17324 let write_highlights =
17325 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17326 let read_highlights =
17327 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17328 let ranges = write_highlights
17329 .iter()
17330 .flat_map(|(_, ranges)| ranges.iter())
17331 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17332 .cloned()
17333 .collect();
17334
17335 this.highlight_text::<Rename>(
17336 ranges,
17337 HighlightStyle {
17338 fade_out: Some(0.6),
17339 ..Default::default()
17340 },
17341 cx,
17342 );
17343 let rename_focus_handle = rename_editor.focus_handle(cx);
17344 window.focus(&rename_focus_handle);
17345 let block_id = this.insert_blocks(
17346 [BlockProperties {
17347 style: BlockStyle::Flex,
17348 placement: BlockPlacement::Below(range.start),
17349 height: Some(1),
17350 render: Arc::new({
17351 let rename_editor = rename_editor.clone();
17352 move |cx: &mut BlockContext| {
17353 let mut text_style = cx.editor_style.text.clone();
17354 if let Some(highlight_style) = old_highlight_id
17355 .and_then(|h| h.style(&cx.editor_style.syntax))
17356 {
17357 text_style = text_style.highlight(highlight_style);
17358 }
17359 div()
17360 .block_mouse_except_scroll()
17361 .pl(cx.anchor_x)
17362 .child(EditorElement::new(
17363 &rename_editor,
17364 EditorStyle {
17365 background: cx.theme().system().transparent,
17366 local_player: cx.editor_style.local_player,
17367 text: text_style,
17368 scrollbar_width: cx.editor_style.scrollbar_width,
17369 syntax: cx.editor_style.syntax.clone(),
17370 status: cx.editor_style.status.clone(),
17371 inlay_hints_style: HighlightStyle {
17372 font_weight: Some(FontWeight::BOLD),
17373 ..make_inlay_hints_style(cx.app)
17374 },
17375 edit_prediction_styles: make_suggestion_styles(
17376 cx.app,
17377 ),
17378 ..EditorStyle::default()
17379 },
17380 ))
17381 .into_any_element()
17382 }
17383 }),
17384 priority: 0,
17385 }],
17386 Some(Autoscroll::fit()),
17387 cx,
17388 )[0];
17389 this.pending_rename = Some(RenameState {
17390 range,
17391 old_name,
17392 editor: rename_editor,
17393 block_id,
17394 });
17395 })?;
17396 }
17397
17398 Ok(())
17399 }))
17400 }
17401
17402 pub fn confirm_rename(
17403 &mut self,
17404 _: &ConfirmRename,
17405 window: &mut Window,
17406 cx: &mut Context<Self>,
17407 ) -> Option<Task<Result<()>>> {
17408 let rename = self.take_rename(false, window, cx)?;
17409 let workspace = self.workspace()?.downgrade();
17410 let (buffer, start) = self
17411 .buffer
17412 .read(cx)
17413 .text_anchor_for_position(rename.range.start, cx)?;
17414 let (end_buffer, _) = self
17415 .buffer
17416 .read(cx)
17417 .text_anchor_for_position(rename.range.end, cx)?;
17418 if buffer != end_buffer {
17419 return None;
17420 }
17421
17422 let old_name = rename.old_name;
17423 let new_name = rename.editor.read(cx).text(cx);
17424
17425 let rename = self.semantics_provider.as_ref()?.perform_rename(
17426 &buffer,
17427 start,
17428 new_name.clone(),
17429 cx,
17430 )?;
17431
17432 Some(cx.spawn_in(window, async move |editor, cx| {
17433 let project_transaction = rename.await?;
17434 Self::open_project_transaction(
17435 &editor,
17436 workspace,
17437 project_transaction,
17438 format!("Rename: {} → {}", old_name, new_name),
17439 cx,
17440 )
17441 .await?;
17442
17443 editor.update(cx, |editor, cx| {
17444 editor.refresh_document_highlights(cx);
17445 })?;
17446 Ok(())
17447 }))
17448 }
17449
17450 fn take_rename(
17451 &mut self,
17452 moving_cursor: bool,
17453 window: &mut Window,
17454 cx: &mut Context<Self>,
17455 ) -> Option<RenameState> {
17456 let rename = self.pending_rename.take()?;
17457 if rename.editor.focus_handle(cx).is_focused(window) {
17458 window.focus(&self.focus_handle);
17459 }
17460
17461 self.remove_blocks(
17462 [rename.block_id].into_iter().collect(),
17463 Some(Autoscroll::fit()),
17464 cx,
17465 );
17466 self.clear_highlights::<Rename>(cx);
17467 self.show_local_selections = true;
17468
17469 if moving_cursor {
17470 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17471 editor
17472 .selections
17473 .newest::<usize>(&editor.display_snapshot(cx))
17474 .head()
17475 });
17476
17477 // Update the selection to match the position of the selection inside
17478 // the rename editor.
17479 let snapshot = self.buffer.read(cx).read(cx);
17480 let rename_range = rename.range.to_offset(&snapshot);
17481 let cursor_in_editor = snapshot
17482 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17483 .min(rename_range.end);
17484 drop(snapshot);
17485
17486 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17487 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17488 });
17489 } else {
17490 self.refresh_document_highlights(cx);
17491 }
17492
17493 Some(rename)
17494 }
17495
17496 pub fn pending_rename(&self) -> Option<&RenameState> {
17497 self.pending_rename.as_ref()
17498 }
17499
17500 fn format(
17501 &mut self,
17502 _: &Format,
17503 window: &mut Window,
17504 cx: &mut Context<Self>,
17505 ) -> Option<Task<Result<()>>> {
17506 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17507
17508 let project = match &self.project {
17509 Some(project) => project.clone(),
17510 None => return None,
17511 };
17512
17513 Some(self.perform_format(
17514 project,
17515 FormatTrigger::Manual,
17516 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17517 window,
17518 cx,
17519 ))
17520 }
17521
17522 fn format_selections(
17523 &mut self,
17524 _: &FormatSelections,
17525 window: &mut Window,
17526 cx: &mut Context<Self>,
17527 ) -> Option<Task<Result<()>>> {
17528 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17529
17530 let project = match &self.project {
17531 Some(project) => project.clone(),
17532 None => return None,
17533 };
17534
17535 let ranges = self
17536 .selections
17537 .all_adjusted(&self.display_snapshot(cx))
17538 .into_iter()
17539 .map(|selection| selection.range())
17540 .collect_vec();
17541
17542 Some(self.perform_format(
17543 project,
17544 FormatTrigger::Manual,
17545 FormatTarget::Ranges(ranges),
17546 window,
17547 cx,
17548 ))
17549 }
17550
17551 fn perform_format(
17552 &mut self,
17553 project: Entity<Project>,
17554 trigger: FormatTrigger,
17555 target: FormatTarget,
17556 window: &mut Window,
17557 cx: &mut Context<Self>,
17558 ) -> Task<Result<()>> {
17559 let buffer = self.buffer.clone();
17560 let (buffers, target) = match target {
17561 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17562 FormatTarget::Ranges(selection_ranges) => {
17563 let multi_buffer = buffer.read(cx);
17564 let snapshot = multi_buffer.read(cx);
17565 let mut buffers = HashSet::default();
17566 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17567 BTreeMap::new();
17568 for selection_range in selection_ranges {
17569 for (buffer, buffer_range, _) in
17570 snapshot.range_to_buffer_ranges(selection_range)
17571 {
17572 let buffer_id = buffer.remote_id();
17573 let start = buffer.anchor_before(buffer_range.start);
17574 let end = buffer.anchor_after(buffer_range.end);
17575 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17576 buffer_id_to_ranges
17577 .entry(buffer_id)
17578 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17579 .or_insert_with(|| vec![start..end]);
17580 }
17581 }
17582 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17583 }
17584 };
17585
17586 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17587 let selections_prev = transaction_id_prev
17588 .and_then(|transaction_id_prev| {
17589 // default to selections as they were after the last edit, if we have them,
17590 // instead of how they are now.
17591 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17592 // will take you back to where you made the last edit, instead of staying where you scrolled
17593 self.selection_history
17594 .transaction(transaction_id_prev)
17595 .map(|t| t.0.clone())
17596 })
17597 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17598
17599 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17600 let format = project.update(cx, |project, cx| {
17601 project.format(buffers, target, true, trigger, cx)
17602 });
17603
17604 cx.spawn_in(window, async move |editor, cx| {
17605 let transaction = futures::select_biased! {
17606 transaction = format.log_err().fuse() => transaction,
17607 () = timeout => {
17608 log::warn!("timed out waiting for formatting");
17609 None
17610 }
17611 };
17612
17613 buffer
17614 .update(cx, |buffer, cx| {
17615 if let Some(transaction) = transaction
17616 && !buffer.is_singleton()
17617 {
17618 buffer.push_transaction(&transaction.0, cx);
17619 }
17620 cx.notify();
17621 })
17622 .ok();
17623
17624 if let Some(transaction_id_now) =
17625 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17626 {
17627 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17628 if has_new_transaction {
17629 _ = editor.update(cx, |editor, _| {
17630 editor
17631 .selection_history
17632 .insert_transaction(transaction_id_now, selections_prev);
17633 });
17634 }
17635 }
17636
17637 Ok(())
17638 })
17639 }
17640
17641 fn organize_imports(
17642 &mut self,
17643 _: &OrganizeImports,
17644 window: &mut Window,
17645 cx: &mut Context<Self>,
17646 ) -> Option<Task<Result<()>>> {
17647 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17648 let project = match &self.project {
17649 Some(project) => project.clone(),
17650 None => return None,
17651 };
17652 Some(self.perform_code_action_kind(
17653 project,
17654 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17655 window,
17656 cx,
17657 ))
17658 }
17659
17660 fn perform_code_action_kind(
17661 &mut self,
17662 project: Entity<Project>,
17663 kind: CodeActionKind,
17664 window: &mut Window,
17665 cx: &mut Context<Self>,
17666 ) -> Task<Result<()>> {
17667 let buffer = self.buffer.clone();
17668 let buffers = buffer.read(cx).all_buffers();
17669 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17670 let apply_action = project.update(cx, |project, cx| {
17671 project.apply_code_action_kind(buffers, kind, true, cx)
17672 });
17673 cx.spawn_in(window, async move |_, cx| {
17674 let transaction = futures::select_biased! {
17675 () = timeout => {
17676 log::warn!("timed out waiting for executing code action");
17677 None
17678 }
17679 transaction = apply_action.log_err().fuse() => transaction,
17680 };
17681 buffer
17682 .update(cx, |buffer, cx| {
17683 // check if we need this
17684 if let Some(transaction) = transaction
17685 && !buffer.is_singleton()
17686 {
17687 buffer.push_transaction(&transaction.0, cx);
17688 }
17689 cx.notify();
17690 })
17691 .ok();
17692 Ok(())
17693 })
17694 }
17695
17696 pub fn restart_language_server(
17697 &mut self,
17698 _: &RestartLanguageServer,
17699 _: &mut Window,
17700 cx: &mut Context<Self>,
17701 ) {
17702 if let Some(project) = self.project.clone() {
17703 self.buffer.update(cx, |multi_buffer, cx| {
17704 project.update(cx, |project, cx| {
17705 project.restart_language_servers_for_buffers(
17706 multi_buffer.all_buffers().into_iter().collect(),
17707 HashSet::default(),
17708 cx,
17709 );
17710 });
17711 })
17712 }
17713 }
17714
17715 pub fn stop_language_server(
17716 &mut self,
17717 _: &StopLanguageServer,
17718 _: &mut Window,
17719 cx: &mut Context<Self>,
17720 ) {
17721 if let Some(project) = self.project.clone() {
17722 self.buffer.update(cx, |multi_buffer, cx| {
17723 project.update(cx, |project, cx| {
17724 project.stop_language_servers_for_buffers(
17725 multi_buffer.all_buffers().into_iter().collect(),
17726 HashSet::default(),
17727 cx,
17728 );
17729 });
17730 });
17731 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17732 }
17733 }
17734
17735 fn cancel_language_server_work(
17736 workspace: &mut Workspace,
17737 _: &actions::CancelLanguageServerWork,
17738 _: &mut Window,
17739 cx: &mut Context<Workspace>,
17740 ) {
17741 let project = workspace.project();
17742 let buffers = workspace
17743 .active_item(cx)
17744 .and_then(|item| item.act_as::<Editor>(cx))
17745 .map_or(HashSet::default(), |editor| {
17746 editor.read(cx).buffer.read(cx).all_buffers()
17747 });
17748 project.update(cx, |project, cx| {
17749 project.cancel_language_server_work_for_buffers(buffers, cx);
17750 });
17751 }
17752
17753 fn show_character_palette(
17754 &mut self,
17755 _: &ShowCharacterPalette,
17756 window: &mut Window,
17757 _: &mut Context<Self>,
17758 ) {
17759 window.show_character_palette();
17760 }
17761
17762 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17763 if !self.diagnostics_enabled() {
17764 return;
17765 }
17766
17767 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17768 let buffer = self.buffer.read(cx).snapshot(cx);
17769 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17770 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17771 let is_valid = buffer
17772 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17773 .any(|entry| {
17774 entry.diagnostic.is_primary
17775 && !entry.range.is_empty()
17776 && entry.range.start == primary_range_start
17777 && entry.diagnostic.message == active_diagnostics.active_message
17778 });
17779
17780 if !is_valid {
17781 self.dismiss_diagnostics(cx);
17782 }
17783 }
17784 }
17785
17786 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17787 match &self.active_diagnostics {
17788 ActiveDiagnostic::Group(group) => Some(group),
17789 _ => None,
17790 }
17791 }
17792
17793 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17794 if !self.diagnostics_enabled() {
17795 return;
17796 }
17797 self.dismiss_diagnostics(cx);
17798 self.active_diagnostics = ActiveDiagnostic::All;
17799 }
17800
17801 fn activate_diagnostics(
17802 &mut self,
17803 buffer_id: BufferId,
17804 diagnostic: DiagnosticEntryRef<'_, usize>,
17805 window: &mut Window,
17806 cx: &mut Context<Self>,
17807 ) {
17808 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17809 return;
17810 }
17811 self.dismiss_diagnostics(cx);
17812 let snapshot = self.snapshot(window, cx);
17813 let buffer = self.buffer.read(cx).snapshot(cx);
17814 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17815 return;
17816 };
17817
17818 let diagnostic_group = buffer
17819 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17820 .collect::<Vec<_>>();
17821
17822 let blocks =
17823 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17824
17825 let blocks = self.display_map.update(cx, |display_map, cx| {
17826 display_map.insert_blocks(blocks, cx).into_iter().collect()
17827 });
17828 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17829 active_range: buffer.anchor_before(diagnostic.range.start)
17830 ..buffer.anchor_after(diagnostic.range.end),
17831 active_message: diagnostic.diagnostic.message.clone(),
17832 group_id: diagnostic.diagnostic.group_id,
17833 blocks,
17834 });
17835 cx.notify();
17836 }
17837
17838 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17839 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17840 return;
17841 };
17842
17843 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17844 if let ActiveDiagnostic::Group(group) = prev {
17845 self.display_map.update(cx, |display_map, cx| {
17846 display_map.remove_blocks(group.blocks, cx);
17847 });
17848 cx.notify();
17849 }
17850 }
17851
17852 /// Disable inline diagnostics rendering for this editor.
17853 pub fn disable_inline_diagnostics(&mut self) {
17854 self.inline_diagnostics_enabled = false;
17855 self.inline_diagnostics_update = Task::ready(());
17856 self.inline_diagnostics.clear();
17857 }
17858
17859 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17860 self.diagnostics_enabled = false;
17861 self.dismiss_diagnostics(cx);
17862 self.inline_diagnostics_update = Task::ready(());
17863 self.inline_diagnostics.clear();
17864 }
17865
17866 pub fn disable_word_completions(&mut self) {
17867 self.word_completions_enabled = false;
17868 }
17869
17870 pub fn diagnostics_enabled(&self) -> bool {
17871 self.diagnostics_enabled && self.mode.is_full()
17872 }
17873
17874 pub fn inline_diagnostics_enabled(&self) -> bool {
17875 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17876 }
17877
17878 pub fn show_inline_diagnostics(&self) -> bool {
17879 self.show_inline_diagnostics
17880 }
17881
17882 pub fn toggle_inline_diagnostics(
17883 &mut self,
17884 _: &ToggleInlineDiagnostics,
17885 window: &mut Window,
17886 cx: &mut Context<Editor>,
17887 ) {
17888 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17889 self.refresh_inline_diagnostics(false, window, cx);
17890 }
17891
17892 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17893 self.diagnostics_max_severity = severity;
17894 self.display_map.update(cx, |display_map, _| {
17895 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17896 });
17897 }
17898
17899 pub fn toggle_diagnostics(
17900 &mut self,
17901 _: &ToggleDiagnostics,
17902 window: &mut Window,
17903 cx: &mut Context<Editor>,
17904 ) {
17905 if !self.diagnostics_enabled() {
17906 return;
17907 }
17908
17909 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17910 EditorSettings::get_global(cx)
17911 .diagnostics_max_severity
17912 .filter(|severity| severity != &DiagnosticSeverity::Off)
17913 .unwrap_or(DiagnosticSeverity::Hint)
17914 } else {
17915 DiagnosticSeverity::Off
17916 };
17917 self.set_max_diagnostics_severity(new_severity, cx);
17918 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17919 self.active_diagnostics = ActiveDiagnostic::None;
17920 self.inline_diagnostics_update = Task::ready(());
17921 self.inline_diagnostics.clear();
17922 } else {
17923 self.refresh_inline_diagnostics(false, window, cx);
17924 }
17925
17926 cx.notify();
17927 }
17928
17929 pub fn toggle_minimap(
17930 &mut self,
17931 _: &ToggleMinimap,
17932 window: &mut Window,
17933 cx: &mut Context<Editor>,
17934 ) {
17935 if self.supports_minimap(cx) {
17936 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17937 }
17938 }
17939
17940 fn refresh_inline_diagnostics(
17941 &mut self,
17942 debounce: bool,
17943 window: &mut Window,
17944 cx: &mut Context<Self>,
17945 ) {
17946 let max_severity = ProjectSettings::get_global(cx)
17947 .diagnostics
17948 .inline
17949 .max_severity
17950 .unwrap_or(self.diagnostics_max_severity);
17951
17952 if !self.inline_diagnostics_enabled()
17953 || !self.diagnostics_enabled()
17954 || !self.show_inline_diagnostics
17955 || max_severity == DiagnosticSeverity::Off
17956 {
17957 self.inline_diagnostics_update = Task::ready(());
17958 self.inline_diagnostics.clear();
17959 return;
17960 }
17961
17962 let debounce_ms = ProjectSettings::get_global(cx)
17963 .diagnostics
17964 .inline
17965 .update_debounce_ms;
17966 let debounce = if debounce && debounce_ms > 0 {
17967 Some(Duration::from_millis(debounce_ms))
17968 } else {
17969 None
17970 };
17971 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17972 if let Some(debounce) = debounce {
17973 cx.background_executor().timer(debounce).await;
17974 }
17975 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17976 editor
17977 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17978 .ok()
17979 }) else {
17980 return;
17981 };
17982
17983 let new_inline_diagnostics = cx
17984 .background_spawn(async move {
17985 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17986 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17987 let message = diagnostic_entry
17988 .diagnostic
17989 .message
17990 .split_once('\n')
17991 .map(|(line, _)| line)
17992 .map(SharedString::new)
17993 .unwrap_or_else(|| {
17994 SharedString::new(&*diagnostic_entry.diagnostic.message)
17995 });
17996 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17997 let (Ok(i) | Err(i)) = inline_diagnostics
17998 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17999 inline_diagnostics.insert(
18000 i,
18001 (
18002 start_anchor,
18003 InlineDiagnostic {
18004 message,
18005 group_id: diagnostic_entry.diagnostic.group_id,
18006 start: diagnostic_entry.range.start.to_point(&snapshot),
18007 is_primary: diagnostic_entry.diagnostic.is_primary,
18008 severity: diagnostic_entry.diagnostic.severity,
18009 },
18010 ),
18011 );
18012 }
18013 inline_diagnostics
18014 })
18015 .await;
18016
18017 editor
18018 .update(cx, |editor, cx| {
18019 editor.inline_diagnostics = new_inline_diagnostics;
18020 cx.notify();
18021 })
18022 .ok();
18023 });
18024 }
18025
18026 fn pull_diagnostics(
18027 &mut self,
18028 buffer_id: Option<BufferId>,
18029 window: &Window,
18030 cx: &mut Context<Self>,
18031 ) -> Option<()> {
18032 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18033 return None;
18034 }
18035 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18036 .diagnostics
18037 .lsp_pull_diagnostics;
18038 if !pull_diagnostics_settings.enabled {
18039 return None;
18040 }
18041 let project = self.project()?.downgrade();
18042 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18043 let mut buffers = self.buffer.read(cx).all_buffers();
18044 buffers.retain(|buffer| {
18045 let buffer_id_to_retain = buffer.read(cx).remote_id();
18046 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18047 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18048 });
18049 if buffers.is_empty() {
18050 self.pull_diagnostics_task = Task::ready(());
18051 return None;
18052 }
18053
18054 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18055 cx.background_executor().timer(debounce).await;
18056
18057 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18058 buffers
18059 .into_iter()
18060 .filter_map(|buffer| {
18061 project
18062 .update(cx, |project, cx| {
18063 project.lsp_store().update(cx, |lsp_store, cx| {
18064 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18065 })
18066 })
18067 .ok()
18068 })
18069 .collect::<FuturesUnordered<_>>()
18070 }) else {
18071 return;
18072 };
18073
18074 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18075 match pull_task {
18076 Ok(()) => {
18077 if editor
18078 .update_in(cx, |editor, window, cx| {
18079 editor.update_diagnostics_state(window, cx);
18080 })
18081 .is_err()
18082 {
18083 return;
18084 }
18085 }
18086 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18087 }
18088 }
18089 });
18090
18091 Some(())
18092 }
18093
18094 pub fn set_selections_from_remote(
18095 &mut self,
18096 selections: Vec<Selection<Anchor>>,
18097 pending_selection: Option<Selection<Anchor>>,
18098 window: &mut Window,
18099 cx: &mut Context<Self>,
18100 ) {
18101 let old_cursor_position = self.selections.newest_anchor().head();
18102 self.selections
18103 .change_with(&self.display_snapshot(cx), |s| {
18104 s.select_anchors(selections);
18105 if let Some(pending_selection) = pending_selection {
18106 s.set_pending(pending_selection, SelectMode::Character);
18107 } else {
18108 s.clear_pending();
18109 }
18110 });
18111 self.selections_did_change(
18112 false,
18113 &old_cursor_position,
18114 SelectionEffects::default(),
18115 window,
18116 cx,
18117 );
18118 }
18119
18120 pub fn transact(
18121 &mut self,
18122 window: &mut Window,
18123 cx: &mut Context<Self>,
18124 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18125 ) -> Option<TransactionId> {
18126 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18127 this.start_transaction_at(Instant::now(), window, cx);
18128 update(this, window, cx);
18129 this.end_transaction_at(Instant::now(), cx)
18130 })
18131 }
18132
18133 pub fn start_transaction_at(
18134 &mut self,
18135 now: Instant,
18136 window: &mut Window,
18137 cx: &mut Context<Self>,
18138 ) -> Option<TransactionId> {
18139 self.end_selection(window, cx);
18140 if let Some(tx_id) = self
18141 .buffer
18142 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18143 {
18144 self.selection_history
18145 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18146 cx.emit(EditorEvent::TransactionBegun {
18147 transaction_id: tx_id,
18148 });
18149 Some(tx_id)
18150 } else {
18151 None
18152 }
18153 }
18154
18155 pub fn end_transaction_at(
18156 &mut self,
18157 now: Instant,
18158 cx: &mut Context<Self>,
18159 ) -> Option<TransactionId> {
18160 if let Some(transaction_id) = self
18161 .buffer
18162 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18163 {
18164 if let Some((_, end_selections)) =
18165 self.selection_history.transaction_mut(transaction_id)
18166 {
18167 *end_selections = Some(self.selections.disjoint_anchors_arc());
18168 } else {
18169 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18170 }
18171
18172 cx.emit(EditorEvent::Edited { transaction_id });
18173 Some(transaction_id)
18174 } else {
18175 None
18176 }
18177 }
18178
18179 pub fn modify_transaction_selection_history(
18180 &mut self,
18181 transaction_id: TransactionId,
18182 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18183 ) -> bool {
18184 self.selection_history
18185 .transaction_mut(transaction_id)
18186 .map(modify)
18187 .is_some()
18188 }
18189
18190 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18191 if self.selection_mark_mode {
18192 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18193 s.move_with(|_, sel| {
18194 sel.collapse_to(sel.head(), SelectionGoal::None);
18195 });
18196 })
18197 }
18198 self.selection_mark_mode = true;
18199 cx.notify();
18200 }
18201
18202 pub fn swap_selection_ends(
18203 &mut self,
18204 _: &actions::SwapSelectionEnds,
18205 window: &mut Window,
18206 cx: &mut Context<Self>,
18207 ) {
18208 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18209 s.move_with(|_, sel| {
18210 if sel.start != sel.end {
18211 sel.reversed = !sel.reversed
18212 }
18213 });
18214 });
18215 self.request_autoscroll(Autoscroll::newest(), cx);
18216 cx.notify();
18217 }
18218
18219 pub fn toggle_focus(
18220 workspace: &mut Workspace,
18221 _: &actions::ToggleFocus,
18222 window: &mut Window,
18223 cx: &mut Context<Workspace>,
18224 ) {
18225 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18226 return;
18227 };
18228 workspace.activate_item(&item, true, true, window, cx);
18229 }
18230
18231 pub fn toggle_fold(
18232 &mut self,
18233 _: &actions::ToggleFold,
18234 window: &mut Window,
18235 cx: &mut Context<Self>,
18236 ) {
18237 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18238 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18239 let selection = self.selections.newest::<Point>(&display_map);
18240
18241 let range = if selection.is_empty() {
18242 let point = selection.head().to_display_point(&display_map);
18243 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18244 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18245 .to_point(&display_map);
18246 start..end
18247 } else {
18248 selection.range()
18249 };
18250 if display_map.folds_in_range(range).next().is_some() {
18251 self.unfold_lines(&Default::default(), window, cx)
18252 } else {
18253 self.fold(&Default::default(), window, cx)
18254 }
18255 } else {
18256 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18257 let buffer_ids: HashSet<_> = self
18258 .selections
18259 .disjoint_anchor_ranges()
18260 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18261 .collect();
18262
18263 let should_unfold = buffer_ids
18264 .iter()
18265 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18266
18267 for buffer_id in buffer_ids {
18268 if should_unfold {
18269 self.unfold_buffer(buffer_id, cx);
18270 } else {
18271 self.fold_buffer(buffer_id, cx);
18272 }
18273 }
18274 }
18275 }
18276
18277 pub fn toggle_fold_recursive(
18278 &mut self,
18279 _: &actions::ToggleFoldRecursive,
18280 window: &mut Window,
18281 cx: &mut Context<Self>,
18282 ) {
18283 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18284
18285 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18286 let range = if selection.is_empty() {
18287 let point = selection.head().to_display_point(&display_map);
18288 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18289 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18290 .to_point(&display_map);
18291 start..end
18292 } else {
18293 selection.range()
18294 };
18295 if display_map.folds_in_range(range).next().is_some() {
18296 self.unfold_recursive(&Default::default(), window, cx)
18297 } else {
18298 self.fold_recursive(&Default::default(), window, cx)
18299 }
18300 }
18301
18302 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18303 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18304 let mut to_fold = Vec::new();
18305 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18306 let selections = self.selections.all_adjusted(&display_map);
18307
18308 for selection in selections {
18309 let range = selection.range().sorted();
18310 let buffer_start_row = range.start.row;
18311
18312 if range.start.row != range.end.row {
18313 let mut found = false;
18314 let mut row = range.start.row;
18315 while row <= range.end.row {
18316 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18317 {
18318 found = true;
18319 row = crease.range().end.row + 1;
18320 to_fold.push(crease);
18321 } else {
18322 row += 1
18323 }
18324 }
18325 if found {
18326 continue;
18327 }
18328 }
18329
18330 for row in (0..=range.start.row).rev() {
18331 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18332 && crease.range().end.row >= buffer_start_row
18333 {
18334 to_fold.push(crease);
18335 if row <= range.start.row {
18336 break;
18337 }
18338 }
18339 }
18340 }
18341
18342 self.fold_creases(to_fold, true, window, cx);
18343 } else {
18344 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18345 let buffer_ids = self
18346 .selections
18347 .disjoint_anchor_ranges()
18348 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18349 .collect::<HashSet<_>>();
18350 for buffer_id in buffer_ids {
18351 self.fold_buffer(buffer_id, cx);
18352 }
18353 }
18354 }
18355
18356 pub fn toggle_fold_all(
18357 &mut self,
18358 _: &actions::ToggleFoldAll,
18359 window: &mut Window,
18360 cx: &mut Context<Self>,
18361 ) {
18362 if self.buffer.read(cx).is_singleton() {
18363 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18364 let has_folds = display_map
18365 .folds_in_range(0..display_map.buffer_snapshot().len())
18366 .next()
18367 .is_some();
18368
18369 if has_folds {
18370 self.unfold_all(&actions::UnfoldAll, window, cx);
18371 } else {
18372 self.fold_all(&actions::FoldAll, window, cx);
18373 }
18374 } else {
18375 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18376 let should_unfold = buffer_ids
18377 .iter()
18378 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18379
18380 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18381 editor
18382 .update_in(cx, |editor, _, cx| {
18383 for buffer_id in buffer_ids {
18384 if should_unfold {
18385 editor.unfold_buffer(buffer_id, cx);
18386 } else {
18387 editor.fold_buffer(buffer_id, cx);
18388 }
18389 }
18390 })
18391 .ok();
18392 });
18393 }
18394 }
18395
18396 fn fold_at_level(
18397 &mut self,
18398 fold_at: &FoldAtLevel,
18399 window: &mut Window,
18400 cx: &mut Context<Self>,
18401 ) {
18402 if !self.buffer.read(cx).is_singleton() {
18403 return;
18404 }
18405
18406 let fold_at_level = fold_at.0;
18407 let snapshot = self.buffer.read(cx).snapshot(cx);
18408 let mut to_fold = Vec::new();
18409 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18410
18411 let row_ranges_to_keep: Vec<Range<u32>> = self
18412 .selections
18413 .all::<Point>(&self.display_snapshot(cx))
18414 .into_iter()
18415 .map(|sel| sel.start.row..sel.end.row)
18416 .collect();
18417
18418 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18419 while start_row < end_row {
18420 match self
18421 .snapshot(window, cx)
18422 .crease_for_buffer_row(MultiBufferRow(start_row))
18423 {
18424 Some(crease) => {
18425 let nested_start_row = crease.range().start.row + 1;
18426 let nested_end_row = crease.range().end.row;
18427
18428 if current_level < fold_at_level {
18429 stack.push((nested_start_row, nested_end_row, current_level + 1));
18430 } else if current_level == fold_at_level {
18431 // Fold iff there is no selection completely contained within the fold region
18432 if !row_ranges_to_keep.iter().any(|selection| {
18433 selection.end >= nested_start_row
18434 && selection.start <= nested_end_row
18435 }) {
18436 to_fold.push(crease);
18437 }
18438 }
18439
18440 start_row = nested_end_row + 1;
18441 }
18442 None => start_row += 1,
18443 }
18444 }
18445 }
18446
18447 self.fold_creases(to_fold, true, window, cx);
18448 }
18449
18450 pub fn fold_at_level_1(
18451 &mut self,
18452 _: &actions::FoldAtLevel1,
18453 window: &mut Window,
18454 cx: &mut Context<Self>,
18455 ) {
18456 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18457 }
18458
18459 pub fn fold_at_level_2(
18460 &mut self,
18461 _: &actions::FoldAtLevel2,
18462 window: &mut Window,
18463 cx: &mut Context<Self>,
18464 ) {
18465 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18466 }
18467
18468 pub fn fold_at_level_3(
18469 &mut self,
18470 _: &actions::FoldAtLevel3,
18471 window: &mut Window,
18472 cx: &mut Context<Self>,
18473 ) {
18474 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18475 }
18476
18477 pub fn fold_at_level_4(
18478 &mut self,
18479 _: &actions::FoldAtLevel4,
18480 window: &mut Window,
18481 cx: &mut Context<Self>,
18482 ) {
18483 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18484 }
18485
18486 pub fn fold_at_level_5(
18487 &mut self,
18488 _: &actions::FoldAtLevel5,
18489 window: &mut Window,
18490 cx: &mut Context<Self>,
18491 ) {
18492 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18493 }
18494
18495 pub fn fold_at_level_6(
18496 &mut self,
18497 _: &actions::FoldAtLevel6,
18498 window: &mut Window,
18499 cx: &mut Context<Self>,
18500 ) {
18501 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18502 }
18503
18504 pub fn fold_at_level_7(
18505 &mut self,
18506 _: &actions::FoldAtLevel7,
18507 window: &mut Window,
18508 cx: &mut Context<Self>,
18509 ) {
18510 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18511 }
18512
18513 pub fn fold_at_level_8(
18514 &mut self,
18515 _: &actions::FoldAtLevel8,
18516 window: &mut Window,
18517 cx: &mut Context<Self>,
18518 ) {
18519 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18520 }
18521
18522 pub fn fold_at_level_9(
18523 &mut self,
18524 _: &actions::FoldAtLevel9,
18525 window: &mut Window,
18526 cx: &mut Context<Self>,
18527 ) {
18528 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18529 }
18530
18531 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18532 if self.buffer.read(cx).is_singleton() {
18533 let mut fold_ranges = Vec::new();
18534 let snapshot = self.buffer.read(cx).snapshot(cx);
18535
18536 for row in 0..snapshot.max_row().0 {
18537 if let Some(foldable_range) = self
18538 .snapshot(window, cx)
18539 .crease_for_buffer_row(MultiBufferRow(row))
18540 {
18541 fold_ranges.push(foldable_range);
18542 }
18543 }
18544
18545 self.fold_creases(fold_ranges, true, window, cx);
18546 } else {
18547 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18548 editor
18549 .update_in(cx, |editor, _, cx| {
18550 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18551 editor.fold_buffer(buffer_id, cx);
18552 }
18553 })
18554 .ok();
18555 });
18556 }
18557 }
18558
18559 pub fn fold_function_bodies(
18560 &mut self,
18561 _: &actions::FoldFunctionBodies,
18562 window: &mut Window,
18563 cx: &mut Context<Self>,
18564 ) {
18565 let snapshot = self.buffer.read(cx).snapshot(cx);
18566
18567 let ranges = snapshot
18568 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18569 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18570 .collect::<Vec<_>>();
18571
18572 let creases = ranges
18573 .into_iter()
18574 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18575 .collect();
18576
18577 self.fold_creases(creases, true, window, cx);
18578 }
18579
18580 pub fn fold_recursive(
18581 &mut self,
18582 _: &actions::FoldRecursive,
18583 window: &mut Window,
18584 cx: &mut Context<Self>,
18585 ) {
18586 let mut to_fold = Vec::new();
18587 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18588 let selections = self.selections.all_adjusted(&display_map);
18589
18590 for selection in selections {
18591 let range = selection.range().sorted();
18592 let buffer_start_row = range.start.row;
18593
18594 if range.start.row != range.end.row {
18595 let mut found = false;
18596 for row in range.start.row..=range.end.row {
18597 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18598 found = true;
18599 to_fold.push(crease);
18600 }
18601 }
18602 if found {
18603 continue;
18604 }
18605 }
18606
18607 for row in (0..=range.start.row).rev() {
18608 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18609 if crease.range().end.row >= buffer_start_row {
18610 to_fold.push(crease);
18611 } else {
18612 break;
18613 }
18614 }
18615 }
18616 }
18617
18618 self.fold_creases(to_fold, true, window, cx);
18619 }
18620
18621 pub fn fold_at(
18622 &mut self,
18623 buffer_row: MultiBufferRow,
18624 window: &mut Window,
18625 cx: &mut Context<Self>,
18626 ) {
18627 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18628
18629 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18630 let autoscroll = self
18631 .selections
18632 .all::<Point>(&display_map)
18633 .iter()
18634 .any(|selection| crease.range().overlaps(&selection.range()));
18635
18636 self.fold_creases(vec![crease], autoscroll, window, cx);
18637 }
18638 }
18639
18640 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18641 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18642 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18643 let buffer = display_map.buffer_snapshot();
18644 let selections = self.selections.all::<Point>(&display_map);
18645 let ranges = selections
18646 .iter()
18647 .map(|s| {
18648 let range = s.display_range(&display_map).sorted();
18649 let mut start = range.start.to_point(&display_map);
18650 let mut end = range.end.to_point(&display_map);
18651 start.column = 0;
18652 end.column = buffer.line_len(MultiBufferRow(end.row));
18653 start..end
18654 })
18655 .collect::<Vec<_>>();
18656
18657 self.unfold_ranges(&ranges, true, true, cx);
18658 } else {
18659 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18660 let buffer_ids = self
18661 .selections
18662 .disjoint_anchor_ranges()
18663 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18664 .collect::<HashSet<_>>();
18665 for buffer_id in buffer_ids {
18666 self.unfold_buffer(buffer_id, cx);
18667 }
18668 }
18669 }
18670
18671 pub fn unfold_recursive(
18672 &mut self,
18673 _: &UnfoldRecursive,
18674 _window: &mut Window,
18675 cx: &mut Context<Self>,
18676 ) {
18677 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18678 let selections = self.selections.all::<Point>(&display_map);
18679 let ranges = selections
18680 .iter()
18681 .map(|s| {
18682 let mut range = s.display_range(&display_map).sorted();
18683 *range.start.column_mut() = 0;
18684 *range.end.column_mut() = display_map.line_len(range.end.row());
18685 let start = range.start.to_point(&display_map);
18686 let end = range.end.to_point(&display_map);
18687 start..end
18688 })
18689 .collect::<Vec<_>>();
18690
18691 self.unfold_ranges(&ranges, true, true, cx);
18692 }
18693
18694 pub fn unfold_at(
18695 &mut self,
18696 buffer_row: MultiBufferRow,
18697 _window: &mut Window,
18698 cx: &mut Context<Self>,
18699 ) {
18700 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18701
18702 let intersection_range = Point::new(buffer_row.0, 0)
18703 ..Point::new(
18704 buffer_row.0,
18705 display_map.buffer_snapshot().line_len(buffer_row),
18706 );
18707
18708 let autoscroll = self
18709 .selections
18710 .all::<Point>(&display_map)
18711 .iter()
18712 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18713
18714 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18715 }
18716
18717 pub fn unfold_all(
18718 &mut self,
18719 _: &actions::UnfoldAll,
18720 _window: &mut Window,
18721 cx: &mut Context<Self>,
18722 ) {
18723 if self.buffer.read(cx).is_singleton() {
18724 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18725 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18726 } else {
18727 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18728 editor
18729 .update(cx, |editor, cx| {
18730 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18731 editor.unfold_buffer(buffer_id, cx);
18732 }
18733 })
18734 .ok();
18735 });
18736 }
18737 }
18738
18739 pub fn fold_selected_ranges(
18740 &mut self,
18741 _: &FoldSelectedRanges,
18742 window: &mut Window,
18743 cx: &mut Context<Self>,
18744 ) {
18745 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18746 let selections = self.selections.all_adjusted(&display_map);
18747 let ranges = selections
18748 .into_iter()
18749 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18750 .collect::<Vec<_>>();
18751 self.fold_creases(ranges, true, window, cx);
18752 }
18753
18754 pub fn fold_ranges<T: ToOffset + Clone>(
18755 &mut self,
18756 ranges: Vec<Range<T>>,
18757 auto_scroll: bool,
18758 window: &mut Window,
18759 cx: &mut Context<Self>,
18760 ) {
18761 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18762 let ranges = ranges
18763 .into_iter()
18764 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18765 .collect::<Vec<_>>();
18766 self.fold_creases(ranges, auto_scroll, window, cx);
18767 }
18768
18769 pub fn fold_creases<T: ToOffset + Clone>(
18770 &mut self,
18771 creases: Vec<Crease<T>>,
18772 auto_scroll: bool,
18773 _window: &mut Window,
18774 cx: &mut Context<Self>,
18775 ) {
18776 if creases.is_empty() {
18777 return;
18778 }
18779
18780 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18781
18782 if auto_scroll {
18783 self.request_autoscroll(Autoscroll::fit(), cx);
18784 }
18785
18786 cx.notify();
18787
18788 self.scrollbar_marker_state.dirty = true;
18789 self.folds_did_change(cx);
18790 }
18791
18792 /// Removes any folds whose ranges intersect any of the given ranges.
18793 pub fn unfold_ranges<T: ToOffset + Clone>(
18794 &mut self,
18795 ranges: &[Range<T>],
18796 inclusive: bool,
18797 auto_scroll: bool,
18798 cx: &mut Context<Self>,
18799 ) {
18800 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18801 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18802 });
18803 self.folds_did_change(cx);
18804 }
18805
18806 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18807 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18808 return;
18809 }
18810 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18811 self.display_map.update(cx, |display_map, cx| {
18812 display_map.fold_buffers([buffer_id], cx)
18813 });
18814 cx.emit(EditorEvent::BufferFoldToggled {
18815 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18816 folded: true,
18817 });
18818 cx.notify();
18819 }
18820
18821 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18822 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18823 return;
18824 }
18825 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18826 self.display_map.update(cx, |display_map, cx| {
18827 display_map.unfold_buffers([buffer_id], cx);
18828 });
18829 cx.emit(EditorEvent::BufferFoldToggled {
18830 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18831 folded: false,
18832 });
18833 cx.notify();
18834 }
18835
18836 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18837 self.display_map.read(cx).is_buffer_folded(buffer)
18838 }
18839
18840 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18841 self.display_map.read(cx).folded_buffers()
18842 }
18843
18844 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18845 self.display_map.update(cx, |display_map, cx| {
18846 display_map.disable_header_for_buffer(buffer_id, cx);
18847 });
18848 cx.notify();
18849 }
18850
18851 /// Removes any folds with the given ranges.
18852 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18853 &mut self,
18854 ranges: &[Range<T>],
18855 type_id: TypeId,
18856 auto_scroll: bool,
18857 cx: &mut Context<Self>,
18858 ) {
18859 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18860 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18861 });
18862 self.folds_did_change(cx);
18863 }
18864
18865 fn remove_folds_with<T: ToOffset + Clone>(
18866 &mut self,
18867 ranges: &[Range<T>],
18868 auto_scroll: bool,
18869 cx: &mut Context<Self>,
18870 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18871 ) {
18872 if ranges.is_empty() {
18873 return;
18874 }
18875
18876 let mut buffers_affected = HashSet::default();
18877 let multi_buffer = self.buffer().read(cx);
18878 for range in ranges {
18879 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18880 buffers_affected.insert(buffer.read(cx).remote_id());
18881 };
18882 }
18883
18884 self.display_map.update(cx, update);
18885
18886 if auto_scroll {
18887 self.request_autoscroll(Autoscroll::fit(), cx);
18888 }
18889
18890 cx.notify();
18891 self.scrollbar_marker_state.dirty = true;
18892 self.active_indent_guides_state.dirty = true;
18893 }
18894
18895 pub fn update_renderer_widths(
18896 &mut self,
18897 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18898 cx: &mut Context<Self>,
18899 ) -> bool {
18900 self.display_map
18901 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18902 }
18903
18904 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18905 self.display_map.read(cx).fold_placeholder.clone()
18906 }
18907
18908 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18909 self.buffer.update(cx, |buffer, cx| {
18910 buffer.set_all_diff_hunks_expanded(cx);
18911 });
18912 }
18913
18914 pub fn expand_all_diff_hunks(
18915 &mut self,
18916 _: &ExpandAllDiffHunks,
18917 _window: &mut Window,
18918 cx: &mut Context<Self>,
18919 ) {
18920 self.buffer.update(cx, |buffer, cx| {
18921 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18922 });
18923 }
18924
18925 pub fn collapse_all_diff_hunks(
18926 &mut self,
18927 _: &CollapseAllDiffHunks,
18928 _window: &mut Window,
18929 cx: &mut Context<Self>,
18930 ) {
18931 self.buffer.update(cx, |buffer, cx| {
18932 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18933 });
18934 }
18935
18936 pub fn toggle_selected_diff_hunks(
18937 &mut self,
18938 _: &ToggleSelectedDiffHunks,
18939 _window: &mut Window,
18940 cx: &mut Context<Self>,
18941 ) {
18942 let ranges: Vec<_> = self
18943 .selections
18944 .disjoint_anchors()
18945 .iter()
18946 .map(|s| s.range())
18947 .collect();
18948 self.toggle_diff_hunks_in_ranges(ranges, cx);
18949 }
18950
18951 pub fn diff_hunks_in_ranges<'a>(
18952 &'a self,
18953 ranges: &'a [Range<Anchor>],
18954 buffer: &'a MultiBufferSnapshot,
18955 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18956 ranges.iter().flat_map(move |range| {
18957 let end_excerpt_id = range.end.excerpt_id;
18958 let range = range.to_point(buffer);
18959 let mut peek_end = range.end;
18960 if range.end.row < buffer.max_row().0 {
18961 peek_end = Point::new(range.end.row + 1, 0);
18962 }
18963 buffer
18964 .diff_hunks_in_range(range.start..peek_end)
18965 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18966 })
18967 }
18968
18969 pub fn has_stageable_diff_hunks_in_ranges(
18970 &self,
18971 ranges: &[Range<Anchor>],
18972 snapshot: &MultiBufferSnapshot,
18973 ) -> bool {
18974 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18975 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18976 }
18977
18978 pub fn toggle_staged_selected_diff_hunks(
18979 &mut self,
18980 _: &::git::ToggleStaged,
18981 _: &mut Window,
18982 cx: &mut Context<Self>,
18983 ) {
18984 let snapshot = self.buffer.read(cx).snapshot(cx);
18985 let ranges: Vec<_> = self
18986 .selections
18987 .disjoint_anchors()
18988 .iter()
18989 .map(|s| s.range())
18990 .collect();
18991 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18992 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18993 }
18994
18995 pub fn set_render_diff_hunk_controls(
18996 &mut self,
18997 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18998 cx: &mut Context<Self>,
18999 ) {
19000 self.render_diff_hunk_controls = render_diff_hunk_controls;
19001 cx.notify();
19002 }
19003
19004 pub fn stage_and_next(
19005 &mut self,
19006 _: &::git::StageAndNext,
19007 window: &mut Window,
19008 cx: &mut Context<Self>,
19009 ) {
19010 self.do_stage_or_unstage_and_next(true, window, cx);
19011 }
19012
19013 pub fn unstage_and_next(
19014 &mut self,
19015 _: &::git::UnstageAndNext,
19016 window: &mut Window,
19017 cx: &mut Context<Self>,
19018 ) {
19019 self.do_stage_or_unstage_and_next(false, window, cx);
19020 }
19021
19022 pub fn stage_or_unstage_diff_hunks(
19023 &mut self,
19024 stage: bool,
19025 ranges: Vec<Range<Anchor>>,
19026 cx: &mut Context<Self>,
19027 ) {
19028 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19029 cx.spawn(async move |this, cx| {
19030 task.await?;
19031 this.update(cx, |this, cx| {
19032 let snapshot = this.buffer.read(cx).snapshot(cx);
19033 let chunk_by = this
19034 .diff_hunks_in_ranges(&ranges, &snapshot)
19035 .chunk_by(|hunk| hunk.buffer_id);
19036 for (buffer_id, hunks) in &chunk_by {
19037 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19038 }
19039 })
19040 })
19041 .detach_and_log_err(cx);
19042 }
19043
19044 fn save_buffers_for_ranges_if_needed(
19045 &mut self,
19046 ranges: &[Range<Anchor>],
19047 cx: &mut Context<Editor>,
19048 ) -> Task<Result<()>> {
19049 let multibuffer = self.buffer.read(cx);
19050 let snapshot = multibuffer.read(cx);
19051 let buffer_ids: HashSet<_> = ranges
19052 .iter()
19053 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19054 .collect();
19055 drop(snapshot);
19056
19057 let mut buffers = HashSet::default();
19058 for buffer_id in buffer_ids {
19059 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19060 let buffer = buffer_entity.read(cx);
19061 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19062 {
19063 buffers.insert(buffer_entity);
19064 }
19065 }
19066 }
19067
19068 if let Some(project) = &self.project {
19069 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19070 } else {
19071 Task::ready(Ok(()))
19072 }
19073 }
19074
19075 fn do_stage_or_unstage_and_next(
19076 &mut self,
19077 stage: bool,
19078 window: &mut Window,
19079 cx: &mut Context<Self>,
19080 ) {
19081 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19082
19083 if ranges.iter().any(|range| range.start != range.end) {
19084 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19085 return;
19086 }
19087
19088 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19089 let snapshot = self.snapshot(window, cx);
19090 let position = self
19091 .selections
19092 .newest::<Point>(&snapshot.display_snapshot)
19093 .head();
19094 let mut row = snapshot
19095 .buffer_snapshot()
19096 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19097 .find(|hunk| hunk.row_range.start.0 > position.row)
19098 .map(|hunk| hunk.row_range.start);
19099
19100 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19101 // Outside of the project diff editor, wrap around to the beginning.
19102 if !all_diff_hunks_expanded {
19103 row = row.or_else(|| {
19104 snapshot
19105 .buffer_snapshot()
19106 .diff_hunks_in_range(Point::zero()..position)
19107 .find(|hunk| hunk.row_range.end.0 < position.row)
19108 .map(|hunk| hunk.row_range.start)
19109 });
19110 }
19111
19112 if let Some(row) = row {
19113 let destination = Point::new(row.0, 0);
19114 let autoscroll = Autoscroll::center();
19115
19116 self.unfold_ranges(&[destination..destination], false, false, cx);
19117 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19118 s.select_ranges([destination..destination]);
19119 });
19120 }
19121 }
19122
19123 fn do_stage_or_unstage(
19124 &self,
19125 stage: bool,
19126 buffer_id: BufferId,
19127 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19128 cx: &mut App,
19129 ) -> Option<()> {
19130 let project = self.project()?;
19131 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19132 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19133 let buffer_snapshot = buffer.read(cx).snapshot();
19134 let file_exists = buffer_snapshot
19135 .file()
19136 .is_some_and(|file| file.disk_state().exists());
19137 diff.update(cx, |diff, cx| {
19138 diff.stage_or_unstage_hunks(
19139 stage,
19140 &hunks
19141 .map(|hunk| buffer_diff::DiffHunk {
19142 buffer_range: hunk.buffer_range,
19143 diff_base_byte_range: hunk.diff_base_byte_range,
19144 secondary_status: hunk.secondary_status,
19145 range: Point::zero()..Point::zero(), // unused
19146 })
19147 .collect::<Vec<_>>(),
19148 &buffer_snapshot,
19149 file_exists,
19150 cx,
19151 )
19152 });
19153 None
19154 }
19155
19156 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19157 let ranges: Vec<_> = self
19158 .selections
19159 .disjoint_anchors()
19160 .iter()
19161 .map(|s| s.range())
19162 .collect();
19163 self.buffer
19164 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19165 }
19166
19167 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19168 self.buffer.update(cx, |buffer, cx| {
19169 let ranges = vec![Anchor::min()..Anchor::max()];
19170 if !buffer.all_diff_hunks_expanded()
19171 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19172 {
19173 buffer.collapse_diff_hunks(ranges, cx);
19174 true
19175 } else {
19176 false
19177 }
19178 })
19179 }
19180
19181 fn toggle_diff_hunks_in_ranges(
19182 &mut self,
19183 ranges: Vec<Range<Anchor>>,
19184 cx: &mut Context<Editor>,
19185 ) {
19186 self.buffer.update(cx, |buffer, cx| {
19187 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19188 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19189 })
19190 }
19191
19192 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19193 self.buffer.update(cx, |buffer, cx| {
19194 let snapshot = buffer.snapshot(cx);
19195 let excerpt_id = range.end.excerpt_id;
19196 let point_range = range.to_point(&snapshot);
19197 let expand = !buffer.single_hunk_is_expanded(range, cx);
19198 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19199 })
19200 }
19201
19202 pub(crate) fn apply_all_diff_hunks(
19203 &mut self,
19204 _: &ApplyAllDiffHunks,
19205 window: &mut Window,
19206 cx: &mut Context<Self>,
19207 ) {
19208 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19209
19210 let buffers = self.buffer.read(cx).all_buffers();
19211 for branch_buffer in buffers {
19212 branch_buffer.update(cx, |branch_buffer, cx| {
19213 branch_buffer.merge_into_base(Vec::new(), cx);
19214 });
19215 }
19216
19217 if let Some(project) = self.project.clone() {
19218 self.save(
19219 SaveOptions {
19220 format: true,
19221 autosave: false,
19222 },
19223 project,
19224 window,
19225 cx,
19226 )
19227 .detach_and_log_err(cx);
19228 }
19229 }
19230
19231 pub(crate) fn apply_selected_diff_hunks(
19232 &mut self,
19233 _: &ApplyDiffHunk,
19234 window: &mut Window,
19235 cx: &mut Context<Self>,
19236 ) {
19237 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19238 let snapshot = self.snapshot(window, cx);
19239 let hunks = snapshot.hunks_for_ranges(
19240 self.selections
19241 .all(&snapshot.display_snapshot)
19242 .into_iter()
19243 .map(|selection| selection.range()),
19244 );
19245 let mut ranges_by_buffer = HashMap::default();
19246 self.transact(window, cx, |editor, _window, cx| {
19247 for hunk in hunks {
19248 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19249 ranges_by_buffer
19250 .entry(buffer.clone())
19251 .or_insert_with(Vec::new)
19252 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19253 }
19254 }
19255
19256 for (buffer, ranges) in ranges_by_buffer {
19257 buffer.update(cx, |buffer, cx| {
19258 buffer.merge_into_base(ranges, cx);
19259 });
19260 }
19261 });
19262
19263 if let Some(project) = self.project.clone() {
19264 self.save(
19265 SaveOptions {
19266 format: true,
19267 autosave: false,
19268 },
19269 project,
19270 window,
19271 cx,
19272 )
19273 .detach_and_log_err(cx);
19274 }
19275 }
19276
19277 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19278 if hovered != self.gutter_hovered {
19279 self.gutter_hovered = hovered;
19280 cx.notify();
19281 }
19282 }
19283
19284 pub fn insert_blocks(
19285 &mut self,
19286 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19287 autoscroll: Option<Autoscroll>,
19288 cx: &mut Context<Self>,
19289 ) -> Vec<CustomBlockId> {
19290 let blocks = self
19291 .display_map
19292 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19293 if let Some(autoscroll) = autoscroll {
19294 self.request_autoscroll(autoscroll, cx);
19295 }
19296 cx.notify();
19297 blocks
19298 }
19299
19300 pub fn resize_blocks(
19301 &mut self,
19302 heights: HashMap<CustomBlockId, u32>,
19303 autoscroll: Option<Autoscroll>,
19304 cx: &mut Context<Self>,
19305 ) {
19306 self.display_map
19307 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19308 if let Some(autoscroll) = autoscroll {
19309 self.request_autoscroll(autoscroll, cx);
19310 }
19311 cx.notify();
19312 }
19313
19314 pub fn replace_blocks(
19315 &mut self,
19316 renderers: HashMap<CustomBlockId, RenderBlock>,
19317 autoscroll: Option<Autoscroll>,
19318 cx: &mut Context<Self>,
19319 ) {
19320 self.display_map
19321 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19322 if let Some(autoscroll) = autoscroll {
19323 self.request_autoscroll(autoscroll, cx);
19324 }
19325 cx.notify();
19326 }
19327
19328 pub fn remove_blocks(
19329 &mut self,
19330 block_ids: HashSet<CustomBlockId>,
19331 autoscroll: Option<Autoscroll>,
19332 cx: &mut Context<Self>,
19333 ) {
19334 self.display_map.update(cx, |display_map, cx| {
19335 display_map.remove_blocks(block_ids, cx)
19336 });
19337 if let Some(autoscroll) = autoscroll {
19338 self.request_autoscroll(autoscroll, cx);
19339 }
19340 cx.notify();
19341 }
19342
19343 pub fn row_for_block(
19344 &self,
19345 block_id: CustomBlockId,
19346 cx: &mut Context<Self>,
19347 ) -> Option<DisplayRow> {
19348 self.display_map
19349 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19350 }
19351
19352 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19353 self.focused_block = Some(focused_block);
19354 }
19355
19356 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19357 self.focused_block.take()
19358 }
19359
19360 pub fn insert_creases(
19361 &mut self,
19362 creases: impl IntoIterator<Item = Crease<Anchor>>,
19363 cx: &mut Context<Self>,
19364 ) -> Vec<CreaseId> {
19365 self.display_map
19366 .update(cx, |map, cx| map.insert_creases(creases, cx))
19367 }
19368
19369 pub fn remove_creases(
19370 &mut self,
19371 ids: impl IntoIterator<Item = CreaseId>,
19372 cx: &mut Context<Self>,
19373 ) -> Vec<(CreaseId, Range<Anchor>)> {
19374 self.display_map
19375 .update(cx, |map, cx| map.remove_creases(ids, cx))
19376 }
19377
19378 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19379 self.display_map
19380 .update(cx, |map, cx| map.snapshot(cx))
19381 .longest_row()
19382 }
19383
19384 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19385 self.display_map
19386 .update(cx, |map, cx| map.snapshot(cx))
19387 .max_point()
19388 }
19389
19390 pub fn text(&self, cx: &App) -> String {
19391 self.buffer.read(cx).read(cx).text()
19392 }
19393
19394 pub fn is_empty(&self, cx: &App) -> bool {
19395 self.buffer.read(cx).read(cx).is_empty()
19396 }
19397
19398 pub fn text_option(&self, cx: &App) -> Option<String> {
19399 let text = self.text(cx);
19400 let text = text.trim();
19401
19402 if text.is_empty() {
19403 return None;
19404 }
19405
19406 Some(text.to_string())
19407 }
19408
19409 pub fn set_text(
19410 &mut self,
19411 text: impl Into<Arc<str>>,
19412 window: &mut Window,
19413 cx: &mut Context<Self>,
19414 ) {
19415 self.transact(window, cx, |this, _, cx| {
19416 this.buffer
19417 .read(cx)
19418 .as_singleton()
19419 .expect("you can only call set_text on editors for singleton buffers")
19420 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19421 });
19422 }
19423
19424 pub fn display_text(&self, cx: &mut App) -> String {
19425 self.display_map
19426 .update(cx, |map, cx| map.snapshot(cx))
19427 .text()
19428 }
19429
19430 fn create_minimap(
19431 &self,
19432 minimap_settings: MinimapSettings,
19433 window: &mut Window,
19434 cx: &mut Context<Self>,
19435 ) -> Option<Entity<Self>> {
19436 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19437 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19438 }
19439
19440 fn initialize_new_minimap(
19441 &self,
19442 minimap_settings: MinimapSettings,
19443 window: &mut Window,
19444 cx: &mut Context<Self>,
19445 ) -> Entity<Self> {
19446 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19447
19448 let mut minimap = Editor::new_internal(
19449 EditorMode::Minimap {
19450 parent: cx.weak_entity(),
19451 },
19452 self.buffer.clone(),
19453 None,
19454 Some(self.display_map.clone()),
19455 window,
19456 cx,
19457 );
19458 minimap.scroll_manager.clone_state(&self.scroll_manager);
19459 minimap.set_text_style_refinement(TextStyleRefinement {
19460 font_size: Some(MINIMAP_FONT_SIZE),
19461 font_weight: Some(MINIMAP_FONT_WEIGHT),
19462 ..Default::default()
19463 });
19464 minimap.update_minimap_configuration(minimap_settings, cx);
19465 cx.new(|_| minimap)
19466 }
19467
19468 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19469 let current_line_highlight = minimap_settings
19470 .current_line_highlight
19471 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19472 self.set_current_line_highlight(Some(current_line_highlight));
19473 }
19474
19475 pub fn minimap(&self) -> Option<&Entity<Self>> {
19476 self.minimap
19477 .as_ref()
19478 .filter(|_| self.minimap_visibility.visible())
19479 }
19480
19481 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19482 let mut wrap_guides = smallvec![];
19483
19484 if self.show_wrap_guides == Some(false) {
19485 return wrap_guides;
19486 }
19487
19488 let settings = self.buffer.read(cx).language_settings(cx);
19489 if settings.show_wrap_guides {
19490 match self.soft_wrap_mode(cx) {
19491 SoftWrap::Column(soft_wrap) => {
19492 wrap_guides.push((soft_wrap as usize, true));
19493 }
19494 SoftWrap::Bounded(soft_wrap) => {
19495 wrap_guides.push((soft_wrap as usize, true));
19496 }
19497 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19498 }
19499 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19500 }
19501
19502 wrap_guides
19503 }
19504
19505 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19506 let settings = self.buffer.read(cx).language_settings(cx);
19507 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19508 match mode {
19509 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19510 SoftWrap::None
19511 }
19512 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19513 language_settings::SoftWrap::PreferredLineLength => {
19514 SoftWrap::Column(settings.preferred_line_length)
19515 }
19516 language_settings::SoftWrap::Bounded => {
19517 SoftWrap::Bounded(settings.preferred_line_length)
19518 }
19519 }
19520 }
19521
19522 pub fn set_soft_wrap_mode(
19523 &mut self,
19524 mode: language_settings::SoftWrap,
19525
19526 cx: &mut Context<Self>,
19527 ) {
19528 self.soft_wrap_mode_override = Some(mode);
19529 cx.notify();
19530 }
19531
19532 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19533 self.hard_wrap = hard_wrap;
19534 cx.notify();
19535 }
19536
19537 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19538 self.text_style_refinement = Some(style);
19539 }
19540
19541 /// called by the Element so we know what style we were most recently rendered with.
19542 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19543 // We intentionally do not inform the display map about the minimap style
19544 // so that wrapping is not recalculated and stays consistent for the editor
19545 // and its linked minimap.
19546 if !self.mode.is_minimap() {
19547 let font = style.text.font();
19548 let font_size = style.text.font_size.to_pixels(window.rem_size());
19549 let display_map = self
19550 .placeholder_display_map
19551 .as_ref()
19552 .filter(|_| self.is_empty(cx))
19553 .unwrap_or(&self.display_map);
19554
19555 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19556 }
19557 self.style = Some(style);
19558 }
19559
19560 pub fn style(&self) -> Option<&EditorStyle> {
19561 self.style.as_ref()
19562 }
19563
19564 // Called by the element. This method is not designed to be called outside of the editor
19565 // element's layout code because it does not notify when rewrapping is computed synchronously.
19566 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19567 if self.is_empty(cx) {
19568 self.placeholder_display_map
19569 .as_ref()
19570 .map_or(false, |display_map| {
19571 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19572 })
19573 } else {
19574 self.display_map
19575 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19576 }
19577 }
19578
19579 pub fn set_soft_wrap(&mut self) {
19580 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19581 }
19582
19583 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19584 if self.soft_wrap_mode_override.is_some() {
19585 self.soft_wrap_mode_override.take();
19586 } else {
19587 let soft_wrap = match self.soft_wrap_mode(cx) {
19588 SoftWrap::GitDiff => return,
19589 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19590 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19591 language_settings::SoftWrap::None
19592 }
19593 };
19594 self.soft_wrap_mode_override = Some(soft_wrap);
19595 }
19596 cx.notify();
19597 }
19598
19599 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19600 let Some(workspace) = self.workspace() else {
19601 return;
19602 };
19603 let fs = workspace.read(cx).app_state().fs.clone();
19604 let current_show = TabBarSettings::get_global(cx).show;
19605 update_settings_file(fs, cx, move |setting, _| {
19606 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19607 });
19608 }
19609
19610 pub fn toggle_indent_guides(
19611 &mut self,
19612 _: &ToggleIndentGuides,
19613 _: &mut Window,
19614 cx: &mut Context<Self>,
19615 ) {
19616 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19617 self.buffer
19618 .read(cx)
19619 .language_settings(cx)
19620 .indent_guides
19621 .enabled
19622 });
19623 self.show_indent_guides = Some(!currently_enabled);
19624 cx.notify();
19625 }
19626
19627 fn should_show_indent_guides(&self) -> Option<bool> {
19628 self.show_indent_guides
19629 }
19630
19631 pub fn toggle_line_numbers(
19632 &mut self,
19633 _: &ToggleLineNumbers,
19634 _: &mut Window,
19635 cx: &mut Context<Self>,
19636 ) {
19637 let mut editor_settings = EditorSettings::get_global(cx).clone();
19638 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19639 EditorSettings::override_global(editor_settings, cx);
19640 }
19641
19642 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19643 if let Some(show_line_numbers) = self.show_line_numbers {
19644 return show_line_numbers;
19645 }
19646 EditorSettings::get_global(cx).gutter.line_numbers
19647 }
19648
19649 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19650 match (
19651 self.use_relative_line_numbers,
19652 EditorSettings::get_global(cx).relative_line_numbers,
19653 ) {
19654 (None, setting) => setting,
19655 (Some(false), _) => RelativeLineNumbers::Disabled,
19656 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19657 (Some(true), _) => RelativeLineNumbers::Enabled,
19658 }
19659 }
19660
19661 pub fn toggle_relative_line_numbers(
19662 &mut self,
19663 _: &ToggleRelativeLineNumbers,
19664 _: &mut Window,
19665 cx: &mut Context<Self>,
19666 ) {
19667 let is_relative = self.relative_line_numbers(cx);
19668 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19669 }
19670
19671 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19672 self.use_relative_line_numbers = is_relative;
19673 cx.notify();
19674 }
19675
19676 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19677 self.show_gutter = show_gutter;
19678 cx.notify();
19679 }
19680
19681 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19682 self.show_scrollbars = ScrollbarAxes {
19683 horizontal: show,
19684 vertical: show,
19685 };
19686 cx.notify();
19687 }
19688
19689 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19690 self.show_scrollbars.vertical = show;
19691 cx.notify();
19692 }
19693
19694 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19695 self.show_scrollbars.horizontal = show;
19696 cx.notify();
19697 }
19698
19699 pub fn set_minimap_visibility(
19700 &mut self,
19701 minimap_visibility: MinimapVisibility,
19702 window: &mut Window,
19703 cx: &mut Context<Self>,
19704 ) {
19705 if self.minimap_visibility != minimap_visibility {
19706 if minimap_visibility.visible() && self.minimap.is_none() {
19707 let minimap_settings = EditorSettings::get_global(cx).minimap;
19708 self.minimap =
19709 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19710 }
19711 self.minimap_visibility = minimap_visibility;
19712 cx.notify();
19713 }
19714 }
19715
19716 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19717 self.set_show_scrollbars(false, cx);
19718 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19719 }
19720
19721 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19722 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19723 }
19724
19725 /// Normally the text in full mode and auto height editors is padded on the
19726 /// left side by roughly half a character width for improved hit testing.
19727 ///
19728 /// Use this method to disable this for cases where this is not wanted (e.g.
19729 /// if you want to align the editor text with some other text above or below)
19730 /// or if you want to add this padding to single-line editors.
19731 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19732 self.offset_content = offset_content;
19733 cx.notify();
19734 }
19735
19736 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19737 self.show_line_numbers = Some(show_line_numbers);
19738 cx.notify();
19739 }
19740
19741 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19742 self.disable_expand_excerpt_buttons = true;
19743 cx.notify();
19744 }
19745
19746 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19747 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19748 cx.notify();
19749 }
19750
19751 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19752 self.show_code_actions = Some(show_code_actions);
19753 cx.notify();
19754 }
19755
19756 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19757 self.show_runnables = Some(show_runnables);
19758 cx.notify();
19759 }
19760
19761 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19762 self.show_breakpoints = Some(show_breakpoints);
19763 cx.notify();
19764 }
19765
19766 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19767 if self.display_map.read(cx).masked != masked {
19768 self.display_map.update(cx, |map, _| map.masked = masked);
19769 }
19770 cx.notify()
19771 }
19772
19773 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19774 self.show_wrap_guides = Some(show_wrap_guides);
19775 cx.notify();
19776 }
19777
19778 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19779 self.show_indent_guides = Some(show_indent_guides);
19780 cx.notify();
19781 }
19782
19783 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19784 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19785 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19786 && let Some(dir) = file.abs_path(cx).parent()
19787 {
19788 return Some(dir.to_owned());
19789 }
19790 }
19791
19792 None
19793 }
19794
19795 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19796 self.active_excerpt(cx)?
19797 .1
19798 .read(cx)
19799 .file()
19800 .and_then(|f| f.as_local())
19801 }
19802
19803 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19804 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19805 let buffer = buffer.read(cx);
19806 if let Some(project_path) = buffer.project_path(cx) {
19807 let project = self.project()?.read(cx);
19808 project.absolute_path(&project_path, cx)
19809 } else {
19810 buffer
19811 .file()
19812 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19813 }
19814 })
19815 }
19816
19817 pub fn reveal_in_finder(
19818 &mut self,
19819 _: &RevealInFileManager,
19820 _window: &mut Window,
19821 cx: &mut Context<Self>,
19822 ) {
19823 if let Some(target) = self.target_file(cx) {
19824 cx.reveal_path(&target.abs_path(cx));
19825 }
19826 }
19827
19828 pub fn copy_path(
19829 &mut self,
19830 _: &zed_actions::workspace::CopyPath,
19831 _window: &mut Window,
19832 cx: &mut Context<Self>,
19833 ) {
19834 if let Some(path) = self.target_file_abs_path(cx)
19835 && let Some(path) = path.to_str()
19836 {
19837 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19838 } else {
19839 cx.propagate();
19840 }
19841 }
19842
19843 pub fn copy_relative_path(
19844 &mut self,
19845 _: &zed_actions::workspace::CopyRelativePath,
19846 _window: &mut Window,
19847 cx: &mut Context<Self>,
19848 ) {
19849 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19850 let project = self.project()?.read(cx);
19851 let path = buffer.read(cx).file()?.path();
19852 let path = path.display(project.path_style(cx));
19853 Some(path)
19854 }) {
19855 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19856 } else {
19857 cx.propagate();
19858 }
19859 }
19860
19861 /// Returns the project path for the editor's buffer, if any buffer is
19862 /// opened in the editor.
19863 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19864 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19865 buffer.read(cx).project_path(cx)
19866 } else {
19867 None
19868 }
19869 }
19870
19871 // Returns true if the editor handled a go-to-line request
19872 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19873 maybe!({
19874 let breakpoint_store = self.breakpoint_store.as_ref()?;
19875
19876 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19877 else {
19878 self.clear_row_highlights::<ActiveDebugLine>();
19879 return None;
19880 };
19881
19882 let position = active_stack_frame.position;
19883 let buffer_id = position.buffer_id?;
19884 let snapshot = self
19885 .project
19886 .as_ref()?
19887 .read(cx)
19888 .buffer_for_id(buffer_id, cx)?
19889 .read(cx)
19890 .snapshot();
19891
19892 let mut handled = false;
19893 for (id, ExcerptRange { context, .. }) in
19894 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19895 {
19896 if context.start.cmp(&position, &snapshot).is_ge()
19897 || context.end.cmp(&position, &snapshot).is_lt()
19898 {
19899 continue;
19900 }
19901 let snapshot = self.buffer.read(cx).snapshot(cx);
19902 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19903
19904 handled = true;
19905 self.clear_row_highlights::<ActiveDebugLine>();
19906
19907 self.go_to_line::<ActiveDebugLine>(
19908 multibuffer_anchor,
19909 Some(cx.theme().colors().editor_debugger_active_line_background),
19910 window,
19911 cx,
19912 );
19913
19914 cx.notify();
19915 }
19916
19917 handled.then_some(())
19918 })
19919 .is_some()
19920 }
19921
19922 pub fn copy_file_name_without_extension(
19923 &mut self,
19924 _: &CopyFileNameWithoutExtension,
19925 _: &mut Window,
19926 cx: &mut Context<Self>,
19927 ) {
19928 if let Some(file) = self.target_file(cx)
19929 && let Some(file_stem) = file.path().file_stem()
19930 {
19931 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19932 }
19933 }
19934
19935 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19936 if let Some(file) = self.target_file(cx)
19937 && let Some(name) = file.path().file_name()
19938 {
19939 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19940 }
19941 }
19942
19943 pub fn toggle_git_blame(
19944 &mut self,
19945 _: &::git::Blame,
19946 window: &mut Window,
19947 cx: &mut Context<Self>,
19948 ) {
19949 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19950
19951 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19952 self.start_git_blame(true, window, cx);
19953 }
19954
19955 cx.notify();
19956 }
19957
19958 pub fn toggle_git_blame_inline(
19959 &mut self,
19960 _: &ToggleGitBlameInline,
19961 window: &mut Window,
19962 cx: &mut Context<Self>,
19963 ) {
19964 self.toggle_git_blame_inline_internal(true, window, cx);
19965 cx.notify();
19966 }
19967
19968 pub fn open_git_blame_commit(
19969 &mut self,
19970 _: &OpenGitBlameCommit,
19971 window: &mut Window,
19972 cx: &mut Context<Self>,
19973 ) {
19974 self.open_git_blame_commit_internal(window, cx);
19975 }
19976
19977 fn open_git_blame_commit_internal(
19978 &mut self,
19979 window: &mut Window,
19980 cx: &mut Context<Self>,
19981 ) -> Option<()> {
19982 let blame = self.blame.as_ref()?;
19983 let snapshot = self.snapshot(window, cx);
19984 let cursor = self
19985 .selections
19986 .newest::<Point>(&snapshot.display_snapshot)
19987 .head();
19988 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19989 let (_, blame_entry) = blame
19990 .update(cx, |blame, cx| {
19991 blame
19992 .blame_for_rows(
19993 &[RowInfo {
19994 buffer_id: Some(buffer.remote_id()),
19995 buffer_row: Some(point.row),
19996 ..Default::default()
19997 }],
19998 cx,
19999 )
20000 .next()
20001 })
20002 .flatten()?;
20003 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20004 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20005 let workspace = self.workspace()?.downgrade();
20006 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20007 None
20008 }
20009
20010 pub fn git_blame_inline_enabled(&self) -> bool {
20011 self.git_blame_inline_enabled
20012 }
20013
20014 pub fn toggle_selection_menu(
20015 &mut self,
20016 _: &ToggleSelectionMenu,
20017 _: &mut Window,
20018 cx: &mut Context<Self>,
20019 ) {
20020 self.show_selection_menu = self
20021 .show_selection_menu
20022 .map(|show_selections_menu| !show_selections_menu)
20023 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20024
20025 cx.notify();
20026 }
20027
20028 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20029 self.show_selection_menu
20030 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20031 }
20032
20033 fn start_git_blame(
20034 &mut self,
20035 user_triggered: bool,
20036 window: &mut Window,
20037 cx: &mut Context<Self>,
20038 ) {
20039 if let Some(project) = self.project() {
20040 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20041 && buffer.read(cx).file().is_none()
20042 {
20043 return;
20044 }
20045
20046 let focused = self.focus_handle(cx).contains_focused(window, cx);
20047
20048 let project = project.clone();
20049 let blame = cx
20050 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20051 self.blame_subscription =
20052 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20053 self.blame = Some(blame);
20054 }
20055 }
20056
20057 fn toggle_git_blame_inline_internal(
20058 &mut self,
20059 user_triggered: bool,
20060 window: &mut Window,
20061 cx: &mut Context<Self>,
20062 ) {
20063 if self.git_blame_inline_enabled {
20064 self.git_blame_inline_enabled = false;
20065 self.show_git_blame_inline = false;
20066 self.show_git_blame_inline_delay_task.take();
20067 } else {
20068 self.git_blame_inline_enabled = true;
20069 self.start_git_blame_inline(user_triggered, window, cx);
20070 }
20071
20072 cx.notify();
20073 }
20074
20075 fn start_git_blame_inline(
20076 &mut self,
20077 user_triggered: bool,
20078 window: &mut Window,
20079 cx: &mut Context<Self>,
20080 ) {
20081 self.start_git_blame(user_triggered, window, cx);
20082
20083 if ProjectSettings::get_global(cx)
20084 .git
20085 .inline_blame_delay()
20086 .is_some()
20087 {
20088 self.start_inline_blame_timer(window, cx);
20089 } else {
20090 self.show_git_blame_inline = true
20091 }
20092 }
20093
20094 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20095 self.blame.as_ref()
20096 }
20097
20098 pub fn show_git_blame_gutter(&self) -> bool {
20099 self.show_git_blame_gutter
20100 }
20101
20102 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20103 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20104 }
20105
20106 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20107 self.show_git_blame_inline
20108 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20109 && !self.newest_selection_head_on_empty_line(cx)
20110 && self.has_blame_entries(cx)
20111 }
20112
20113 fn has_blame_entries(&self, cx: &App) -> bool {
20114 self.blame()
20115 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20116 }
20117
20118 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20119 let cursor_anchor = self.selections.newest_anchor().head();
20120
20121 let snapshot = self.buffer.read(cx).snapshot(cx);
20122 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20123
20124 snapshot.line_len(buffer_row) == 0
20125 }
20126
20127 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20128 let buffer_and_selection = maybe!({
20129 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20130 let selection_range = selection.range();
20131
20132 let multi_buffer = self.buffer().read(cx);
20133 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20134 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20135
20136 let (buffer, range, _) = if selection.reversed {
20137 buffer_ranges.first()
20138 } else {
20139 buffer_ranges.last()
20140 }?;
20141
20142 let selection = text::ToPoint::to_point(&range.start, buffer).row
20143 ..text::ToPoint::to_point(&range.end, buffer).row;
20144 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20145 });
20146
20147 let Some((buffer, selection)) = buffer_and_selection else {
20148 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20149 };
20150
20151 let Some(project) = self.project() else {
20152 return Task::ready(Err(anyhow!("editor does not have project")));
20153 };
20154
20155 project.update(cx, |project, cx| {
20156 project.get_permalink_to_line(&buffer, selection, cx)
20157 })
20158 }
20159
20160 pub fn copy_permalink_to_line(
20161 &mut self,
20162 _: &CopyPermalinkToLine,
20163 window: &mut Window,
20164 cx: &mut Context<Self>,
20165 ) {
20166 let permalink_task = self.get_permalink_to_line(cx);
20167 let workspace = self.workspace();
20168
20169 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20170 Ok(permalink) => {
20171 cx.update(|_, cx| {
20172 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20173 })
20174 .ok();
20175 }
20176 Err(err) => {
20177 let message = format!("Failed to copy permalink: {err}");
20178
20179 anyhow::Result::<()>::Err(err).log_err();
20180
20181 if let Some(workspace) = workspace {
20182 workspace
20183 .update_in(cx, |workspace, _, cx| {
20184 struct CopyPermalinkToLine;
20185
20186 workspace.show_toast(
20187 Toast::new(
20188 NotificationId::unique::<CopyPermalinkToLine>(),
20189 message,
20190 ),
20191 cx,
20192 )
20193 })
20194 .ok();
20195 }
20196 }
20197 })
20198 .detach();
20199 }
20200
20201 pub fn copy_file_location(
20202 &mut self,
20203 _: &CopyFileLocation,
20204 _: &mut Window,
20205 cx: &mut Context<Self>,
20206 ) {
20207 let selection = self
20208 .selections
20209 .newest::<Point>(&self.display_snapshot(cx))
20210 .start
20211 .row
20212 + 1;
20213 if let Some(file) = self.target_file(cx) {
20214 let path = file.path().display(file.path_style(cx));
20215 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20216 }
20217 }
20218
20219 pub fn open_permalink_to_line(
20220 &mut self,
20221 _: &OpenPermalinkToLine,
20222 window: &mut Window,
20223 cx: &mut Context<Self>,
20224 ) {
20225 let permalink_task = self.get_permalink_to_line(cx);
20226 let workspace = self.workspace();
20227
20228 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20229 Ok(permalink) => {
20230 cx.update(|_, cx| {
20231 cx.open_url(permalink.as_ref());
20232 })
20233 .ok();
20234 }
20235 Err(err) => {
20236 let message = format!("Failed to open permalink: {err}");
20237
20238 anyhow::Result::<()>::Err(err).log_err();
20239
20240 if let Some(workspace) = workspace {
20241 workspace
20242 .update(cx, |workspace, cx| {
20243 struct OpenPermalinkToLine;
20244
20245 workspace.show_toast(
20246 Toast::new(
20247 NotificationId::unique::<OpenPermalinkToLine>(),
20248 message,
20249 ),
20250 cx,
20251 )
20252 })
20253 .ok();
20254 }
20255 }
20256 })
20257 .detach();
20258 }
20259
20260 pub fn insert_uuid_v4(
20261 &mut self,
20262 _: &InsertUuidV4,
20263 window: &mut Window,
20264 cx: &mut Context<Self>,
20265 ) {
20266 self.insert_uuid(UuidVersion::V4, window, cx);
20267 }
20268
20269 pub fn insert_uuid_v7(
20270 &mut self,
20271 _: &InsertUuidV7,
20272 window: &mut Window,
20273 cx: &mut Context<Self>,
20274 ) {
20275 self.insert_uuid(UuidVersion::V7, window, cx);
20276 }
20277
20278 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20279 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20280 self.transact(window, cx, |this, window, cx| {
20281 let edits = this
20282 .selections
20283 .all::<Point>(&this.display_snapshot(cx))
20284 .into_iter()
20285 .map(|selection| {
20286 let uuid = match version {
20287 UuidVersion::V4 => uuid::Uuid::new_v4(),
20288 UuidVersion::V7 => uuid::Uuid::now_v7(),
20289 };
20290
20291 (selection.range(), uuid.to_string())
20292 });
20293 this.edit(edits, cx);
20294 this.refresh_edit_prediction(true, false, window, cx);
20295 });
20296 }
20297
20298 pub fn open_selections_in_multibuffer(
20299 &mut self,
20300 _: &OpenSelectionsInMultibuffer,
20301 window: &mut Window,
20302 cx: &mut Context<Self>,
20303 ) {
20304 let multibuffer = self.buffer.read(cx);
20305
20306 let Some(buffer) = multibuffer.as_singleton() else {
20307 return;
20308 };
20309
20310 let Some(workspace) = self.workspace() else {
20311 return;
20312 };
20313
20314 let title = multibuffer.title(cx).to_string();
20315
20316 let locations = self
20317 .selections
20318 .all_anchors(&self.display_snapshot(cx))
20319 .iter()
20320 .map(|selection| {
20321 (
20322 buffer.clone(),
20323 (selection.start.text_anchor..selection.end.text_anchor)
20324 .to_point(buffer.read(cx)),
20325 )
20326 })
20327 .into_group_map();
20328
20329 cx.spawn_in(window, async move |_, cx| {
20330 workspace.update_in(cx, |workspace, window, cx| {
20331 Self::open_locations_in_multibuffer(
20332 workspace,
20333 locations,
20334 format!("Selections for '{title}'"),
20335 false,
20336 MultibufferSelectionMode::All,
20337 window,
20338 cx,
20339 );
20340 })
20341 })
20342 .detach();
20343 }
20344
20345 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20346 /// last highlight added will be used.
20347 ///
20348 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20349 pub fn highlight_rows<T: 'static>(
20350 &mut self,
20351 range: Range<Anchor>,
20352 color: Hsla,
20353 options: RowHighlightOptions,
20354 cx: &mut Context<Self>,
20355 ) {
20356 let snapshot = self.buffer().read(cx).snapshot(cx);
20357 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20358 let ix = row_highlights.binary_search_by(|highlight| {
20359 Ordering::Equal
20360 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20361 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20362 });
20363
20364 if let Err(mut ix) = ix {
20365 let index = post_inc(&mut self.highlight_order);
20366
20367 // If this range intersects with the preceding highlight, then merge it with
20368 // the preceding highlight. Otherwise insert a new highlight.
20369 let mut merged = false;
20370 if ix > 0 {
20371 let prev_highlight = &mut row_highlights[ix - 1];
20372 if prev_highlight
20373 .range
20374 .end
20375 .cmp(&range.start, &snapshot)
20376 .is_ge()
20377 {
20378 ix -= 1;
20379 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20380 prev_highlight.range.end = range.end;
20381 }
20382 merged = true;
20383 prev_highlight.index = index;
20384 prev_highlight.color = color;
20385 prev_highlight.options = options;
20386 }
20387 }
20388
20389 if !merged {
20390 row_highlights.insert(
20391 ix,
20392 RowHighlight {
20393 range,
20394 index,
20395 color,
20396 options,
20397 type_id: TypeId::of::<T>(),
20398 },
20399 );
20400 }
20401
20402 // If any of the following highlights intersect with this one, merge them.
20403 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20404 let highlight = &row_highlights[ix];
20405 if next_highlight
20406 .range
20407 .start
20408 .cmp(&highlight.range.end, &snapshot)
20409 .is_le()
20410 {
20411 if next_highlight
20412 .range
20413 .end
20414 .cmp(&highlight.range.end, &snapshot)
20415 .is_gt()
20416 {
20417 row_highlights[ix].range.end = next_highlight.range.end;
20418 }
20419 row_highlights.remove(ix + 1);
20420 } else {
20421 break;
20422 }
20423 }
20424 }
20425 }
20426
20427 /// Remove any highlighted row ranges of the given type that intersect the
20428 /// given ranges.
20429 pub fn remove_highlighted_rows<T: 'static>(
20430 &mut self,
20431 ranges_to_remove: Vec<Range<Anchor>>,
20432 cx: &mut Context<Self>,
20433 ) {
20434 let snapshot = self.buffer().read(cx).snapshot(cx);
20435 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20436 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20437 row_highlights.retain(|highlight| {
20438 while let Some(range_to_remove) = ranges_to_remove.peek() {
20439 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20440 Ordering::Less | Ordering::Equal => {
20441 ranges_to_remove.next();
20442 }
20443 Ordering::Greater => {
20444 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20445 Ordering::Less | Ordering::Equal => {
20446 return false;
20447 }
20448 Ordering::Greater => break,
20449 }
20450 }
20451 }
20452 }
20453
20454 true
20455 })
20456 }
20457
20458 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20459 pub fn clear_row_highlights<T: 'static>(&mut self) {
20460 self.highlighted_rows.remove(&TypeId::of::<T>());
20461 }
20462
20463 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20464 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20465 self.highlighted_rows
20466 .get(&TypeId::of::<T>())
20467 .map_or(&[] as &[_], |vec| vec.as_slice())
20468 .iter()
20469 .map(|highlight| (highlight.range.clone(), highlight.color))
20470 }
20471
20472 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20473 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20474 /// Allows to ignore certain kinds of highlights.
20475 pub fn highlighted_display_rows(
20476 &self,
20477 window: &mut Window,
20478 cx: &mut App,
20479 ) -> BTreeMap<DisplayRow, LineHighlight> {
20480 let snapshot = self.snapshot(window, cx);
20481 let mut used_highlight_orders = HashMap::default();
20482 self.highlighted_rows
20483 .iter()
20484 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20485 .fold(
20486 BTreeMap::<DisplayRow, LineHighlight>::new(),
20487 |mut unique_rows, highlight| {
20488 let start = highlight.range.start.to_display_point(&snapshot);
20489 let end = highlight.range.end.to_display_point(&snapshot);
20490 let start_row = start.row().0;
20491 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20492 && end.column() == 0
20493 {
20494 end.row().0.saturating_sub(1)
20495 } else {
20496 end.row().0
20497 };
20498 for row in start_row..=end_row {
20499 let used_index =
20500 used_highlight_orders.entry(row).or_insert(highlight.index);
20501 if highlight.index >= *used_index {
20502 *used_index = highlight.index;
20503 unique_rows.insert(
20504 DisplayRow(row),
20505 LineHighlight {
20506 include_gutter: highlight.options.include_gutter,
20507 border: None,
20508 background: highlight.color.into(),
20509 type_id: Some(highlight.type_id),
20510 },
20511 );
20512 }
20513 }
20514 unique_rows
20515 },
20516 )
20517 }
20518
20519 pub fn highlighted_display_row_for_autoscroll(
20520 &self,
20521 snapshot: &DisplaySnapshot,
20522 ) -> Option<DisplayRow> {
20523 self.highlighted_rows
20524 .values()
20525 .flat_map(|highlighted_rows| highlighted_rows.iter())
20526 .filter_map(|highlight| {
20527 if highlight.options.autoscroll {
20528 Some(highlight.range.start.to_display_point(snapshot).row())
20529 } else {
20530 None
20531 }
20532 })
20533 .min()
20534 }
20535
20536 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20537 self.highlight_background::<SearchWithinRange>(
20538 ranges,
20539 |colors| colors.colors().editor_document_highlight_read_background,
20540 cx,
20541 )
20542 }
20543
20544 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20545 self.breadcrumb_header = Some(new_header);
20546 }
20547
20548 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20549 self.clear_background_highlights::<SearchWithinRange>(cx);
20550 }
20551
20552 pub fn highlight_background<T: 'static>(
20553 &mut self,
20554 ranges: &[Range<Anchor>],
20555 color_fetcher: fn(&Theme) -> Hsla,
20556 cx: &mut Context<Self>,
20557 ) {
20558 self.background_highlights.insert(
20559 HighlightKey::Type(TypeId::of::<T>()),
20560 (color_fetcher, Arc::from(ranges)),
20561 );
20562 self.scrollbar_marker_state.dirty = true;
20563 cx.notify();
20564 }
20565
20566 pub fn highlight_background_key<T: 'static>(
20567 &mut self,
20568 key: usize,
20569 ranges: &[Range<Anchor>],
20570 color_fetcher: fn(&Theme) -> Hsla,
20571 cx: &mut Context<Self>,
20572 ) {
20573 self.background_highlights.insert(
20574 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20575 (color_fetcher, Arc::from(ranges)),
20576 );
20577 self.scrollbar_marker_state.dirty = true;
20578 cx.notify();
20579 }
20580
20581 pub fn clear_background_highlights<T: 'static>(
20582 &mut self,
20583 cx: &mut Context<Self>,
20584 ) -> Option<BackgroundHighlight> {
20585 let text_highlights = self
20586 .background_highlights
20587 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20588 if !text_highlights.1.is_empty() {
20589 self.scrollbar_marker_state.dirty = true;
20590 cx.notify();
20591 }
20592 Some(text_highlights)
20593 }
20594
20595 pub fn highlight_gutter<T: 'static>(
20596 &mut self,
20597 ranges: impl Into<Vec<Range<Anchor>>>,
20598 color_fetcher: fn(&App) -> Hsla,
20599 cx: &mut Context<Self>,
20600 ) {
20601 self.gutter_highlights
20602 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20603 cx.notify();
20604 }
20605
20606 pub fn clear_gutter_highlights<T: 'static>(
20607 &mut self,
20608 cx: &mut Context<Self>,
20609 ) -> Option<GutterHighlight> {
20610 cx.notify();
20611 self.gutter_highlights.remove(&TypeId::of::<T>())
20612 }
20613
20614 pub fn insert_gutter_highlight<T: 'static>(
20615 &mut self,
20616 range: Range<Anchor>,
20617 color_fetcher: fn(&App) -> Hsla,
20618 cx: &mut Context<Self>,
20619 ) {
20620 let snapshot = self.buffer().read(cx).snapshot(cx);
20621 let mut highlights = self
20622 .gutter_highlights
20623 .remove(&TypeId::of::<T>())
20624 .map(|(_, highlights)| highlights)
20625 .unwrap_or_default();
20626 let ix = highlights.binary_search_by(|highlight| {
20627 Ordering::Equal
20628 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20629 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20630 });
20631 if let Err(ix) = ix {
20632 highlights.insert(ix, range);
20633 }
20634 self.gutter_highlights
20635 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20636 }
20637
20638 pub fn remove_gutter_highlights<T: 'static>(
20639 &mut self,
20640 ranges_to_remove: Vec<Range<Anchor>>,
20641 cx: &mut Context<Self>,
20642 ) {
20643 let snapshot = self.buffer().read(cx).snapshot(cx);
20644 let Some((color_fetcher, mut gutter_highlights)) =
20645 self.gutter_highlights.remove(&TypeId::of::<T>())
20646 else {
20647 return;
20648 };
20649 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20650 gutter_highlights.retain(|highlight| {
20651 while let Some(range_to_remove) = ranges_to_remove.peek() {
20652 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20653 Ordering::Less | Ordering::Equal => {
20654 ranges_to_remove.next();
20655 }
20656 Ordering::Greater => {
20657 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20658 Ordering::Less | Ordering::Equal => {
20659 return false;
20660 }
20661 Ordering::Greater => break,
20662 }
20663 }
20664 }
20665 }
20666
20667 true
20668 });
20669 self.gutter_highlights
20670 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20671 }
20672
20673 #[cfg(feature = "test-support")]
20674 pub fn all_text_highlights(
20675 &self,
20676 window: &mut Window,
20677 cx: &mut Context<Self>,
20678 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20679 let snapshot = self.snapshot(window, cx);
20680 self.display_map.update(cx, |display_map, _| {
20681 display_map
20682 .all_text_highlights()
20683 .map(|highlight| {
20684 let (style, ranges) = highlight.as_ref();
20685 (
20686 *style,
20687 ranges
20688 .iter()
20689 .map(|range| range.clone().to_display_points(&snapshot))
20690 .collect(),
20691 )
20692 })
20693 .collect()
20694 })
20695 }
20696
20697 #[cfg(feature = "test-support")]
20698 pub fn all_text_background_highlights(
20699 &self,
20700 window: &mut Window,
20701 cx: &mut Context<Self>,
20702 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20703 let snapshot = self.snapshot(window, cx);
20704 let buffer = &snapshot.buffer_snapshot();
20705 let start = buffer.anchor_before(0);
20706 let end = buffer.anchor_after(buffer.len());
20707 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20708 }
20709
20710 #[cfg(any(test, feature = "test-support"))]
20711 pub fn sorted_background_highlights_in_range(
20712 &self,
20713 search_range: Range<Anchor>,
20714 display_snapshot: &DisplaySnapshot,
20715 theme: &Theme,
20716 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20717 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20718 res.sort_by(|a, b| {
20719 a.0.start
20720 .cmp(&b.0.start)
20721 .then_with(|| a.0.end.cmp(&b.0.end))
20722 .then_with(|| a.1.cmp(&b.1))
20723 });
20724 res
20725 }
20726
20727 #[cfg(feature = "test-support")]
20728 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20729 let snapshot = self.buffer().read(cx).snapshot(cx);
20730
20731 let highlights = self
20732 .background_highlights
20733 .get(&HighlightKey::Type(TypeId::of::<
20734 items::BufferSearchHighlights,
20735 >()));
20736
20737 if let Some((_color, ranges)) = highlights {
20738 ranges
20739 .iter()
20740 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20741 .collect_vec()
20742 } else {
20743 vec![]
20744 }
20745 }
20746
20747 fn document_highlights_for_position<'a>(
20748 &'a self,
20749 position: Anchor,
20750 buffer: &'a MultiBufferSnapshot,
20751 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20752 let read_highlights = self
20753 .background_highlights
20754 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20755 .map(|h| &h.1);
20756 let write_highlights = self
20757 .background_highlights
20758 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20759 .map(|h| &h.1);
20760 let left_position = position.bias_left(buffer);
20761 let right_position = position.bias_right(buffer);
20762 read_highlights
20763 .into_iter()
20764 .chain(write_highlights)
20765 .flat_map(move |ranges| {
20766 let start_ix = match ranges.binary_search_by(|probe| {
20767 let cmp = probe.end.cmp(&left_position, buffer);
20768 if cmp.is_ge() {
20769 Ordering::Greater
20770 } else {
20771 Ordering::Less
20772 }
20773 }) {
20774 Ok(i) | Err(i) => i,
20775 };
20776
20777 ranges[start_ix..]
20778 .iter()
20779 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20780 })
20781 }
20782
20783 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20784 self.background_highlights
20785 .get(&HighlightKey::Type(TypeId::of::<T>()))
20786 .is_some_and(|(_, highlights)| !highlights.is_empty())
20787 }
20788
20789 /// Returns all background highlights for a given range.
20790 ///
20791 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20792 pub fn background_highlights_in_range(
20793 &self,
20794 search_range: Range<Anchor>,
20795 display_snapshot: &DisplaySnapshot,
20796 theme: &Theme,
20797 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20798 let mut results = Vec::new();
20799 for (color_fetcher, ranges) in self.background_highlights.values() {
20800 let color = color_fetcher(theme);
20801 let start_ix = match ranges.binary_search_by(|probe| {
20802 let cmp = probe
20803 .end
20804 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20805 if cmp.is_gt() {
20806 Ordering::Greater
20807 } else {
20808 Ordering::Less
20809 }
20810 }) {
20811 Ok(i) | Err(i) => i,
20812 };
20813 for range in &ranges[start_ix..] {
20814 if range
20815 .start
20816 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20817 .is_ge()
20818 {
20819 break;
20820 }
20821
20822 let start = range.start.to_display_point(display_snapshot);
20823 let end = range.end.to_display_point(display_snapshot);
20824 results.push((start..end, color))
20825 }
20826 }
20827 results
20828 }
20829
20830 pub fn gutter_highlights_in_range(
20831 &self,
20832 search_range: Range<Anchor>,
20833 display_snapshot: &DisplaySnapshot,
20834 cx: &App,
20835 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20836 let mut results = Vec::new();
20837 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20838 let color = color_fetcher(cx);
20839 let start_ix = match ranges.binary_search_by(|probe| {
20840 let cmp = probe
20841 .end
20842 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20843 if cmp.is_gt() {
20844 Ordering::Greater
20845 } else {
20846 Ordering::Less
20847 }
20848 }) {
20849 Ok(i) | Err(i) => i,
20850 };
20851 for range in &ranges[start_ix..] {
20852 if range
20853 .start
20854 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20855 .is_ge()
20856 {
20857 break;
20858 }
20859
20860 let start = range.start.to_display_point(display_snapshot);
20861 let end = range.end.to_display_point(display_snapshot);
20862 results.push((start..end, color))
20863 }
20864 }
20865 results
20866 }
20867
20868 /// Get the text ranges corresponding to the redaction query
20869 pub fn redacted_ranges(
20870 &self,
20871 search_range: Range<Anchor>,
20872 display_snapshot: &DisplaySnapshot,
20873 cx: &App,
20874 ) -> Vec<Range<DisplayPoint>> {
20875 display_snapshot
20876 .buffer_snapshot()
20877 .redacted_ranges(search_range, |file| {
20878 if let Some(file) = file {
20879 file.is_private()
20880 && EditorSettings::get(
20881 Some(SettingsLocation {
20882 worktree_id: file.worktree_id(cx),
20883 path: file.path().as_ref(),
20884 }),
20885 cx,
20886 )
20887 .redact_private_values
20888 } else {
20889 false
20890 }
20891 })
20892 .map(|range| {
20893 range.start.to_display_point(display_snapshot)
20894 ..range.end.to_display_point(display_snapshot)
20895 })
20896 .collect()
20897 }
20898
20899 pub fn highlight_text_key<T: 'static>(
20900 &mut self,
20901 key: usize,
20902 ranges: Vec<Range<Anchor>>,
20903 style: HighlightStyle,
20904 cx: &mut Context<Self>,
20905 ) {
20906 self.display_map.update(cx, |map, _| {
20907 map.highlight_text(
20908 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20909 ranges,
20910 style,
20911 );
20912 });
20913 cx.notify();
20914 }
20915
20916 pub fn highlight_text<T: 'static>(
20917 &mut self,
20918 ranges: Vec<Range<Anchor>>,
20919 style: HighlightStyle,
20920 cx: &mut Context<Self>,
20921 ) {
20922 self.display_map.update(cx, |map, _| {
20923 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20924 });
20925 cx.notify();
20926 }
20927
20928 pub fn text_highlights<'a, T: 'static>(
20929 &'a self,
20930 cx: &'a App,
20931 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20932 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20933 }
20934
20935 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20936 let cleared = self
20937 .display_map
20938 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20939 if cleared {
20940 cx.notify();
20941 }
20942 }
20943
20944 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20945 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20946 && self.focus_handle.is_focused(window)
20947 }
20948
20949 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20950 self.show_cursor_when_unfocused = is_enabled;
20951 cx.notify();
20952 }
20953
20954 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20955 cx.notify();
20956 }
20957
20958 fn on_debug_session_event(
20959 &mut self,
20960 _session: Entity<Session>,
20961 event: &SessionEvent,
20962 cx: &mut Context<Self>,
20963 ) {
20964 if let SessionEvent::InvalidateInlineValue = event {
20965 self.refresh_inline_values(cx);
20966 }
20967 }
20968
20969 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20970 let Some(project) = self.project.clone() else {
20971 return;
20972 };
20973
20974 if !self.inline_value_cache.enabled {
20975 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20976 self.splice_inlays(&inlays, Vec::new(), cx);
20977 return;
20978 }
20979
20980 let current_execution_position = self
20981 .highlighted_rows
20982 .get(&TypeId::of::<ActiveDebugLine>())
20983 .and_then(|lines| lines.last().map(|line| line.range.end));
20984
20985 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20986 let inline_values = editor
20987 .update(cx, |editor, cx| {
20988 let Some(current_execution_position) = current_execution_position else {
20989 return Some(Task::ready(Ok(Vec::new())));
20990 };
20991
20992 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20993 let snapshot = buffer.snapshot(cx);
20994
20995 let excerpt = snapshot.excerpt_containing(
20996 current_execution_position..current_execution_position,
20997 )?;
20998
20999 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21000 })?;
21001
21002 let range =
21003 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21004
21005 project.inline_values(buffer, range, cx)
21006 })
21007 .ok()
21008 .flatten()?
21009 .await
21010 .context("refreshing debugger inlays")
21011 .log_err()?;
21012
21013 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21014
21015 for (buffer_id, inline_value) in inline_values
21016 .into_iter()
21017 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21018 {
21019 buffer_inline_values
21020 .entry(buffer_id)
21021 .or_default()
21022 .push(inline_value);
21023 }
21024
21025 editor
21026 .update(cx, |editor, cx| {
21027 let snapshot = editor.buffer.read(cx).snapshot(cx);
21028 let mut new_inlays = Vec::default();
21029
21030 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21031 let buffer_id = buffer_snapshot.remote_id();
21032 buffer_inline_values
21033 .get(&buffer_id)
21034 .into_iter()
21035 .flatten()
21036 .for_each(|hint| {
21037 let inlay = Inlay::debugger(
21038 post_inc(&mut editor.next_inlay_id),
21039 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
21040 hint.text(),
21041 );
21042 if !inlay.text().chars().contains(&'\n') {
21043 new_inlays.push(inlay);
21044 }
21045 });
21046 }
21047
21048 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21049 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21050
21051 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21052 })
21053 .ok()?;
21054 Some(())
21055 });
21056 }
21057
21058 fn on_buffer_event(
21059 &mut self,
21060 multibuffer: &Entity<MultiBuffer>,
21061 event: &multi_buffer::Event,
21062 window: &mut Window,
21063 cx: &mut Context<Self>,
21064 ) {
21065 match event {
21066 multi_buffer::Event::Edited { edited_buffer } => {
21067 self.scrollbar_marker_state.dirty = true;
21068 self.active_indent_guides_state.dirty = true;
21069 self.refresh_active_diagnostics(cx);
21070 self.refresh_code_actions(window, cx);
21071 self.refresh_selected_text_highlights(true, window, cx);
21072 self.refresh_single_line_folds(window, cx);
21073 self.refresh_matching_bracket_highlights(window, cx);
21074 if self.has_active_edit_prediction() {
21075 self.update_visible_edit_prediction(window, cx);
21076 }
21077
21078 if let Some(buffer) = edited_buffer {
21079 if buffer.read(cx).file().is_none() {
21080 cx.emit(EditorEvent::TitleChanged);
21081 }
21082
21083 if self.project.is_some() {
21084 let buffer_id = buffer.read(cx).remote_id();
21085 self.register_buffer(buffer_id, cx);
21086 self.update_lsp_data(Some(buffer_id), window, cx);
21087 self.refresh_inlay_hints(
21088 InlayHintRefreshReason::BufferEdited(buffer_id),
21089 cx,
21090 );
21091 }
21092 }
21093
21094 cx.emit(EditorEvent::BufferEdited);
21095 cx.emit(SearchEvent::MatchesInvalidated);
21096
21097 let Some(project) = &self.project else { return };
21098 let (telemetry, is_via_ssh) = {
21099 let project = project.read(cx);
21100 let telemetry = project.client().telemetry().clone();
21101 let is_via_ssh = project.is_via_remote_server();
21102 (telemetry, is_via_ssh)
21103 };
21104 telemetry.log_edit_event("editor", is_via_ssh);
21105 }
21106 multi_buffer::Event::ExcerptsAdded {
21107 buffer,
21108 predecessor,
21109 excerpts,
21110 } => {
21111 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21112 let buffer_id = buffer.read(cx).remote_id();
21113 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21114 && let Some(project) = &self.project
21115 {
21116 update_uncommitted_diff_for_buffer(
21117 cx.entity(),
21118 project,
21119 [buffer.clone()],
21120 self.buffer.clone(),
21121 cx,
21122 )
21123 .detach();
21124 }
21125 self.update_lsp_data(Some(buffer_id), window, cx);
21126 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21127 cx.emit(EditorEvent::ExcerptsAdded {
21128 buffer: buffer.clone(),
21129 predecessor: *predecessor,
21130 excerpts: excerpts.clone(),
21131 });
21132 }
21133 multi_buffer::Event::ExcerptsRemoved {
21134 ids,
21135 removed_buffer_ids,
21136 } => {
21137 if let Some(inlay_hints) = &mut self.inlay_hints {
21138 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21139 }
21140 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21141 for buffer_id in removed_buffer_ids {
21142 self.registered_buffers.remove(buffer_id);
21143 }
21144 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21145 cx.emit(EditorEvent::ExcerptsRemoved {
21146 ids: ids.clone(),
21147 removed_buffer_ids: removed_buffer_ids.clone(),
21148 });
21149 }
21150 multi_buffer::Event::ExcerptsEdited {
21151 excerpt_ids,
21152 buffer_ids,
21153 } => {
21154 self.display_map.update(cx, |map, cx| {
21155 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21156 });
21157 cx.emit(EditorEvent::ExcerptsEdited {
21158 ids: excerpt_ids.clone(),
21159 });
21160 }
21161 multi_buffer::Event::ExcerptsExpanded { ids } => {
21162 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21163 self.refresh_document_highlights(cx);
21164 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21165 }
21166 multi_buffer::Event::Reparsed(buffer_id) => {
21167 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21168 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21169
21170 cx.emit(EditorEvent::Reparsed(*buffer_id));
21171 }
21172 multi_buffer::Event::DiffHunksToggled => {
21173 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21174 }
21175 multi_buffer::Event::LanguageChanged(buffer_id) => {
21176 self.registered_buffers.remove(&buffer_id);
21177 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21178 cx.emit(EditorEvent::Reparsed(*buffer_id));
21179 cx.notify();
21180 }
21181 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21182 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21183 multi_buffer::Event::FileHandleChanged
21184 | multi_buffer::Event::Reloaded
21185 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21186 multi_buffer::Event::DiagnosticsUpdated => {
21187 self.update_diagnostics_state(window, cx);
21188 }
21189 _ => {}
21190 };
21191 }
21192
21193 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21194 if !self.diagnostics_enabled() {
21195 return;
21196 }
21197 self.refresh_active_diagnostics(cx);
21198 self.refresh_inline_diagnostics(true, window, cx);
21199 self.scrollbar_marker_state.dirty = true;
21200 cx.notify();
21201 }
21202
21203 pub fn start_temporary_diff_override(&mut self) {
21204 self.load_diff_task.take();
21205 self.temporary_diff_override = true;
21206 }
21207
21208 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21209 self.temporary_diff_override = false;
21210 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21211 self.buffer.update(cx, |buffer, cx| {
21212 buffer.set_all_diff_hunks_collapsed(cx);
21213 });
21214
21215 if let Some(project) = self.project.clone() {
21216 self.load_diff_task = Some(
21217 update_uncommitted_diff_for_buffer(
21218 cx.entity(),
21219 &project,
21220 self.buffer.read(cx).all_buffers(),
21221 self.buffer.clone(),
21222 cx,
21223 )
21224 .shared(),
21225 );
21226 }
21227 }
21228
21229 fn on_display_map_changed(
21230 &mut self,
21231 _: Entity<DisplayMap>,
21232 _: &mut Window,
21233 cx: &mut Context<Self>,
21234 ) {
21235 cx.notify();
21236 }
21237
21238 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21239 if self.diagnostics_enabled() {
21240 let new_severity = EditorSettings::get_global(cx)
21241 .diagnostics_max_severity
21242 .unwrap_or(DiagnosticSeverity::Hint);
21243 self.set_max_diagnostics_severity(new_severity, cx);
21244 }
21245 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21246 self.update_edit_prediction_settings(cx);
21247 self.refresh_edit_prediction(true, false, window, cx);
21248 self.refresh_inline_values(cx);
21249 self.refresh_inlay_hints(
21250 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21251 self.selections.newest_anchor().head(),
21252 &self.buffer.read(cx).snapshot(cx),
21253 cx,
21254 )),
21255 cx,
21256 );
21257
21258 let old_cursor_shape = self.cursor_shape;
21259 let old_show_breadcrumbs = self.show_breadcrumbs;
21260
21261 {
21262 let editor_settings = EditorSettings::get_global(cx);
21263 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21264 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21265 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21266 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21267 }
21268
21269 if old_cursor_shape != self.cursor_shape {
21270 cx.emit(EditorEvent::CursorShapeChanged);
21271 }
21272
21273 if old_show_breadcrumbs != self.show_breadcrumbs {
21274 cx.emit(EditorEvent::BreadcrumbsChanged);
21275 }
21276
21277 let project_settings = ProjectSettings::get_global(cx);
21278 self.buffer_serialization = self
21279 .should_serialize_buffer()
21280 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21281
21282 if self.mode.is_full() {
21283 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21284 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21285 if self.show_inline_diagnostics != show_inline_diagnostics {
21286 self.show_inline_diagnostics = show_inline_diagnostics;
21287 self.refresh_inline_diagnostics(false, window, cx);
21288 }
21289
21290 if self.git_blame_inline_enabled != inline_blame_enabled {
21291 self.toggle_git_blame_inline_internal(false, window, cx);
21292 }
21293
21294 let minimap_settings = EditorSettings::get_global(cx).minimap;
21295 if self.minimap_visibility != MinimapVisibility::Disabled {
21296 if self.minimap_visibility.settings_visibility()
21297 != minimap_settings.minimap_enabled()
21298 {
21299 self.set_minimap_visibility(
21300 MinimapVisibility::for_mode(self.mode(), cx),
21301 window,
21302 cx,
21303 );
21304 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21305 minimap_entity.update(cx, |minimap_editor, cx| {
21306 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21307 })
21308 }
21309 }
21310 }
21311
21312 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21313 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21314 }) {
21315 if !inlay_splice.is_empty() {
21316 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21317 }
21318 self.refresh_colors_for_visible_range(None, window, cx);
21319 }
21320
21321 cx.notify();
21322 }
21323
21324 pub fn set_searchable(&mut self, searchable: bool) {
21325 self.searchable = searchable;
21326 }
21327
21328 pub fn searchable(&self) -> bool {
21329 self.searchable
21330 }
21331
21332 pub fn open_excerpts_in_split(
21333 &mut self,
21334 _: &OpenExcerptsSplit,
21335 window: &mut Window,
21336 cx: &mut Context<Self>,
21337 ) {
21338 self.open_excerpts_common(None, true, window, cx)
21339 }
21340
21341 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21342 self.open_excerpts_common(None, false, window, cx)
21343 }
21344
21345 fn open_excerpts_common(
21346 &mut self,
21347 jump_data: Option<JumpData>,
21348 split: bool,
21349 window: &mut Window,
21350 cx: &mut Context<Self>,
21351 ) {
21352 let Some(workspace) = self.workspace() else {
21353 cx.propagate();
21354 return;
21355 };
21356
21357 if self.buffer.read(cx).is_singleton() {
21358 cx.propagate();
21359 return;
21360 }
21361
21362 let mut new_selections_by_buffer = HashMap::default();
21363 match &jump_data {
21364 Some(JumpData::MultiBufferPoint {
21365 excerpt_id,
21366 position,
21367 anchor,
21368 line_offset_from_top,
21369 }) => {
21370 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21371 if let Some(buffer) = multi_buffer_snapshot
21372 .buffer_id_for_excerpt(*excerpt_id)
21373 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21374 {
21375 let buffer_snapshot = buffer.read(cx).snapshot();
21376 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21377 language::ToPoint::to_point(anchor, &buffer_snapshot)
21378 } else {
21379 buffer_snapshot.clip_point(*position, Bias::Left)
21380 };
21381 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21382 new_selections_by_buffer.insert(
21383 buffer,
21384 (
21385 vec![jump_to_offset..jump_to_offset],
21386 Some(*line_offset_from_top),
21387 ),
21388 );
21389 }
21390 }
21391 Some(JumpData::MultiBufferRow {
21392 row,
21393 line_offset_from_top,
21394 }) => {
21395 let point = MultiBufferPoint::new(row.0, 0);
21396 if let Some((buffer, buffer_point, _)) =
21397 self.buffer.read(cx).point_to_buffer_point(point, cx)
21398 {
21399 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21400 new_selections_by_buffer
21401 .entry(buffer)
21402 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21403 .0
21404 .push(buffer_offset..buffer_offset)
21405 }
21406 }
21407 None => {
21408 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21409 let multi_buffer = self.buffer.read(cx);
21410 for selection in selections {
21411 for (snapshot, range, _, anchor) in multi_buffer
21412 .snapshot(cx)
21413 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21414 {
21415 if let Some(anchor) = anchor {
21416 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21417 else {
21418 continue;
21419 };
21420 let offset = text::ToOffset::to_offset(
21421 &anchor.text_anchor,
21422 &buffer_handle.read(cx).snapshot(),
21423 );
21424 let range = offset..offset;
21425 new_selections_by_buffer
21426 .entry(buffer_handle)
21427 .or_insert((Vec::new(), None))
21428 .0
21429 .push(range)
21430 } else {
21431 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21432 else {
21433 continue;
21434 };
21435 new_selections_by_buffer
21436 .entry(buffer_handle)
21437 .or_insert((Vec::new(), None))
21438 .0
21439 .push(range)
21440 }
21441 }
21442 }
21443 }
21444 }
21445
21446 new_selections_by_buffer
21447 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21448
21449 if new_selections_by_buffer.is_empty() {
21450 return;
21451 }
21452
21453 // We defer the pane interaction because we ourselves are a workspace item
21454 // and activating a new item causes the pane to call a method on us reentrantly,
21455 // which panics if we're on the stack.
21456 window.defer(cx, move |window, cx| {
21457 workspace.update(cx, |workspace, cx| {
21458 let pane = if split {
21459 workspace.adjacent_pane(window, cx)
21460 } else {
21461 workspace.active_pane().clone()
21462 };
21463
21464 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21465 let editor = buffer
21466 .read(cx)
21467 .file()
21468 .is_none()
21469 .then(|| {
21470 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21471 // so `workspace.open_project_item` will never find them, always opening a new editor.
21472 // Instead, we try to activate the existing editor in the pane first.
21473 let (editor, pane_item_index) =
21474 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21475 let editor = item.downcast::<Editor>()?;
21476 let singleton_buffer =
21477 editor.read(cx).buffer().read(cx).as_singleton()?;
21478 if singleton_buffer == buffer {
21479 Some((editor, i))
21480 } else {
21481 None
21482 }
21483 })?;
21484 pane.update(cx, |pane, cx| {
21485 pane.activate_item(pane_item_index, true, true, window, cx)
21486 });
21487 Some(editor)
21488 })
21489 .flatten()
21490 .unwrap_or_else(|| {
21491 workspace.open_project_item::<Self>(
21492 pane.clone(),
21493 buffer,
21494 true,
21495 true,
21496 window,
21497 cx,
21498 )
21499 });
21500
21501 editor.update(cx, |editor, cx| {
21502 let autoscroll = match scroll_offset {
21503 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21504 None => Autoscroll::newest(),
21505 };
21506 let nav_history = editor.nav_history.take();
21507 editor.change_selections(
21508 SelectionEffects::scroll(autoscroll),
21509 window,
21510 cx,
21511 |s| {
21512 s.select_ranges(ranges);
21513 },
21514 );
21515 editor.nav_history = nav_history;
21516 });
21517 }
21518 })
21519 });
21520 }
21521
21522 // For now, don't allow opening excerpts in buffers that aren't backed by
21523 // regular project files.
21524 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21525 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21526 }
21527
21528 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21529 let snapshot = self.buffer.read(cx).read(cx);
21530 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21531 Some(
21532 ranges
21533 .iter()
21534 .map(move |range| {
21535 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21536 })
21537 .collect(),
21538 )
21539 }
21540
21541 fn selection_replacement_ranges(
21542 &self,
21543 range: Range<OffsetUtf16>,
21544 cx: &mut App,
21545 ) -> Vec<Range<OffsetUtf16>> {
21546 let selections = self
21547 .selections
21548 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21549 let newest_selection = selections
21550 .iter()
21551 .max_by_key(|selection| selection.id)
21552 .unwrap();
21553 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21554 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21555 let snapshot = self.buffer.read(cx).read(cx);
21556 selections
21557 .into_iter()
21558 .map(|mut selection| {
21559 selection.start.0 =
21560 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21561 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21562 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21563 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21564 })
21565 .collect()
21566 }
21567
21568 fn report_editor_event(
21569 &self,
21570 reported_event: ReportEditorEvent,
21571 file_extension: Option<String>,
21572 cx: &App,
21573 ) {
21574 if cfg!(any(test, feature = "test-support")) {
21575 return;
21576 }
21577
21578 let Some(project) = &self.project else { return };
21579
21580 // If None, we are in a file without an extension
21581 let file = self
21582 .buffer
21583 .read(cx)
21584 .as_singleton()
21585 .and_then(|b| b.read(cx).file());
21586 let file_extension = file_extension.or(file
21587 .as_ref()
21588 .and_then(|file| Path::new(file.file_name(cx)).extension())
21589 .and_then(|e| e.to_str())
21590 .map(|a| a.to_string()));
21591
21592 let vim_mode = vim_flavor(cx).is_some();
21593
21594 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21595 let copilot_enabled = edit_predictions_provider
21596 == language::language_settings::EditPredictionProvider::Copilot;
21597 let copilot_enabled_for_language = self
21598 .buffer
21599 .read(cx)
21600 .language_settings(cx)
21601 .show_edit_predictions;
21602
21603 let project = project.read(cx);
21604 let event_type = reported_event.event_type();
21605
21606 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21607 telemetry::event!(
21608 event_type,
21609 type = if auto_saved {"autosave"} else {"manual"},
21610 file_extension,
21611 vim_mode,
21612 copilot_enabled,
21613 copilot_enabled_for_language,
21614 edit_predictions_provider,
21615 is_via_ssh = project.is_via_remote_server(),
21616 );
21617 } else {
21618 telemetry::event!(
21619 event_type,
21620 file_extension,
21621 vim_mode,
21622 copilot_enabled,
21623 copilot_enabled_for_language,
21624 edit_predictions_provider,
21625 is_via_ssh = project.is_via_remote_server(),
21626 );
21627 };
21628 }
21629
21630 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21631 /// with each line being an array of {text, highlight} objects.
21632 fn copy_highlight_json(
21633 &mut self,
21634 _: &CopyHighlightJson,
21635 window: &mut Window,
21636 cx: &mut Context<Self>,
21637 ) {
21638 #[derive(Serialize)]
21639 struct Chunk<'a> {
21640 text: String,
21641 highlight: Option<&'a str>,
21642 }
21643
21644 let snapshot = self.buffer.read(cx).snapshot(cx);
21645 let range = self
21646 .selected_text_range(false, window, cx)
21647 .and_then(|selection| {
21648 if selection.range.is_empty() {
21649 None
21650 } else {
21651 Some(
21652 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21653 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21654 )
21655 }
21656 })
21657 .unwrap_or_else(|| 0..snapshot.len());
21658
21659 let chunks = snapshot.chunks(range, true);
21660 let mut lines = Vec::new();
21661 let mut line: VecDeque<Chunk> = VecDeque::new();
21662
21663 let Some(style) = self.style.as_ref() else {
21664 return;
21665 };
21666
21667 for chunk in chunks {
21668 let highlight = chunk
21669 .syntax_highlight_id
21670 .and_then(|id| id.name(&style.syntax));
21671 let mut chunk_lines = chunk.text.split('\n').peekable();
21672 while let Some(text) = chunk_lines.next() {
21673 let mut merged_with_last_token = false;
21674 if let Some(last_token) = line.back_mut()
21675 && last_token.highlight == highlight
21676 {
21677 last_token.text.push_str(text);
21678 merged_with_last_token = true;
21679 }
21680
21681 if !merged_with_last_token {
21682 line.push_back(Chunk {
21683 text: text.into(),
21684 highlight,
21685 });
21686 }
21687
21688 if chunk_lines.peek().is_some() {
21689 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21690 line.pop_front();
21691 }
21692 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21693 line.pop_back();
21694 }
21695
21696 lines.push(mem::take(&mut line));
21697 }
21698 }
21699 }
21700
21701 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21702 return;
21703 };
21704 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21705 }
21706
21707 pub fn open_context_menu(
21708 &mut self,
21709 _: &OpenContextMenu,
21710 window: &mut Window,
21711 cx: &mut Context<Self>,
21712 ) {
21713 self.request_autoscroll(Autoscroll::newest(), cx);
21714 let position = self
21715 .selections
21716 .newest_display(&self.display_snapshot(cx))
21717 .start;
21718 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21719 }
21720
21721 pub fn replay_insert_event(
21722 &mut self,
21723 text: &str,
21724 relative_utf16_range: Option<Range<isize>>,
21725 window: &mut Window,
21726 cx: &mut Context<Self>,
21727 ) {
21728 if !self.input_enabled {
21729 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21730 return;
21731 }
21732 if let Some(relative_utf16_range) = relative_utf16_range {
21733 let selections = self
21734 .selections
21735 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21736 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21737 let new_ranges = selections.into_iter().map(|range| {
21738 let start = OffsetUtf16(
21739 range
21740 .head()
21741 .0
21742 .saturating_add_signed(relative_utf16_range.start),
21743 );
21744 let end = OffsetUtf16(
21745 range
21746 .head()
21747 .0
21748 .saturating_add_signed(relative_utf16_range.end),
21749 );
21750 start..end
21751 });
21752 s.select_ranges(new_ranges);
21753 });
21754 }
21755
21756 self.handle_input(text, window, cx);
21757 }
21758
21759 pub fn is_focused(&self, window: &Window) -> bool {
21760 self.focus_handle.is_focused(window)
21761 }
21762
21763 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21764 cx.emit(EditorEvent::Focused);
21765
21766 if let Some(descendant) = self
21767 .last_focused_descendant
21768 .take()
21769 .and_then(|descendant| descendant.upgrade())
21770 {
21771 window.focus(&descendant);
21772 } else {
21773 if let Some(blame) = self.blame.as_ref() {
21774 blame.update(cx, GitBlame::focus)
21775 }
21776
21777 self.blink_manager.update(cx, BlinkManager::enable);
21778 self.show_cursor_names(window, cx);
21779 self.buffer.update(cx, |buffer, cx| {
21780 buffer.finalize_last_transaction(cx);
21781 if self.leader_id.is_none() {
21782 buffer.set_active_selections(
21783 &self.selections.disjoint_anchors_arc(),
21784 self.selections.line_mode(),
21785 self.cursor_shape,
21786 cx,
21787 );
21788 }
21789 });
21790 }
21791 }
21792
21793 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21794 cx.emit(EditorEvent::FocusedIn)
21795 }
21796
21797 fn handle_focus_out(
21798 &mut self,
21799 event: FocusOutEvent,
21800 _window: &mut Window,
21801 cx: &mut Context<Self>,
21802 ) {
21803 if event.blurred != self.focus_handle {
21804 self.last_focused_descendant = Some(event.blurred);
21805 }
21806 self.selection_drag_state = SelectionDragState::None;
21807 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21808 }
21809
21810 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21811 self.blink_manager.update(cx, BlinkManager::disable);
21812 self.buffer
21813 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21814
21815 if let Some(blame) = self.blame.as_ref() {
21816 blame.update(cx, GitBlame::blur)
21817 }
21818 if !self.hover_state.focused(window, cx) {
21819 hide_hover(self, cx);
21820 }
21821 if !self
21822 .context_menu
21823 .borrow()
21824 .as_ref()
21825 .is_some_and(|context_menu| context_menu.focused(window, cx))
21826 {
21827 self.hide_context_menu(window, cx);
21828 }
21829 self.take_active_edit_prediction(cx);
21830 cx.emit(EditorEvent::Blurred);
21831 cx.notify();
21832 }
21833
21834 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21835 let mut pending: String = window
21836 .pending_input_keystrokes()
21837 .into_iter()
21838 .flatten()
21839 .filter_map(|keystroke| {
21840 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21841 keystroke.key_char.clone()
21842 } else {
21843 None
21844 }
21845 })
21846 .collect();
21847
21848 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21849 pending = "".to_string();
21850 }
21851
21852 let existing_pending = self
21853 .text_highlights::<PendingInput>(cx)
21854 .map(|(_, ranges)| ranges.to_vec());
21855 if existing_pending.is_none() && pending.is_empty() {
21856 return;
21857 }
21858 let transaction =
21859 self.transact(window, cx, |this, window, cx| {
21860 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21861 let edits = selections
21862 .iter()
21863 .map(|selection| (selection.end..selection.end, pending.clone()));
21864 this.edit(edits, cx);
21865 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21866 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21867 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21868 }));
21869 });
21870 if let Some(existing_ranges) = existing_pending {
21871 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21872 this.edit(edits, cx);
21873 }
21874 });
21875
21876 let snapshot = self.snapshot(window, cx);
21877 let ranges = self
21878 .selections
21879 .all::<usize>(&snapshot.display_snapshot)
21880 .into_iter()
21881 .map(|selection| {
21882 snapshot.buffer_snapshot().anchor_after(selection.end)
21883 ..snapshot
21884 .buffer_snapshot()
21885 .anchor_before(selection.end + pending.len())
21886 })
21887 .collect();
21888
21889 if pending.is_empty() {
21890 self.clear_highlights::<PendingInput>(cx);
21891 } else {
21892 self.highlight_text::<PendingInput>(
21893 ranges,
21894 HighlightStyle {
21895 underline: Some(UnderlineStyle {
21896 thickness: px(1.),
21897 color: None,
21898 wavy: false,
21899 }),
21900 ..Default::default()
21901 },
21902 cx,
21903 );
21904 }
21905
21906 self.ime_transaction = self.ime_transaction.or(transaction);
21907 if let Some(transaction) = self.ime_transaction {
21908 self.buffer.update(cx, |buffer, cx| {
21909 buffer.group_until_transaction(transaction, cx);
21910 });
21911 }
21912
21913 if self.text_highlights::<PendingInput>(cx).is_none() {
21914 self.ime_transaction.take();
21915 }
21916 }
21917
21918 pub fn register_action_renderer(
21919 &mut self,
21920 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21921 ) -> Subscription {
21922 let id = self.next_editor_action_id.post_inc();
21923 self.editor_actions
21924 .borrow_mut()
21925 .insert(id, Box::new(listener));
21926
21927 let editor_actions = self.editor_actions.clone();
21928 Subscription::new(move || {
21929 editor_actions.borrow_mut().remove(&id);
21930 })
21931 }
21932
21933 pub fn register_action<A: Action>(
21934 &mut self,
21935 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21936 ) -> Subscription {
21937 let id = self.next_editor_action_id.post_inc();
21938 let listener = Arc::new(listener);
21939 self.editor_actions.borrow_mut().insert(
21940 id,
21941 Box::new(move |_, window, _| {
21942 let listener = listener.clone();
21943 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21944 let action = action.downcast_ref().unwrap();
21945 if phase == DispatchPhase::Bubble {
21946 listener(action, window, cx)
21947 }
21948 })
21949 }),
21950 );
21951
21952 let editor_actions = self.editor_actions.clone();
21953 Subscription::new(move || {
21954 editor_actions.borrow_mut().remove(&id);
21955 })
21956 }
21957
21958 pub fn file_header_size(&self) -> u32 {
21959 FILE_HEADER_HEIGHT
21960 }
21961
21962 pub fn restore(
21963 &mut self,
21964 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21965 window: &mut Window,
21966 cx: &mut Context<Self>,
21967 ) {
21968 let workspace = self.workspace();
21969 let project = self.project();
21970 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21971 let mut tasks = Vec::new();
21972 for (buffer_id, changes) in revert_changes {
21973 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21974 buffer.update(cx, |buffer, cx| {
21975 buffer.edit(
21976 changes
21977 .into_iter()
21978 .map(|(range, text)| (range, text.to_string())),
21979 None,
21980 cx,
21981 );
21982 });
21983
21984 if let Some(project) =
21985 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21986 {
21987 project.update(cx, |project, cx| {
21988 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21989 })
21990 }
21991 }
21992 }
21993 tasks
21994 });
21995 cx.spawn_in(window, async move |_, cx| {
21996 for (buffer, task) in save_tasks {
21997 let result = task.await;
21998 if result.is_err() {
21999 let Some(path) = buffer
22000 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22001 .ok()
22002 else {
22003 continue;
22004 };
22005 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22006 let Some(task) = cx
22007 .update_window_entity(workspace, |workspace, window, cx| {
22008 workspace
22009 .open_path_preview(path, None, false, false, false, window, cx)
22010 })
22011 .ok()
22012 else {
22013 continue;
22014 };
22015 task.await.log_err();
22016 }
22017 }
22018 }
22019 })
22020 .detach();
22021 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22022 selections.refresh()
22023 });
22024 }
22025
22026 pub fn to_pixel_point(
22027 &self,
22028 source: multi_buffer::Anchor,
22029 editor_snapshot: &EditorSnapshot,
22030 window: &mut Window,
22031 ) -> Option<gpui::Point<Pixels>> {
22032 let source_point = source.to_display_point(editor_snapshot);
22033 self.display_to_pixel_point(source_point, editor_snapshot, window)
22034 }
22035
22036 pub fn display_to_pixel_point(
22037 &self,
22038 source: DisplayPoint,
22039 editor_snapshot: &EditorSnapshot,
22040 window: &mut Window,
22041 ) -> Option<gpui::Point<Pixels>> {
22042 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22043 let text_layout_details = self.text_layout_details(window);
22044 let scroll_top = text_layout_details
22045 .scroll_anchor
22046 .scroll_position(editor_snapshot)
22047 .y;
22048
22049 if source.row().as_f64() < scroll_top.floor() {
22050 return None;
22051 }
22052 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22053 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22054 Some(gpui::Point::new(source_x, source_y))
22055 }
22056
22057 pub fn has_visible_completions_menu(&self) -> bool {
22058 !self.edit_prediction_preview_is_active()
22059 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22060 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22061 })
22062 }
22063
22064 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22065 if self.mode.is_minimap() {
22066 return;
22067 }
22068 self.addons
22069 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22070 }
22071
22072 pub fn unregister_addon<T: Addon>(&mut self) {
22073 self.addons.remove(&std::any::TypeId::of::<T>());
22074 }
22075
22076 pub fn addon<T: Addon>(&self) -> Option<&T> {
22077 let type_id = std::any::TypeId::of::<T>();
22078 self.addons
22079 .get(&type_id)
22080 .and_then(|item| item.to_any().downcast_ref::<T>())
22081 }
22082
22083 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22084 let type_id = std::any::TypeId::of::<T>();
22085 self.addons
22086 .get_mut(&type_id)
22087 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22088 }
22089
22090 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22091 let text_layout_details = self.text_layout_details(window);
22092 let style = &text_layout_details.editor_style;
22093 let font_id = window.text_system().resolve_font(&style.text.font());
22094 let font_size = style.text.font_size.to_pixels(window.rem_size());
22095 let line_height = style.text.line_height_in_pixels(window.rem_size());
22096 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22097 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22098
22099 CharacterDimensions {
22100 em_width,
22101 em_advance,
22102 line_height,
22103 }
22104 }
22105
22106 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22107 self.load_diff_task.clone()
22108 }
22109
22110 fn read_metadata_from_db(
22111 &mut self,
22112 item_id: u64,
22113 workspace_id: WorkspaceId,
22114 window: &mut Window,
22115 cx: &mut Context<Editor>,
22116 ) {
22117 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22118 && !self.mode.is_minimap()
22119 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22120 {
22121 let buffer_snapshot = OnceCell::new();
22122
22123 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22124 && !folds.is_empty()
22125 {
22126 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22127 self.fold_ranges(
22128 folds
22129 .into_iter()
22130 .map(|(start, end)| {
22131 snapshot.clip_offset(start, Bias::Left)
22132 ..snapshot.clip_offset(end, Bias::Right)
22133 })
22134 .collect(),
22135 false,
22136 window,
22137 cx,
22138 );
22139 }
22140
22141 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22142 && !selections.is_empty()
22143 {
22144 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22145 // skip adding the initial selection to selection history
22146 self.selection_history.mode = SelectionHistoryMode::Skipping;
22147 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22148 s.select_ranges(selections.into_iter().map(|(start, end)| {
22149 snapshot.clip_offset(start, Bias::Left)
22150 ..snapshot.clip_offset(end, Bias::Right)
22151 }));
22152 });
22153 self.selection_history.mode = SelectionHistoryMode::Normal;
22154 };
22155 }
22156
22157 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22158 }
22159
22160 fn update_lsp_data(
22161 &mut self,
22162 for_buffer: Option<BufferId>,
22163 window: &mut Window,
22164 cx: &mut Context<'_, Self>,
22165 ) {
22166 self.pull_diagnostics(for_buffer, window, cx);
22167 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22168 }
22169
22170 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22171 if self.ignore_lsp_data() {
22172 return;
22173 }
22174 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22175 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22176 }
22177 }
22178
22179 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22180 if self.ignore_lsp_data() {
22181 return;
22182 }
22183
22184 if !self.registered_buffers.contains_key(&buffer_id)
22185 && let Some(project) = self.project.as_ref()
22186 {
22187 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22188 project.update(cx, |project, cx| {
22189 self.registered_buffers.insert(
22190 buffer_id,
22191 project.register_buffer_with_language_servers(&buffer, cx),
22192 );
22193 });
22194 } else {
22195 self.registered_buffers.remove(&buffer_id);
22196 }
22197 }
22198 }
22199
22200 fn ignore_lsp_data(&self) -> bool {
22201 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22202 // skip any LSP updates for it.
22203 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22204 }
22205}
22206
22207fn edit_for_markdown_paste<'a>(
22208 buffer: &MultiBufferSnapshot,
22209 range: Range<usize>,
22210 to_insert: &'a str,
22211 url: Option<url::Url>,
22212) -> (Range<usize>, Cow<'a, str>) {
22213 if url.is_none() {
22214 return (range, Cow::Borrowed(to_insert));
22215 };
22216
22217 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22218
22219 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22220 Cow::Borrowed(to_insert)
22221 } else {
22222 Cow::Owned(format!("[{old_text}]({to_insert})"))
22223 };
22224 (range, new_text)
22225}
22226
22227#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
22228pub enum VimFlavor {
22229 Vim,
22230 Helix,
22231}
22232
22233pub fn vim_flavor(cx: &App) -> Option<VimFlavor> {
22234 if vim_mode_setting::HelixModeSetting::try_get(cx)
22235 .map(|helix_mode| helix_mode.0)
22236 .unwrap_or(false)
22237 {
22238 Some(VimFlavor::Helix)
22239 } else if vim_mode_setting::VimModeSetting::try_get(cx)
22240 .map(|vim_mode| vim_mode.0)
22241 .unwrap_or(false)
22242 {
22243 Some(VimFlavor::Vim)
22244 } else {
22245 None // neither vim nor helix mode
22246 }
22247}
22248
22249fn process_completion_for_edit(
22250 completion: &Completion,
22251 intent: CompletionIntent,
22252 buffer: &Entity<Buffer>,
22253 cursor_position: &text::Anchor,
22254 cx: &mut Context<Editor>,
22255) -> CompletionEdit {
22256 let buffer = buffer.read(cx);
22257 let buffer_snapshot = buffer.snapshot();
22258 let (snippet, new_text) = if completion.is_snippet() {
22259 let mut snippet_source = completion.new_text.clone();
22260 // Workaround for typescript language server issues so that methods don't expand within
22261 // strings and functions with type expressions. The previous point is used because the query
22262 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22263 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22264 let previous_point = if previous_point.column > 0 {
22265 cursor_position.to_previous_offset(&buffer_snapshot)
22266 } else {
22267 cursor_position.to_offset(&buffer_snapshot)
22268 };
22269 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22270 && scope.prefers_label_for_snippet_in_completion()
22271 && let Some(label) = completion.label()
22272 && matches!(
22273 completion.kind(),
22274 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22275 )
22276 {
22277 snippet_source = label;
22278 }
22279 match Snippet::parse(&snippet_source).log_err() {
22280 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22281 None => (None, completion.new_text.clone()),
22282 }
22283 } else {
22284 (None, completion.new_text.clone())
22285 };
22286
22287 let mut range_to_replace = {
22288 let replace_range = &completion.replace_range;
22289 if let CompletionSource::Lsp {
22290 insert_range: Some(insert_range),
22291 ..
22292 } = &completion.source
22293 {
22294 debug_assert_eq!(
22295 insert_range.start, replace_range.start,
22296 "insert_range and replace_range should start at the same position"
22297 );
22298 debug_assert!(
22299 insert_range
22300 .start
22301 .cmp(cursor_position, &buffer_snapshot)
22302 .is_le(),
22303 "insert_range should start before or at cursor position"
22304 );
22305 debug_assert!(
22306 replace_range
22307 .start
22308 .cmp(cursor_position, &buffer_snapshot)
22309 .is_le(),
22310 "replace_range should start before or at cursor position"
22311 );
22312
22313 let should_replace = match intent {
22314 CompletionIntent::CompleteWithInsert => false,
22315 CompletionIntent::CompleteWithReplace => true,
22316 CompletionIntent::Complete | CompletionIntent::Compose => {
22317 let insert_mode =
22318 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22319 .completions
22320 .lsp_insert_mode;
22321 match insert_mode {
22322 LspInsertMode::Insert => false,
22323 LspInsertMode::Replace => true,
22324 LspInsertMode::ReplaceSubsequence => {
22325 let mut text_to_replace = buffer.chars_for_range(
22326 buffer.anchor_before(replace_range.start)
22327 ..buffer.anchor_after(replace_range.end),
22328 );
22329 let mut current_needle = text_to_replace.next();
22330 for haystack_ch in completion.label.text.chars() {
22331 if let Some(needle_ch) = current_needle
22332 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22333 {
22334 current_needle = text_to_replace.next();
22335 }
22336 }
22337 current_needle.is_none()
22338 }
22339 LspInsertMode::ReplaceSuffix => {
22340 if replace_range
22341 .end
22342 .cmp(cursor_position, &buffer_snapshot)
22343 .is_gt()
22344 {
22345 let range_after_cursor = *cursor_position..replace_range.end;
22346 let text_after_cursor = buffer
22347 .text_for_range(
22348 buffer.anchor_before(range_after_cursor.start)
22349 ..buffer.anchor_after(range_after_cursor.end),
22350 )
22351 .collect::<String>()
22352 .to_ascii_lowercase();
22353 completion
22354 .label
22355 .text
22356 .to_ascii_lowercase()
22357 .ends_with(&text_after_cursor)
22358 } else {
22359 true
22360 }
22361 }
22362 }
22363 }
22364 };
22365
22366 if should_replace {
22367 replace_range.clone()
22368 } else {
22369 insert_range.clone()
22370 }
22371 } else {
22372 replace_range.clone()
22373 }
22374 };
22375
22376 if range_to_replace
22377 .end
22378 .cmp(cursor_position, &buffer_snapshot)
22379 .is_lt()
22380 {
22381 range_to_replace.end = *cursor_position;
22382 }
22383
22384 CompletionEdit {
22385 new_text,
22386 replace_range: range_to_replace.to_offset(buffer),
22387 snippet,
22388 }
22389}
22390
22391struct CompletionEdit {
22392 new_text: String,
22393 replace_range: Range<usize>,
22394 snippet: Option<Snippet>,
22395}
22396
22397fn insert_extra_newline_brackets(
22398 buffer: &MultiBufferSnapshot,
22399 range: Range<usize>,
22400 language: &language::LanguageScope,
22401) -> bool {
22402 let leading_whitespace_len = buffer
22403 .reversed_chars_at(range.start)
22404 .take_while(|c| c.is_whitespace() && *c != '\n')
22405 .map(|c| c.len_utf8())
22406 .sum::<usize>();
22407 let trailing_whitespace_len = buffer
22408 .chars_at(range.end)
22409 .take_while(|c| c.is_whitespace() && *c != '\n')
22410 .map(|c| c.len_utf8())
22411 .sum::<usize>();
22412 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22413
22414 language.brackets().any(|(pair, enabled)| {
22415 let pair_start = pair.start.trim_end();
22416 let pair_end = pair.end.trim_start();
22417
22418 enabled
22419 && pair.newline
22420 && buffer.contains_str_at(range.end, pair_end)
22421 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22422 })
22423}
22424
22425fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22426 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22427 [(buffer, range, _)] => (*buffer, range.clone()),
22428 _ => return false,
22429 };
22430 let pair = {
22431 let mut result: Option<BracketMatch> = None;
22432
22433 for pair in buffer
22434 .all_bracket_ranges(range.clone())
22435 .filter(move |pair| {
22436 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22437 })
22438 {
22439 let len = pair.close_range.end - pair.open_range.start;
22440
22441 if let Some(existing) = &result {
22442 let existing_len = existing.close_range.end - existing.open_range.start;
22443 if len > existing_len {
22444 continue;
22445 }
22446 }
22447
22448 result = Some(pair);
22449 }
22450
22451 result
22452 };
22453 let Some(pair) = pair else {
22454 return false;
22455 };
22456 pair.newline_only
22457 && buffer
22458 .chars_for_range(pair.open_range.end..range.start)
22459 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22460 .all(|c| c.is_whitespace() && c != '\n')
22461}
22462
22463fn update_uncommitted_diff_for_buffer(
22464 editor: Entity<Editor>,
22465 project: &Entity<Project>,
22466 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22467 buffer: Entity<MultiBuffer>,
22468 cx: &mut App,
22469) -> Task<()> {
22470 let mut tasks = Vec::new();
22471 project.update(cx, |project, cx| {
22472 for buffer in buffers {
22473 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22474 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22475 }
22476 }
22477 });
22478 cx.spawn(async move |cx| {
22479 let diffs = future::join_all(tasks).await;
22480 if editor
22481 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22482 .unwrap_or(false)
22483 {
22484 return;
22485 }
22486
22487 buffer
22488 .update(cx, |buffer, cx| {
22489 for diff in diffs.into_iter().flatten() {
22490 buffer.add_diff(diff, cx);
22491 }
22492 })
22493 .ok();
22494 })
22495}
22496
22497fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22498 let tab_size = tab_size.get() as usize;
22499 let mut width = offset;
22500
22501 for ch in text.chars() {
22502 width += if ch == '\t' {
22503 tab_size - (width % tab_size)
22504 } else {
22505 1
22506 };
22507 }
22508
22509 width - offset
22510}
22511
22512#[cfg(test)]
22513mod tests {
22514 use super::*;
22515
22516 #[test]
22517 fn test_string_size_with_expanded_tabs() {
22518 let nz = |val| NonZeroU32::new(val).unwrap();
22519 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22520 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22521 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22522 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22523 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22524 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22525 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22526 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22527 }
22528}
22529
22530/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22531struct WordBreakingTokenizer<'a> {
22532 input: &'a str,
22533}
22534
22535impl<'a> WordBreakingTokenizer<'a> {
22536 fn new(input: &'a str) -> Self {
22537 Self { input }
22538 }
22539}
22540
22541fn is_char_ideographic(ch: char) -> bool {
22542 use unicode_script::Script::*;
22543 use unicode_script::UnicodeScript;
22544 matches!(ch.script(), Han | Tangut | Yi)
22545}
22546
22547fn is_grapheme_ideographic(text: &str) -> bool {
22548 text.chars().any(is_char_ideographic)
22549}
22550
22551fn is_grapheme_whitespace(text: &str) -> bool {
22552 text.chars().any(|x| x.is_whitespace())
22553}
22554
22555fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22556 text.chars()
22557 .next()
22558 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22559}
22560
22561#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22562enum WordBreakToken<'a> {
22563 Word { token: &'a str, grapheme_len: usize },
22564 InlineWhitespace { token: &'a str, grapheme_len: usize },
22565 Newline,
22566}
22567
22568impl<'a> Iterator for WordBreakingTokenizer<'a> {
22569 /// Yields a span, the count of graphemes in the token, and whether it was
22570 /// whitespace. Note that it also breaks at word boundaries.
22571 type Item = WordBreakToken<'a>;
22572
22573 fn next(&mut self) -> Option<Self::Item> {
22574 use unicode_segmentation::UnicodeSegmentation;
22575 if self.input.is_empty() {
22576 return None;
22577 }
22578
22579 let mut iter = self.input.graphemes(true).peekable();
22580 let mut offset = 0;
22581 let mut grapheme_len = 0;
22582 if let Some(first_grapheme) = iter.next() {
22583 let is_newline = first_grapheme == "\n";
22584 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22585 offset += first_grapheme.len();
22586 grapheme_len += 1;
22587 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22588 if let Some(grapheme) = iter.peek().copied()
22589 && should_stay_with_preceding_ideograph(grapheme)
22590 {
22591 offset += grapheme.len();
22592 grapheme_len += 1;
22593 }
22594 } else {
22595 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22596 let mut next_word_bound = words.peek().copied();
22597 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22598 next_word_bound = words.next();
22599 }
22600 while let Some(grapheme) = iter.peek().copied() {
22601 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22602 break;
22603 };
22604 if is_grapheme_whitespace(grapheme) != is_whitespace
22605 || (grapheme == "\n") != is_newline
22606 {
22607 break;
22608 };
22609 offset += grapheme.len();
22610 grapheme_len += 1;
22611 iter.next();
22612 }
22613 }
22614 let token = &self.input[..offset];
22615 self.input = &self.input[offset..];
22616 if token == "\n" {
22617 Some(WordBreakToken::Newline)
22618 } else if is_whitespace {
22619 Some(WordBreakToken::InlineWhitespace {
22620 token,
22621 grapheme_len,
22622 })
22623 } else {
22624 Some(WordBreakToken::Word {
22625 token,
22626 grapheme_len,
22627 })
22628 }
22629 } else {
22630 None
22631 }
22632 }
22633}
22634
22635#[test]
22636fn test_word_breaking_tokenizer() {
22637 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22638 ("", &[]),
22639 (" ", &[whitespace(" ", 2)]),
22640 ("Ʒ", &[word("Ʒ", 1)]),
22641 ("Ǽ", &[word("Ǽ", 1)]),
22642 ("⋑", &[word("⋑", 1)]),
22643 ("⋑⋑", &[word("⋑⋑", 2)]),
22644 (
22645 "原理,进而",
22646 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22647 ),
22648 (
22649 "hello world",
22650 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22651 ),
22652 (
22653 "hello, world",
22654 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22655 ),
22656 (
22657 " hello world",
22658 &[
22659 whitespace(" ", 2),
22660 word("hello", 5),
22661 whitespace(" ", 1),
22662 word("world", 5),
22663 ],
22664 ),
22665 (
22666 "这是什么 \n 钢笔",
22667 &[
22668 word("这", 1),
22669 word("是", 1),
22670 word("什", 1),
22671 word("么", 1),
22672 whitespace(" ", 1),
22673 newline(),
22674 whitespace(" ", 1),
22675 word("钢", 1),
22676 word("笔", 1),
22677 ],
22678 ),
22679 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22680 ];
22681
22682 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22683 WordBreakToken::Word {
22684 token,
22685 grapheme_len,
22686 }
22687 }
22688
22689 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22690 WordBreakToken::InlineWhitespace {
22691 token,
22692 grapheme_len,
22693 }
22694 }
22695
22696 fn newline() -> WordBreakToken<'static> {
22697 WordBreakToken::Newline
22698 }
22699
22700 for (input, result) in tests {
22701 assert_eq!(
22702 WordBreakingTokenizer::new(input)
22703 .collect::<Vec<_>>()
22704 .as_slice(),
22705 *result,
22706 );
22707 }
22708}
22709
22710fn wrap_with_prefix(
22711 first_line_prefix: String,
22712 subsequent_lines_prefix: String,
22713 unwrapped_text: String,
22714 wrap_column: usize,
22715 tab_size: NonZeroU32,
22716 preserve_existing_whitespace: bool,
22717) -> String {
22718 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22719 let subsequent_lines_prefix_len =
22720 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22721 let mut wrapped_text = String::new();
22722 let mut current_line = first_line_prefix;
22723 let mut is_first_line = true;
22724
22725 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22726 let mut current_line_len = first_line_prefix_len;
22727 let mut in_whitespace = false;
22728 for token in tokenizer {
22729 let have_preceding_whitespace = in_whitespace;
22730 match token {
22731 WordBreakToken::Word {
22732 token,
22733 grapheme_len,
22734 } => {
22735 in_whitespace = false;
22736 let current_prefix_len = if is_first_line {
22737 first_line_prefix_len
22738 } else {
22739 subsequent_lines_prefix_len
22740 };
22741 if current_line_len + grapheme_len > wrap_column
22742 && current_line_len != current_prefix_len
22743 {
22744 wrapped_text.push_str(current_line.trim_end());
22745 wrapped_text.push('\n');
22746 is_first_line = false;
22747 current_line = subsequent_lines_prefix.clone();
22748 current_line_len = subsequent_lines_prefix_len;
22749 }
22750 current_line.push_str(token);
22751 current_line_len += grapheme_len;
22752 }
22753 WordBreakToken::InlineWhitespace {
22754 mut token,
22755 mut grapheme_len,
22756 } => {
22757 in_whitespace = true;
22758 if have_preceding_whitespace && !preserve_existing_whitespace {
22759 continue;
22760 }
22761 if !preserve_existing_whitespace {
22762 // Keep a single whitespace grapheme as-is
22763 if let Some(first) =
22764 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22765 {
22766 token = first;
22767 } else {
22768 token = " ";
22769 }
22770 grapheme_len = 1;
22771 }
22772 let current_prefix_len = if is_first_line {
22773 first_line_prefix_len
22774 } else {
22775 subsequent_lines_prefix_len
22776 };
22777 if current_line_len + grapheme_len > wrap_column {
22778 wrapped_text.push_str(current_line.trim_end());
22779 wrapped_text.push('\n');
22780 is_first_line = false;
22781 current_line = subsequent_lines_prefix.clone();
22782 current_line_len = subsequent_lines_prefix_len;
22783 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22784 current_line.push_str(token);
22785 current_line_len += grapheme_len;
22786 }
22787 }
22788 WordBreakToken::Newline => {
22789 in_whitespace = true;
22790 let current_prefix_len = if is_first_line {
22791 first_line_prefix_len
22792 } else {
22793 subsequent_lines_prefix_len
22794 };
22795 if preserve_existing_whitespace {
22796 wrapped_text.push_str(current_line.trim_end());
22797 wrapped_text.push('\n');
22798 is_first_line = false;
22799 current_line = subsequent_lines_prefix.clone();
22800 current_line_len = subsequent_lines_prefix_len;
22801 } else if have_preceding_whitespace {
22802 continue;
22803 } else if current_line_len + 1 > wrap_column
22804 && current_line_len != current_prefix_len
22805 {
22806 wrapped_text.push_str(current_line.trim_end());
22807 wrapped_text.push('\n');
22808 is_first_line = false;
22809 current_line = subsequent_lines_prefix.clone();
22810 current_line_len = subsequent_lines_prefix_len;
22811 } else if current_line_len != current_prefix_len {
22812 current_line.push(' ');
22813 current_line_len += 1;
22814 }
22815 }
22816 }
22817 }
22818
22819 if !current_line.is_empty() {
22820 wrapped_text.push_str(¤t_line);
22821 }
22822 wrapped_text
22823}
22824
22825#[test]
22826fn test_wrap_with_prefix() {
22827 assert_eq!(
22828 wrap_with_prefix(
22829 "# ".to_string(),
22830 "# ".to_string(),
22831 "abcdefg".to_string(),
22832 4,
22833 NonZeroU32::new(4).unwrap(),
22834 false,
22835 ),
22836 "# abcdefg"
22837 );
22838 assert_eq!(
22839 wrap_with_prefix(
22840 "".to_string(),
22841 "".to_string(),
22842 "\thello world".to_string(),
22843 8,
22844 NonZeroU32::new(4).unwrap(),
22845 false,
22846 ),
22847 "hello\nworld"
22848 );
22849 assert_eq!(
22850 wrap_with_prefix(
22851 "// ".to_string(),
22852 "// ".to_string(),
22853 "xx \nyy zz aa bb cc".to_string(),
22854 12,
22855 NonZeroU32::new(4).unwrap(),
22856 false,
22857 ),
22858 "// xx yy zz\n// aa bb cc"
22859 );
22860 assert_eq!(
22861 wrap_with_prefix(
22862 String::new(),
22863 String::new(),
22864 "这是什么 \n 钢笔".to_string(),
22865 3,
22866 NonZeroU32::new(4).unwrap(),
22867 false,
22868 ),
22869 "这是什\n么 钢\n笔"
22870 );
22871 assert_eq!(
22872 wrap_with_prefix(
22873 String::new(),
22874 String::new(),
22875 format!("foo{}bar", '\u{2009}'), // thin space
22876 80,
22877 NonZeroU32::new(4).unwrap(),
22878 false,
22879 ),
22880 format!("foo{}bar", '\u{2009}')
22881 );
22882}
22883
22884pub trait CollaborationHub {
22885 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22886 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22887 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22888}
22889
22890impl CollaborationHub for Entity<Project> {
22891 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22892 self.read(cx).collaborators()
22893 }
22894
22895 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22896 self.read(cx).user_store().read(cx).participant_indices()
22897 }
22898
22899 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22900 let this = self.read(cx);
22901 let user_ids = this.collaborators().values().map(|c| c.user_id);
22902 this.user_store().read(cx).participant_names(user_ids, cx)
22903 }
22904}
22905
22906pub trait SemanticsProvider {
22907 fn hover(
22908 &self,
22909 buffer: &Entity<Buffer>,
22910 position: text::Anchor,
22911 cx: &mut App,
22912 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22913
22914 fn inline_values(
22915 &self,
22916 buffer_handle: Entity<Buffer>,
22917 range: Range<text::Anchor>,
22918 cx: &mut App,
22919 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22920
22921 fn applicable_inlay_chunks(
22922 &self,
22923 buffer: &Entity<Buffer>,
22924 ranges: &[Range<text::Anchor>],
22925 cx: &mut App,
22926 ) -> Vec<Range<BufferRow>>;
22927
22928 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
22929
22930 fn inlay_hints(
22931 &self,
22932 invalidate: InvalidationStrategy,
22933 buffer: Entity<Buffer>,
22934 ranges: Vec<Range<text::Anchor>>,
22935 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
22936 cx: &mut App,
22937 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
22938
22939 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22940
22941 fn document_highlights(
22942 &self,
22943 buffer: &Entity<Buffer>,
22944 position: text::Anchor,
22945 cx: &mut App,
22946 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22947
22948 fn definitions(
22949 &self,
22950 buffer: &Entity<Buffer>,
22951 position: text::Anchor,
22952 kind: GotoDefinitionKind,
22953 cx: &mut App,
22954 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22955
22956 fn range_for_rename(
22957 &self,
22958 buffer: &Entity<Buffer>,
22959 position: text::Anchor,
22960 cx: &mut App,
22961 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22962
22963 fn perform_rename(
22964 &self,
22965 buffer: &Entity<Buffer>,
22966 position: text::Anchor,
22967 new_name: String,
22968 cx: &mut App,
22969 ) -> Option<Task<Result<ProjectTransaction>>>;
22970}
22971
22972pub trait CompletionProvider {
22973 fn completions(
22974 &self,
22975 excerpt_id: ExcerptId,
22976 buffer: &Entity<Buffer>,
22977 buffer_position: text::Anchor,
22978 trigger: CompletionContext,
22979 window: &mut Window,
22980 cx: &mut Context<Editor>,
22981 ) -> Task<Result<Vec<CompletionResponse>>>;
22982
22983 fn resolve_completions(
22984 &self,
22985 _buffer: Entity<Buffer>,
22986 _completion_indices: Vec<usize>,
22987 _completions: Rc<RefCell<Box<[Completion]>>>,
22988 _cx: &mut Context<Editor>,
22989 ) -> Task<Result<bool>> {
22990 Task::ready(Ok(false))
22991 }
22992
22993 fn apply_additional_edits_for_completion(
22994 &self,
22995 _buffer: Entity<Buffer>,
22996 _completions: Rc<RefCell<Box<[Completion]>>>,
22997 _completion_index: usize,
22998 _push_to_history: bool,
22999 _cx: &mut Context<Editor>,
23000 ) -> Task<Result<Option<language::Transaction>>> {
23001 Task::ready(Ok(None))
23002 }
23003
23004 fn is_completion_trigger(
23005 &self,
23006 buffer: &Entity<Buffer>,
23007 position: language::Anchor,
23008 text: &str,
23009 trigger_in_words: bool,
23010 menu_is_open: bool,
23011 cx: &mut Context<Editor>,
23012 ) -> bool;
23013
23014 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23015
23016 fn sort_completions(&self) -> bool {
23017 true
23018 }
23019
23020 fn filter_completions(&self) -> bool {
23021 true
23022 }
23023}
23024
23025pub trait CodeActionProvider {
23026 fn id(&self) -> Arc<str>;
23027
23028 fn code_actions(
23029 &self,
23030 buffer: &Entity<Buffer>,
23031 range: Range<text::Anchor>,
23032 window: &mut Window,
23033 cx: &mut App,
23034 ) -> Task<Result<Vec<CodeAction>>>;
23035
23036 fn apply_code_action(
23037 &self,
23038 buffer_handle: Entity<Buffer>,
23039 action: CodeAction,
23040 excerpt_id: ExcerptId,
23041 push_to_history: bool,
23042 window: &mut Window,
23043 cx: &mut App,
23044 ) -> Task<Result<ProjectTransaction>>;
23045}
23046
23047impl CodeActionProvider for Entity<Project> {
23048 fn id(&self) -> Arc<str> {
23049 "project".into()
23050 }
23051
23052 fn code_actions(
23053 &self,
23054 buffer: &Entity<Buffer>,
23055 range: Range<text::Anchor>,
23056 _window: &mut Window,
23057 cx: &mut App,
23058 ) -> Task<Result<Vec<CodeAction>>> {
23059 self.update(cx, |project, cx| {
23060 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23061 let code_actions = project.code_actions(buffer, range, None, cx);
23062 cx.background_spawn(async move {
23063 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23064 Ok(code_lens_actions
23065 .context("code lens fetch")?
23066 .into_iter()
23067 .flatten()
23068 .chain(
23069 code_actions
23070 .context("code action fetch")?
23071 .into_iter()
23072 .flatten(),
23073 )
23074 .collect())
23075 })
23076 })
23077 }
23078
23079 fn apply_code_action(
23080 &self,
23081 buffer_handle: Entity<Buffer>,
23082 action: CodeAction,
23083 _excerpt_id: ExcerptId,
23084 push_to_history: bool,
23085 _window: &mut Window,
23086 cx: &mut App,
23087 ) -> Task<Result<ProjectTransaction>> {
23088 self.update(cx, |project, cx| {
23089 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23090 })
23091 }
23092}
23093
23094fn snippet_completions(
23095 project: &Project,
23096 buffer: &Entity<Buffer>,
23097 buffer_position: text::Anchor,
23098 cx: &mut App,
23099) -> Task<Result<CompletionResponse>> {
23100 let languages = buffer.read(cx).languages_at(buffer_position);
23101 let snippet_store = project.snippets().read(cx);
23102
23103 let scopes: Vec<_> = languages
23104 .iter()
23105 .filter_map(|language| {
23106 let language_name = language.lsp_id();
23107 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23108
23109 if snippets.is_empty() {
23110 None
23111 } else {
23112 Some((language.default_scope(), snippets))
23113 }
23114 })
23115 .collect();
23116
23117 if scopes.is_empty() {
23118 return Task::ready(Ok(CompletionResponse {
23119 completions: vec![],
23120 display_options: CompletionDisplayOptions::default(),
23121 is_incomplete: false,
23122 }));
23123 }
23124
23125 let snapshot = buffer.read(cx).text_snapshot();
23126 let executor = cx.background_executor().clone();
23127
23128 cx.background_spawn(async move {
23129 let mut is_incomplete = false;
23130 let mut completions: Vec<Completion> = Vec::new();
23131 for (scope, snippets) in scopes.into_iter() {
23132 let classifier =
23133 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
23134
23135 const MAX_WORD_PREFIX_LEN: usize = 128;
23136 let last_word: String = snapshot
23137 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
23138 .take(MAX_WORD_PREFIX_LEN)
23139 .take_while(|c| classifier.is_word(*c))
23140 .collect::<String>()
23141 .chars()
23142 .rev()
23143 .collect();
23144
23145 if last_word.is_empty() {
23146 return Ok(CompletionResponse {
23147 completions: vec![],
23148 display_options: CompletionDisplayOptions::default(),
23149 is_incomplete: true,
23150 });
23151 }
23152
23153 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
23154 let to_lsp = |point: &text::Anchor| {
23155 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23156 point_to_lsp(end)
23157 };
23158 let lsp_end = to_lsp(&buffer_position);
23159
23160 let candidates = snippets
23161 .iter()
23162 .enumerate()
23163 .flat_map(|(ix, snippet)| {
23164 snippet
23165 .prefix
23166 .iter()
23167 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23168 })
23169 .collect::<Vec<StringMatchCandidate>>();
23170
23171 const MAX_RESULTS: usize = 100;
23172 let mut matches = fuzzy::match_strings(
23173 &candidates,
23174 &last_word,
23175 last_word.chars().any(|c| c.is_uppercase()),
23176 true,
23177 MAX_RESULTS,
23178 &Default::default(),
23179 executor.clone(),
23180 )
23181 .await;
23182
23183 if matches.len() >= MAX_RESULTS {
23184 is_incomplete = true;
23185 }
23186
23187 // Remove all candidates where the query's start does not match the start of any word in the candidate
23188 if let Some(query_start) = last_word.chars().next() {
23189 matches.retain(|string_match| {
23190 split_words(&string_match.string).any(|word| {
23191 // Check that the first codepoint of the word as lowercase matches the first
23192 // codepoint of the query as lowercase
23193 word.chars()
23194 .flat_map(|codepoint| codepoint.to_lowercase())
23195 .zip(query_start.to_lowercase())
23196 .all(|(word_cp, query_cp)| word_cp == query_cp)
23197 })
23198 });
23199 }
23200
23201 let matched_strings = matches
23202 .into_iter()
23203 .map(|m| m.string)
23204 .collect::<HashSet<_>>();
23205
23206 completions.extend(snippets.iter().filter_map(|snippet| {
23207 let matching_prefix = snippet
23208 .prefix
23209 .iter()
23210 .find(|prefix| matched_strings.contains(*prefix))?;
23211 let start = as_offset - last_word.len();
23212 let start = snapshot.anchor_before(start);
23213 let range = start..buffer_position;
23214 let lsp_start = to_lsp(&start);
23215 let lsp_range = lsp::Range {
23216 start: lsp_start,
23217 end: lsp_end,
23218 };
23219 Some(Completion {
23220 replace_range: range,
23221 new_text: snippet.body.clone(),
23222 source: CompletionSource::Lsp {
23223 insert_range: None,
23224 server_id: LanguageServerId(usize::MAX),
23225 resolved: true,
23226 lsp_completion: Box::new(lsp::CompletionItem {
23227 label: snippet.prefix.first().unwrap().clone(),
23228 kind: Some(CompletionItemKind::SNIPPET),
23229 label_details: snippet.description.as_ref().map(|description| {
23230 lsp::CompletionItemLabelDetails {
23231 detail: Some(description.clone()),
23232 description: None,
23233 }
23234 }),
23235 insert_text_format: Some(InsertTextFormat::SNIPPET),
23236 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23237 lsp::InsertReplaceEdit {
23238 new_text: snippet.body.clone(),
23239 insert: lsp_range,
23240 replace: lsp_range,
23241 },
23242 )),
23243 filter_text: Some(snippet.body.clone()),
23244 sort_text: Some(char::MAX.to_string()),
23245 ..lsp::CompletionItem::default()
23246 }),
23247 lsp_defaults: None,
23248 },
23249 label: CodeLabel::plain(matching_prefix.clone(), None),
23250 icon_path: None,
23251 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23252 single_line: snippet.name.clone().into(),
23253 plain_text: snippet
23254 .description
23255 .clone()
23256 .map(|description| description.into()),
23257 }),
23258 insert_text_mode: None,
23259 confirm: None,
23260 })
23261 }))
23262 }
23263
23264 Ok(CompletionResponse {
23265 completions,
23266 display_options: CompletionDisplayOptions::default(),
23267 is_incomplete,
23268 })
23269 })
23270}
23271
23272impl CompletionProvider for Entity<Project> {
23273 fn completions(
23274 &self,
23275 _excerpt_id: ExcerptId,
23276 buffer: &Entity<Buffer>,
23277 buffer_position: text::Anchor,
23278 options: CompletionContext,
23279 _window: &mut Window,
23280 cx: &mut Context<Editor>,
23281 ) -> Task<Result<Vec<CompletionResponse>>> {
23282 self.update(cx, |project, cx| {
23283 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23284 let project_completions = project.completions(buffer, buffer_position, options, cx);
23285 cx.background_spawn(async move {
23286 let mut responses = project_completions.await?;
23287 let snippets = snippets.await?;
23288 if !snippets.completions.is_empty() {
23289 responses.push(snippets);
23290 }
23291 Ok(responses)
23292 })
23293 })
23294 }
23295
23296 fn resolve_completions(
23297 &self,
23298 buffer: Entity<Buffer>,
23299 completion_indices: Vec<usize>,
23300 completions: Rc<RefCell<Box<[Completion]>>>,
23301 cx: &mut Context<Editor>,
23302 ) -> Task<Result<bool>> {
23303 self.update(cx, |project, cx| {
23304 project.lsp_store().update(cx, |lsp_store, cx| {
23305 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23306 })
23307 })
23308 }
23309
23310 fn apply_additional_edits_for_completion(
23311 &self,
23312 buffer: Entity<Buffer>,
23313 completions: Rc<RefCell<Box<[Completion]>>>,
23314 completion_index: usize,
23315 push_to_history: bool,
23316 cx: &mut Context<Editor>,
23317 ) -> Task<Result<Option<language::Transaction>>> {
23318 self.update(cx, |project, cx| {
23319 project.lsp_store().update(cx, |lsp_store, cx| {
23320 lsp_store.apply_additional_edits_for_completion(
23321 buffer,
23322 completions,
23323 completion_index,
23324 push_to_history,
23325 cx,
23326 )
23327 })
23328 })
23329 }
23330
23331 fn is_completion_trigger(
23332 &self,
23333 buffer: &Entity<Buffer>,
23334 position: language::Anchor,
23335 text: &str,
23336 trigger_in_words: bool,
23337 menu_is_open: bool,
23338 cx: &mut Context<Editor>,
23339 ) -> bool {
23340 let mut chars = text.chars();
23341 let char = if let Some(char) = chars.next() {
23342 char
23343 } else {
23344 return false;
23345 };
23346 if chars.next().is_some() {
23347 return false;
23348 }
23349
23350 let buffer = buffer.read(cx);
23351 let snapshot = buffer.snapshot();
23352 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23353 return false;
23354 }
23355 let classifier = snapshot
23356 .char_classifier_at(position)
23357 .scope_context(Some(CharScopeContext::Completion));
23358 if trigger_in_words && classifier.is_word(char) {
23359 return true;
23360 }
23361
23362 buffer.completion_triggers().contains(text)
23363 }
23364}
23365
23366impl SemanticsProvider for Entity<Project> {
23367 fn hover(
23368 &self,
23369 buffer: &Entity<Buffer>,
23370 position: text::Anchor,
23371 cx: &mut App,
23372 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23373 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23374 }
23375
23376 fn document_highlights(
23377 &self,
23378 buffer: &Entity<Buffer>,
23379 position: text::Anchor,
23380 cx: &mut App,
23381 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23382 Some(self.update(cx, |project, cx| {
23383 project.document_highlights(buffer, position, cx)
23384 }))
23385 }
23386
23387 fn definitions(
23388 &self,
23389 buffer: &Entity<Buffer>,
23390 position: text::Anchor,
23391 kind: GotoDefinitionKind,
23392 cx: &mut App,
23393 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23394 Some(self.update(cx, |project, cx| match kind {
23395 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23396 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23397 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23398 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23399 }))
23400 }
23401
23402 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23403 self.update(cx, |project, cx| {
23404 if project
23405 .active_debug_session(cx)
23406 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23407 {
23408 return true;
23409 }
23410
23411 buffer.update(cx, |buffer, cx| {
23412 project.any_language_server_supports_inlay_hints(buffer, cx)
23413 })
23414 })
23415 }
23416
23417 fn inline_values(
23418 &self,
23419 buffer_handle: Entity<Buffer>,
23420 range: Range<text::Anchor>,
23421 cx: &mut App,
23422 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23423 self.update(cx, |project, cx| {
23424 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23425
23426 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23427 })
23428 }
23429
23430 fn applicable_inlay_chunks(
23431 &self,
23432 buffer: &Entity<Buffer>,
23433 ranges: &[Range<text::Anchor>],
23434 cx: &mut App,
23435 ) -> Vec<Range<BufferRow>> {
23436 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23437 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23438 })
23439 }
23440
23441 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23442 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23443 lsp_store.invalidate_inlay_hints(for_buffers)
23444 });
23445 }
23446
23447 fn inlay_hints(
23448 &self,
23449 invalidate: InvalidationStrategy,
23450 buffer: Entity<Buffer>,
23451 ranges: Vec<Range<text::Anchor>>,
23452 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23453 cx: &mut App,
23454 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23455 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23456 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23457 }))
23458 }
23459
23460 fn range_for_rename(
23461 &self,
23462 buffer: &Entity<Buffer>,
23463 position: text::Anchor,
23464 cx: &mut App,
23465 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23466 Some(self.update(cx, |project, cx| {
23467 let buffer = buffer.clone();
23468 let task = project.prepare_rename(buffer.clone(), position, cx);
23469 cx.spawn(async move |_, cx| {
23470 Ok(match task.await? {
23471 PrepareRenameResponse::Success(range) => Some(range),
23472 PrepareRenameResponse::InvalidPosition => None,
23473 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23474 // Fallback on using TreeSitter info to determine identifier range
23475 buffer.read_with(cx, |buffer, _| {
23476 let snapshot = buffer.snapshot();
23477 let (range, kind) = snapshot.surrounding_word(position, None);
23478 if kind != Some(CharKind::Word) {
23479 return None;
23480 }
23481 Some(
23482 snapshot.anchor_before(range.start)
23483 ..snapshot.anchor_after(range.end),
23484 )
23485 })?
23486 }
23487 })
23488 })
23489 }))
23490 }
23491
23492 fn perform_rename(
23493 &self,
23494 buffer: &Entity<Buffer>,
23495 position: text::Anchor,
23496 new_name: String,
23497 cx: &mut App,
23498 ) -> Option<Task<Result<ProjectTransaction>>> {
23499 Some(self.update(cx, |project, cx| {
23500 project.perform_rename(buffer.clone(), position, new_name, cx)
23501 }))
23502 }
23503}
23504
23505fn consume_contiguous_rows(
23506 contiguous_row_selections: &mut Vec<Selection<Point>>,
23507 selection: &Selection<Point>,
23508 display_map: &DisplaySnapshot,
23509 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23510) -> (MultiBufferRow, MultiBufferRow) {
23511 contiguous_row_selections.push(selection.clone());
23512 let start_row = starting_row(selection, display_map);
23513 let mut end_row = ending_row(selection, display_map);
23514
23515 while let Some(next_selection) = selections.peek() {
23516 if next_selection.start.row <= end_row.0 {
23517 end_row = ending_row(next_selection, display_map);
23518 contiguous_row_selections.push(selections.next().unwrap().clone());
23519 } else {
23520 break;
23521 }
23522 }
23523 (start_row, end_row)
23524}
23525
23526fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23527 if selection.start.column > 0 {
23528 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23529 } else {
23530 MultiBufferRow(selection.start.row)
23531 }
23532}
23533
23534fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23535 if next_selection.end.column > 0 || next_selection.is_empty() {
23536 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23537 } else {
23538 MultiBufferRow(next_selection.end.row)
23539 }
23540}
23541
23542impl EditorSnapshot {
23543 pub fn remote_selections_in_range<'a>(
23544 &'a self,
23545 range: &'a Range<Anchor>,
23546 collaboration_hub: &dyn CollaborationHub,
23547 cx: &'a App,
23548 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23549 let participant_names = collaboration_hub.user_names(cx);
23550 let participant_indices = collaboration_hub.user_participant_indices(cx);
23551 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23552 let collaborators_by_replica_id = collaborators_by_peer_id
23553 .values()
23554 .map(|collaborator| (collaborator.replica_id, collaborator))
23555 .collect::<HashMap<_, _>>();
23556 self.buffer_snapshot()
23557 .selections_in_range(range, false)
23558 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23559 if replica_id == ReplicaId::AGENT {
23560 Some(RemoteSelection {
23561 replica_id,
23562 selection,
23563 cursor_shape,
23564 line_mode,
23565 collaborator_id: CollaboratorId::Agent,
23566 user_name: Some("Agent".into()),
23567 color: cx.theme().players().agent(),
23568 })
23569 } else {
23570 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23571 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23572 let user_name = participant_names.get(&collaborator.user_id).cloned();
23573 Some(RemoteSelection {
23574 replica_id,
23575 selection,
23576 cursor_shape,
23577 line_mode,
23578 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23579 user_name,
23580 color: if let Some(index) = participant_index {
23581 cx.theme().players().color_for_participant(index.0)
23582 } else {
23583 cx.theme().players().absent()
23584 },
23585 })
23586 }
23587 })
23588 }
23589
23590 pub fn hunks_for_ranges(
23591 &self,
23592 ranges: impl IntoIterator<Item = Range<Point>>,
23593 ) -> Vec<MultiBufferDiffHunk> {
23594 let mut hunks = Vec::new();
23595 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23596 HashMap::default();
23597 for query_range in ranges {
23598 let query_rows =
23599 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23600 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23601 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23602 ) {
23603 // Include deleted hunks that are adjacent to the query range, because
23604 // otherwise they would be missed.
23605 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23606 if hunk.status().is_deleted() {
23607 intersects_range |= hunk.row_range.start == query_rows.end;
23608 intersects_range |= hunk.row_range.end == query_rows.start;
23609 }
23610 if intersects_range {
23611 if !processed_buffer_rows
23612 .entry(hunk.buffer_id)
23613 .or_default()
23614 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23615 {
23616 continue;
23617 }
23618 hunks.push(hunk);
23619 }
23620 }
23621 }
23622
23623 hunks
23624 }
23625
23626 fn display_diff_hunks_for_rows<'a>(
23627 &'a self,
23628 display_rows: Range<DisplayRow>,
23629 folded_buffers: &'a HashSet<BufferId>,
23630 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23631 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23632 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23633
23634 self.buffer_snapshot()
23635 .diff_hunks_in_range(buffer_start..buffer_end)
23636 .filter_map(|hunk| {
23637 if folded_buffers.contains(&hunk.buffer_id) {
23638 return None;
23639 }
23640
23641 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23642 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23643
23644 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23645 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23646
23647 let display_hunk = if hunk_display_start.column() != 0 {
23648 DisplayDiffHunk::Folded {
23649 display_row: hunk_display_start.row(),
23650 }
23651 } else {
23652 let mut end_row = hunk_display_end.row();
23653 if hunk_display_end.column() > 0 {
23654 end_row.0 += 1;
23655 }
23656 let is_created_file = hunk.is_created_file();
23657 DisplayDiffHunk::Unfolded {
23658 status: hunk.status(),
23659 diff_base_byte_range: hunk.diff_base_byte_range,
23660 display_row_range: hunk_display_start.row()..end_row,
23661 multi_buffer_range: Anchor::range_in_buffer(
23662 hunk.excerpt_id,
23663 hunk.buffer_id,
23664 hunk.buffer_range,
23665 ),
23666 is_created_file,
23667 }
23668 };
23669
23670 Some(display_hunk)
23671 })
23672 }
23673
23674 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23675 self.display_snapshot
23676 .buffer_snapshot()
23677 .language_at(position)
23678 }
23679
23680 pub fn is_focused(&self) -> bool {
23681 self.is_focused
23682 }
23683
23684 pub fn placeholder_text(&self) -> Option<String> {
23685 self.placeholder_display_snapshot
23686 .as_ref()
23687 .map(|display_map| display_map.text())
23688 }
23689
23690 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23691 self.scroll_anchor.scroll_position(&self.display_snapshot)
23692 }
23693
23694 fn gutter_dimensions(
23695 &self,
23696 font_id: FontId,
23697 font_size: Pixels,
23698 max_line_number_width: Pixels,
23699 cx: &App,
23700 ) -> Option<GutterDimensions> {
23701 if !self.show_gutter {
23702 return None;
23703 }
23704
23705 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23706 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23707
23708 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23709 matches!(
23710 ProjectSettings::get_global(cx).git.git_gutter,
23711 GitGutterSetting::TrackedFiles
23712 )
23713 });
23714 let gutter_settings = EditorSettings::get_global(cx).gutter;
23715 let show_line_numbers = self
23716 .show_line_numbers
23717 .unwrap_or(gutter_settings.line_numbers);
23718 let line_gutter_width = if show_line_numbers {
23719 // Avoid flicker-like gutter resizes when the line number gains another digit by
23720 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23721 let min_width_for_number_on_gutter =
23722 ch_advance * gutter_settings.min_line_number_digits as f32;
23723 max_line_number_width.max(min_width_for_number_on_gutter)
23724 } else {
23725 0.0.into()
23726 };
23727
23728 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23729 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23730
23731 let git_blame_entries_width =
23732 self.git_blame_gutter_max_author_length
23733 .map(|max_author_length| {
23734 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23735 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23736
23737 /// The number of characters to dedicate to gaps and margins.
23738 const SPACING_WIDTH: usize = 4;
23739
23740 let max_char_count = max_author_length.min(renderer.max_author_length())
23741 + ::git::SHORT_SHA_LENGTH
23742 + MAX_RELATIVE_TIMESTAMP.len()
23743 + SPACING_WIDTH;
23744
23745 ch_advance * max_char_count
23746 });
23747
23748 let is_singleton = self.buffer_snapshot().is_singleton();
23749
23750 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23751 left_padding += if !is_singleton {
23752 ch_width * 4.0
23753 } else if show_runnables || show_breakpoints {
23754 ch_width * 3.0
23755 } else if show_git_gutter && show_line_numbers {
23756 ch_width * 2.0
23757 } else if show_git_gutter || show_line_numbers {
23758 ch_width
23759 } else {
23760 px(0.)
23761 };
23762
23763 let shows_folds = is_singleton && gutter_settings.folds;
23764
23765 let right_padding = if shows_folds && show_line_numbers {
23766 ch_width * 4.0
23767 } else if shows_folds || (!is_singleton && show_line_numbers) {
23768 ch_width * 3.0
23769 } else if show_line_numbers {
23770 ch_width
23771 } else {
23772 px(0.)
23773 };
23774
23775 Some(GutterDimensions {
23776 left_padding,
23777 right_padding,
23778 width: line_gutter_width + left_padding + right_padding,
23779 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23780 git_blame_entries_width,
23781 })
23782 }
23783
23784 pub fn render_crease_toggle(
23785 &self,
23786 buffer_row: MultiBufferRow,
23787 row_contains_cursor: bool,
23788 editor: Entity<Editor>,
23789 window: &mut Window,
23790 cx: &mut App,
23791 ) -> Option<AnyElement> {
23792 let folded = self.is_line_folded(buffer_row);
23793 let mut is_foldable = false;
23794
23795 if let Some(crease) = self
23796 .crease_snapshot
23797 .query_row(buffer_row, self.buffer_snapshot())
23798 {
23799 is_foldable = true;
23800 match crease {
23801 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23802 if let Some(render_toggle) = render_toggle {
23803 let toggle_callback =
23804 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23805 if folded {
23806 editor.update(cx, |editor, cx| {
23807 editor.fold_at(buffer_row, window, cx)
23808 });
23809 } else {
23810 editor.update(cx, |editor, cx| {
23811 editor.unfold_at(buffer_row, window, cx)
23812 });
23813 }
23814 });
23815 return Some((render_toggle)(
23816 buffer_row,
23817 folded,
23818 toggle_callback,
23819 window,
23820 cx,
23821 ));
23822 }
23823 }
23824 }
23825 }
23826
23827 is_foldable |= self.starts_indent(buffer_row);
23828
23829 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23830 Some(
23831 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23832 .toggle_state(folded)
23833 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23834 if folded {
23835 this.unfold_at(buffer_row, window, cx);
23836 } else {
23837 this.fold_at(buffer_row, window, cx);
23838 }
23839 }))
23840 .into_any_element(),
23841 )
23842 } else {
23843 None
23844 }
23845 }
23846
23847 pub fn render_crease_trailer(
23848 &self,
23849 buffer_row: MultiBufferRow,
23850 window: &mut Window,
23851 cx: &mut App,
23852 ) -> Option<AnyElement> {
23853 let folded = self.is_line_folded(buffer_row);
23854 if let Crease::Inline { render_trailer, .. } = self
23855 .crease_snapshot
23856 .query_row(buffer_row, self.buffer_snapshot())?
23857 {
23858 let render_trailer = render_trailer.as_ref()?;
23859 Some(render_trailer(buffer_row, folded, window, cx))
23860 } else {
23861 None
23862 }
23863 }
23864}
23865
23866impl Deref for EditorSnapshot {
23867 type Target = DisplaySnapshot;
23868
23869 fn deref(&self) -> &Self::Target {
23870 &self.display_snapshot
23871 }
23872}
23873
23874#[derive(Clone, Debug, PartialEq, Eq)]
23875pub enum EditorEvent {
23876 InputIgnored {
23877 text: Arc<str>,
23878 },
23879 InputHandled {
23880 utf16_range_to_replace: Option<Range<isize>>,
23881 text: Arc<str>,
23882 },
23883 ExcerptsAdded {
23884 buffer: Entity<Buffer>,
23885 predecessor: ExcerptId,
23886 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23887 },
23888 ExcerptsRemoved {
23889 ids: Vec<ExcerptId>,
23890 removed_buffer_ids: Vec<BufferId>,
23891 },
23892 BufferFoldToggled {
23893 ids: Vec<ExcerptId>,
23894 folded: bool,
23895 },
23896 ExcerptsEdited {
23897 ids: Vec<ExcerptId>,
23898 },
23899 ExcerptsExpanded {
23900 ids: Vec<ExcerptId>,
23901 },
23902 BufferEdited,
23903 Edited {
23904 transaction_id: clock::Lamport,
23905 },
23906 Reparsed(BufferId),
23907 Focused,
23908 FocusedIn,
23909 Blurred,
23910 DirtyChanged,
23911 Saved,
23912 TitleChanged,
23913 SelectionsChanged {
23914 local: bool,
23915 },
23916 ScrollPositionChanged {
23917 local: bool,
23918 autoscroll: bool,
23919 },
23920 TransactionUndone {
23921 transaction_id: clock::Lamport,
23922 },
23923 TransactionBegun {
23924 transaction_id: clock::Lamport,
23925 },
23926 CursorShapeChanged,
23927 BreadcrumbsChanged,
23928 PushedToNavHistory {
23929 anchor: Anchor,
23930 is_deactivate: bool,
23931 },
23932}
23933
23934impl EventEmitter<EditorEvent> for Editor {}
23935
23936impl Focusable for Editor {
23937 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23938 self.focus_handle.clone()
23939 }
23940}
23941
23942impl Render for Editor {
23943 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23944 let settings = ThemeSettings::get_global(cx);
23945
23946 let mut text_style = match self.mode {
23947 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23948 color: cx.theme().colors().editor_foreground,
23949 font_family: settings.ui_font.family.clone(),
23950 font_features: settings.ui_font.features.clone(),
23951 font_fallbacks: settings.ui_font.fallbacks.clone(),
23952 font_size: rems(0.875).into(),
23953 font_weight: settings.ui_font.weight,
23954 line_height: relative(settings.buffer_line_height.value()),
23955 ..Default::default()
23956 },
23957 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23958 color: cx.theme().colors().editor_foreground,
23959 font_family: settings.buffer_font.family.clone(),
23960 font_features: settings.buffer_font.features.clone(),
23961 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23962 font_size: settings.buffer_font_size(cx).into(),
23963 font_weight: settings.buffer_font.weight,
23964 line_height: relative(settings.buffer_line_height.value()),
23965 ..Default::default()
23966 },
23967 };
23968 if let Some(text_style_refinement) = &self.text_style_refinement {
23969 text_style.refine(text_style_refinement)
23970 }
23971
23972 let background = match self.mode {
23973 EditorMode::SingleLine => cx.theme().system().transparent,
23974 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23975 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23976 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23977 };
23978
23979 EditorElement::new(
23980 &cx.entity(),
23981 EditorStyle {
23982 background,
23983 border: cx.theme().colors().border,
23984 local_player: cx.theme().players().local(),
23985 text: text_style,
23986 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23987 syntax: cx.theme().syntax().clone(),
23988 status: cx.theme().status().clone(),
23989 inlay_hints_style: make_inlay_hints_style(cx),
23990 edit_prediction_styles: make_suggestion_styles(cx),
23991 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23992 show_underlines: self.diagnostics_enabled(),
23993 },
23994 )
23995 }
23996}
23997
23998impl EntityInputHandler for Editor {
23999 fn text_for_range(
24000 &mut self,
24001 range_utf16: Range<usize>,
24002 adjusted_range: &mut Option<Range<usize>>,
24003 _: &mut Window,
24004 cx: &mut Context<Self>,
24005 ) -> Option<String> {
24006 let snapshot = self.buffer.read(cx).read(cx);
24007 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
24008 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
24009 if (start.0..end.0) != range_utf16 {
24010 adjusted_range.replace(start.0..end.0);
24011 }
24012 Some(snapshot.text_for_range(start..end).collect())
24013 }
24014
24015 fn selected_text_range(
24016 &mut self,
24017 ignore_disabled_input: bool,
24018 _: &mut Window,
24019 cx: &mut Context<Self>,
24020 ) -> Option<UTF16Selection> {
24021 // Prevent the IME menu from appearing when holding down an alphabetic key
24022 // while input is disabled.
24023 if !ignore_disabled_input && !self.input_enabled {
24024 return None;
24025 }
24026
24027 let selection = self
24028 .selections
24029 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
24030 let range = selection.range();
24031
24032 Some(UTF16Selection {
24033 range: range.start.0..range.end.0,
24034 reversed: selection.reversed,
24035 })
24036 }
24037
24038 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24039 let snapshot = self.buffer.read(cx).read(cx);
24040 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24041 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
24042 }
24043
24044 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24045 self.clear_highlights::<InputComposition>(cx);
24046 self.ime_transaction.take();
24047 }
24048
24049 fn replace_text_in_range(
24050 &mut self,
24051 range_utf16: Option<Range<usize>>,
24052 text: &str,
24053 window: &mut Window,
24054 cx: &mut Context<Self>,
24055 ) {
24056 if !self.input_enabled {
24057 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24058 return;
24059 }
24060
24061 self.transact(window, cx, |this, window, cx| {
24062 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24063 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24064 Some(this.selection_replacement_ranges(range_utf16, cx))
24065 } else {
24066 this.marked_text_ranges(cx)
24067 };
24068
24069 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24070 let newest_selection_id = this.selections.newest_anchor().id;
24071 this.selections
24072 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24073 .iter()
24074 .zip(ranges_to_replace.iter())
24075 .find_map(|(selection, range)| {
24076 if selection.id == newest_selection_id {
24077 Some(
24078 (range.start.0 as isize - selection.head().0 as isize)
24079 ..(range.end.0 as isize - selection.head().0 as isize),
24080 )
24081 } else {
24082 None
24083 }
24084 })
24085 });
24086
24087 cx.emit(EditorEvent::InputHandled {
24088 utf16_range_to_replace: range_to_replace,
24089 text: text.into(),
24090 });
24091
24092 if let Some(new_selected_ranges) = new_selected_ranges {
24093 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24094 selections.select_ranges(new_selected_ranges)
24095 });
24096 this.backspace(&Default::default(), window, cx);
24097 }
24098
24099 this.handle_input(text, window, cx);
24100 });
24101
24102 if let Some(transaction) = self.ime_transaction {
24103 self.buffer.update(cx, |buffer, cx| {
24104 buffer.group_until_transaction(transaction, cx);
24105 });
24106 }
24107
24108 self.unmark_text(window, cx);
24109 }
24110
24111 fn replace_and_mark_text_in_range(
24112 &mut self,
24113 range_utf16: Option<Range<usize>>,
24114 text: &str,
24115 new_selected_range_utf16: Option<Range<usize>>,
24116 window: &mut Window,
24117 cx: &mut Context<Self>,
24118 ) {
24119 if !self.input_enabled {
24120 return;
24121 }
24122
24123 let transaction = self.transact(window, cx, |this, window, cx| {
24124 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24125 let snapshot = this.buffer.read(cx).read(cx);
24126 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24127 for marked_range in &mut marked_ranges {
24128 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24129 marked_range.start.0 += relative_range_utf16.start;
24130 marked_range.start =
24131 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24132 marked_range.end =
24133 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24134 }
24135 }
24136 Some(marked_ranges)
24137 } else if let Some(range_utf16) = range_utf16 {
24138 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24139 Some(this.selection_replacement_ranges(range_utf16, cx))
24140 } else {
24141 None
24142 };
24143
24144 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24145 let newest_selection_id = this.selections.newest_anchor().id;
24146 this.selections
24147 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24148 .iter()
24149 .zip(ranges_to_replace.iter())
24150 .find_map(|(selection, range)| {
24151 if selection.id == newest_selection_id {
24152 Some(
24153 (range.start.0 as isize - selection.head().0 as isize)
24154 ..(range.end.0 as isize - selection.head().0 as isize),
24155 )
24156 } else {
24157 None
24158 }
24159 })
24160 });
24161
24162 cx.emit(EditorEvent::InputHandled {
24163 utf16_range_to_replace: range_to_replace,
24164 text: text.into(),
24165 });
24166
24167 if let Some(ranges) = ranges_to_replace {
24168 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24169 s.select_ranges(ranges)
24170 });
24171 }
24172
24173 let marked_ranges = {
24174 let snapshot = this.buffer.read(cx).read(cx);
24175 this.selections
24176 .disjoint_anchors_arc()
24177 .iter()
24178 .map(|selection| {
24179 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24180 })
24181 .collect::<Vec<_>>()
24182 };
24183
24184 if text.is_empty() {
24185 this.unmark_text(window, cx);
24186 } else {
24187 this.highlight_text::<InputComposition>(
24188 marked_ranges.clone(),
24189 HighlightStyle {
24190 underline: Some(UnderlineStyle {
24191 thickness: px(1.),
24192 color: None,
24193 wavy: false,
24194 }),
24195 ..Default::default()
24196 },
24197 cx,
24198 );
24199 }
24200
24201 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24202 let use_autoclose = this.use_autoclose;
24203 let use_auto_surround = this.use_auto_surround;
24204 this.set_use_autoclose(false);
24205 this.set_use_auto_surround(false);
24206 this.handle_input(text, window, cx);
24207 this.set_use_autoclose(use_autoclose);
24208 this.set_use_auto_surround(use_auto_surround);
24209
24210 if let Some(new_selected_range) = new_selected_range_utf16 {
24211 let snapshot = this.buffer.read(cx).read(cx);
24212 let new_selected_ranges = marked_ranges
24213 .into_iter()
24214 .map(|marked_range| {
24215 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24216 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24217 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24218 snapshot.clip_offset_utf16(new_start, Bias::Left)
24219 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24220 })
24221 .collect::<Vec<_>>();
24222
24223 drop(snapshot);
24224 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24225 selections.select_ranges(new_selected_ranges)
24226 });
24227 }
24228 });
24229
24230 self.ime_transaction = self.ime_transaction.or(transaction);
24231 if let Some(transaction) = self.ime_transaction {
24232 self.buffer.update(cx, |buffer, cx| {
24233 buffer.group_until_transaction(transaction, cx);
24234 });
24235 }
24236
24237 if self.text_highlights::<InputComposition>(cx).is_none() {
24238 self.ime_transaction.take();
24239 }
24240 }
24241
24242 fn bounds_for_range(
24243 &mut self,
24244 range_utf16: Range<usize>,
24245 element_bounds: gpui::Bounds<Pixels>,
24246 window: &mut Window,
24247 cx: &mut Context<Self>,
24248 ) -> Option<gpui::Bounds<Pixels>> {
24249 let text_layout_details = self.text_layout_details(window);
24250 let CharacterDimensions {
24251 em_width,
24252 em_advance,
24253 line_height,
24254 } = self.character_dimensions(window);
24255
24256 let snapshot = self.snapshot(window, cx);
24257 let scroll_position = snapshot.scroll_position();
24258 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24259
24260 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24261 let x = Pixels::from(
24262 ScrollOffset::from(
24263 snapshot.x_for_display_point(start, &text_layout_details)
24264 + self.gutter_dimensions.full_width(),
24265 ) - scroll_left,
24266 );
24267 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24268
24269 Some(Bounds {
24270 origin: element_bounds.origin + point(x, y),
24271 size: size(em_width, line_height),
24272 })
24273 }
24274
24275 fn character_index_for_point(
24276 &mut self,
24277 point: gpui::Point<Pixels>,
24278 _window: &mut Window,
24279 _cx: &mut Context<Self>,
24280 ) -> Option<usize> {
24281 let position_map = self.last_position_map.as_ref()?;
24282 if !position_map.text_hitbox.contains(&point) {
24283 return None;
24284 }
24285 let display_point = position_map.point_for_position(point).previous_valid;
24286 let anchor = position_map
24287 .snapshot
24288 .display_point_to_anchor(display_point, Bias::Left);
24289 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24290 Some(utf16_offset.0)
24291 }
24292
24293 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24294 self.input_enabled
24295 }
24296}
24297
24298trait SelectionExt {
24299 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24300 fn spanned_rows(
24301 &self,
24302 include_end_if_at_line_start: bool,
24303 map: &DisplaySnapshot,
24304 ) -> Range<MultiBufferRow>;
24305}
24306
24307impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24308 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24309 let start = self
24310 .start
24311 .to_point(map.buffer_snapshot())
24312 .to_display_point(map);
24313 let end = self
24314 .end
24315 .to_point(map.buffer_snapshot())
24316 .to_display_point(map);
24317 if self.reversed {
24318 end..start
24319 } else {
24320 start..end
24321 }
24322 }
24323
24324 fn spanned_rows(
24325 &self,
24326 include_end_if_at_line_start: bool,
24327 map: &DisplaySnapshot,
24328 ) -> Range<MultiBufferRow> {
24329 let start = self.start.to_point(map.buffer_snapshot());
24330 let mut end = self.end.to_point(map.buffer_snapshot());
24331 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24332 end.row -= 1;
24333 }
24334
24335 let buffer_start = map.prev_line_boundary(start).0;
24336 let buffer_end = map.next_line_boundary(end).0;
24337 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24338 }
24339}
24340
24341impl<T: InvalidationRegion> InvalidationStack<T> {
24342 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24343 where
24344 S: Clone + ToOffset,
24345 {
24346 while let Some(region) = self.last() {
24347 let all_selections_inside_invalidation_ranges =
24348 if selections.len() == region.ranges().len() {
24349 selections
24350 .iter()
24351 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24352 .all(|(selection, invalidation_range)| {
24353 let head = selection.head().to_offset(buffer);
24354 invalidation_range.start <= head && invalidation_range.end >= head
24355 })
24356 } else {
24357 false
24358 };
24359
24360 if all_selections_inside_invalidation_ranges {
24361 break;
24362 } else {
24363 self.pop();
24364 }
24365 }
24366 }
24367}
24368
24369impl<T> Default for InvalidationStack<T> {
24370 fn default() -> Self {
24371 Self(Default::default())
24372 }
24373}
24374
24375impl<T> Deref for InvalidationStack<T> {
24376 type Target = Vec<T>;
24377
24378 fn deref(&self) -> &Self::Target {
24379 &self.0
24380 }
24381}
24382
24383impl<T> DerefMut for InvalidationStack<T> {
24384 fn deref_mut(&mut self) -> &mut Self::Target {
24385 &mut self.0
24386 }
24387}
24388
24389impl InvalidationRegion for SnippetState {
24390 fn ranges(&self) -> &[Range<Anchor>] {
24391 &self.ranges[self.active_index]
24392 }
24393}
24394
24395fn edit_prediction_edit_text(
24396 current_snapshot: &BufferSnapshot,
24397 edits: &[(Range<Anchor>, impl AsRef<str>)],
24398 edit_preview: &EditPreview,
24399 include_deletions: bool,
24400 cx: &App,
24401) -> HighlightedText {
24402 let edits = edits
24403 .iter()
24404 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24405 .collect::<Vec<_>>();
24406
24407 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24408}
24409
24410fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24411 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24412 // Just show the raw edit text with basic styling
24413 let mut text = String::new();
24414 let mut highlights = Vec::new();
24415
24416 let insertion_highlight_style = HighlightStyle {
24417 color: Some(cx.theme().colors().text),
24418 ..Default::default()
24419 };
24420
24421 for (_, edit_text) in edits {
24422 let start_offset = text.len();
24423 text.push_str(edit_text);
24424 let end_offset = text.len();
24425
24426 if start_offset < end_offset {
24427 highlights.push((start_offset..end_offset, insertion_highlight_style));
24428 }
24429 }
24430
24431 HighlightedText {
24432 text: text.into(),
24433 highlights,
24434 }
24435}
24436
24437pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24438 match severity {
24439 lsp::DiagnosticSeverity::ERROR => colors.error,
24440 lsp::DiagnosticSeverity::WARNING => colors.warning,
24441 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24442 lsp::DiagnosticSeverity::HINT => colors.info,
24443 _ => colors.ignored,
24444 }
24445}
24446
24447pub fn styled_runs_for_code_label<'a>(
24448 label: &'a CodeLabel,
24449 syntax_theme: &'a theme::SyntaxTheme,
24450) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24451 let fade_out = HighlightStyle {
24452 fade_out: Some(0.35),
24453 ..Default::default()
24454 };
24455
24456 let mut prev_end = label.filter_range.end;
24457 label
24458 .runs
24459 .iter()
24460 .enumerate()
24461 .flat_map(move |(ix, (range, highlight_id))| {
24462 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24463 style
24464 } else {
24465 return Default::default();
24466 };
24467 let muted_style = style.highlight(fade_out);
24468
24469 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24470 if range.start >= label.filter_range.end {
24471 if range.start > prev_end {
24472 runs.push((prev_end..range.start, fade_out));
24473 }
24474 runs.push((range.clone(), muted_style));
24475 } else if range.end <= label.filter_range.end {
24476 runs.push((range.clone(), style));
24477 } else {
24478 runs.push((range.start..label.filter_range.end, style));
24479 runs.push((label.filter_range.end..range.end, muted_style));
24480 }
24481 prev_end = cmp::max(prev_end, range.end);
24482
24483 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24484 runs.push((prev_end..label.text.len(), fade_out));
24485 }
24486
24487 runs
24488 })
24489}
24490
24491pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24492 let mut prev_index = 0;
24493 let mut prev_codepoint: Option<char> = None;
24494 text.char_indices()
24495 .chain([(text.len(), '\0')])
24496 .filter_map(move |(index, codepoint)| {
24497 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24498 let is_boundary = index == text.len()
24499 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24500 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24501 if is_boundary {
24502 let chunk = &text[prev_index..index];
24503 prev_index = index;
24504 Some(chunk)
24505 } else {
24506 None
24507 }
24508 })
24509}
24510
24511pub trait RangeToAnchorExt: Sized {
24512 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24513
24514 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24515 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24516 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24517 }
24518}
24519
24520impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24521 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24522 let start_offset = self.start.to_offset(snapshot);
24523 let end_offset = self.end.to_offset(snapshot);
24524 if start_offset == end_offset {
24525 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24526 } else {
24527 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24528 }
24529 }
24530}
24531
24532pub trait RowExt {
24533 fn as_f64(&self) -> f64;
24534
24535 fn next_row(&self) -> Self;
24536
24537 fn previous_row(&self) -> Self;
24538
24539 fn minus(&self, other: Self) -> u32;
24540}
24541
24542impl RowExt for DisplayRow {
24543 fn as_f64(&self) -> f64 {
24544 self.0 as _
24545 }
24546
24547 fn next_row(&self) -> Self {
24548 Self(self.0 + 1)
24549 }
24550
24551 fn previous_row(&self) -> Self {
24552 Self(self.0.saturating_sub(1))
24553 }
24554
24555 fn minus(&self, other: Self) -> u32 {
24556 self.0 - other.0
24557 }
24558}
24559
24560impl RowExt for MultiBufferRow {
24561 fn as_f64(&self) -> f64 {
24562 self.0 as _
24563 }
24564
24565 fn next_row(&self) -> Self {
24566 Self(self.0 + 1)
24567 }
24568
24569 fn previous_row(&self) -> Self {
24570 Self(self.0.saturating_sub(1))
24571 }
24572
24573 fn minus(&self, other: Self) -> u32 {
24574 self.0 - other.0
24575 }
24576}
24577
24578trait RowRangeExt {
24579 type Row;
24580
24581 fn len(&self) -> usize;
24582
24583 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24584}
24585
24586impl RowRangeExt for Range<MultiBufferRow> {
24587 type Row = MultiBufferRow;
24588
24589 fn len(&self) -> usize {
24590 (self.end.0 - self.start.0) as usize
24591 }
24592
24593 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24594 (self.start.0..self.end.0).map(MultiBufferRow)
24595 }
24596}
24597
24598impl RowRangeExt for Range<DisplayRow> {
24599 type Row = DisplayRow;
24600
24601 fn len(&self) -> usize {
24602 (self.end.0 - self.start.0) as usize
24603 }
24604
24605 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24606 (self.start.0..self.end.0).map(DisplayRow)
24607 }
24608}
24609
24610/// If select range has more than one line, we
24611/// just point the cursor to range.start.
24612fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24613 if range.start.row == range.end.row {
24614 range
24615 } else {
24616 range.start..range.start
24617 }
24618}
24619pub struct KillRing(ClipboardItem);
24620impl Global for KillRing {}
24621
24622const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24623
24624enum BreakpointPromptEditAction {
24625 Log,
24626 Condition,
24627 HitCondition,
24628}
24629
24630struct BreakpointPromptEditor {
24631 pub(crate) prompt: Entity<Editor>,
24632 editor: WeakEntity<Editor>,
24633 breakpoint_anchor: Anchor,
24634 breakpoint: Breakpoint,
24635 edit_action: BreakpointPromptEditAction,
24636 block_ids: HashSet<CustomBlockId>,
24637 editor_margins: Arc<Mutex<EditorMargins>>,
24638 _subscriptions: Vec<Subscription>,
24639}
24640
24641impl BreakpointPromptEditor {
24642 const MAX_LINES: u8 = 4;
24643
24644 fn new(
24645 editor: WeakEntity<Editor>,
24646 breakpoint_anchor: Anchor,
24647 breakpoint: Breakpoint,
24648 edit_action: BreakpointPromptEditAction,
24649 window: &mut Window,
24650 cx: &mut Context<Self>,
24651 ) -> Self {
24652 let base_text = match edit_action {
24653 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24654 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24655 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24656 }
24657 .map(|msg| msg.to_string())
24658 .unwrap_or_default();
24659
24660 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24661 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24662
24663 let prompt = cx.new(|cx| {
24664 let mut prompt = Editor::new(
24665 EditorMode::AutoHeight {
24666 min_lines: 1,
24667 max_lines: Some(Self::MAX_LINES as usize),
24668 },
24669 buffer,
24670 None,
24671 window,
24672 cx,
24673 );
24674 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24675 prompt.set_show_cursor_when_unfocused(false, cx);
24676 prompt.set_placeholder_text(
24677 match edit_action {
24678 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24679 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24680 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24681 },
24682 window,
24683 cx,
24684 );
24685
24686 prompt
24687 });
24688
24689 Self {
24690 prompt,
24691 editor,
24692 breakpoint_anchor,
24693 breakpoint,
24694 edit_action,
24695 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24696 block_ids: Default::default(),
24697 _subscriptions: vec![],
24698 }
24699 }
24700
24701 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24702 self.block_ids.extend(block_ids)
24703 }
24704
24705 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24706 if let Some(editor) = self.editor.upgrade() {
24707 let message = self
24708 .prompt
24709 .read(cx)
24710 .buffer
24711 .read(cx)
24712 .as_singleton()
24713 .expect("A multi buffer in breakpoint prompt isn't possible")
24714 .read(cx)
24715 .as_rope()
24716 .to_string();
24717
24718 editor.update(cx, |editor, cx| {
24719 editor.edit_breakpoint_at_anchor(
24720 self.breakpoint_anchor,
24721 self.breakpoint.clone(),
24722 match self.edit_action {
24723 BreakpointPromptEditAction::Log => {
24724 BreakpointEditAction::EditLogMessage(message.into())
24725 }
24726 BreakpointPromptEditAction::Condition => {
24727 BreakpointEditAction::EditCondition(message.into())
24728 }
24729 BreakpointPromptEditAction::HitCondition => {
24730 BreakpointEditAction::EditHitCondition(message.into())
24731 }
24732 },
24733 cx,
24734 );
24735
24736 editor.remove_blocks(self.block_ids.clone(), None, cx);
24737 cx.focus_self(window);
24738 });
24739 }
24740 }
24741
24742 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24743 self.editor
24744 .update(cx, |editor, cx| {
24745 editor.remove_blocks(self.block_ids.clone(), None, cx);
24746 window.focus(&editor.focus_handle);
24747 })
24748 .log_err();
24749 }
24750
24751 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24752 let settings = ThemeSettings::get_global(cx);
24753 let text_style = TextStyle {
24754 color: if self.prompt.read(cx).read_only(cx) {
24755 cx.theme().colors().text_disabled
24756 } else {
24757 cx.theme().colors().text
24758 },
24759 font_family: settings.buffer_font.family.clone(),
24760 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24761 font_size: settings.buffer_font_size(cx).into(),
24762 font_weight: settings.buffer_font.weight,
24763 line_height: relative(settings.buffer_line_height.value()),
24764 ..Default::default()
24765 };
24766 EditorElement::new(
24767 &self.prompt,
24768 EditorStyle {
24769 background: cx.theme().colors().editor_background,
24770 local_player: cx.theme().players().local(),
24771 text: text_style,
24772 ..Default::default()
24773 },
24774 )
24775 }
24776}
24777
24778impl Render for BreakpointPromptEditor {
24779 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24780 let editor_margins = *self.editor_margins.lock();
24781 let gutter_dimensions = editor_margins.gutter;
24782 h_flex()
24783 .key_context("Editor")
24784 .bg(cx.theme().colors().editor_background)
24785 .border_y_1()
24786 .border_color(cx.theme().status().info_border)
24787 .size_full()
24788 .py(window.line_height() / 2.5)
24789 .on_action(cx.listener(Self::confirm))
24790 .on_action(cx.listener(Self::cancel))
24791 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24792 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24793 }
24794}
24795
24796impl Focusable for BreakpointPromptEditor {
24797 fn focus_handle(&self, cx: &App) -> FocusHandle {
24798 self.prompt.focus_handle(cx)
24799 }
24800}
24801
24802fn all_edits_insertions_or_deletions(
24803 edits: &Vec<(Range<Anchor>, Arc<str>)>,
24804 snapshot: &MultiBufferSnapshot,
24805) -> bool {
24806 let mut all_insertions = true;
24807 let mut all_deletions = true;
24808
24809 for (range, new_text) in edits.iter() {
24810 let range_is_empty = range.to_offset(snapshot).is_empty();
24811 let text_is_empty = new_text.is_empty();
24812
24813 if range_is_empty != text_is_empty {
24814 if range_is_empty {
24815 all_deletions = false;
24816 } else {
24817 all_insertions = false;
24818 }
24819 } else {
24820 return false;
24821 }
24822
24823 if !all_insertions && !all_deletions {
24824 return false;
24825 }
24826 }
24827 all_insertions || all_deletions
24828}
24829
24830struct MissingEditPredictionKeybindingTooltip;
24831
24832impl Render for MissingEditPredictionKeybindingTooltip {
24833 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24834 ui::tooltip_container(cx, |container, cx| {
24835 container
24836 .flex_shrink_0()
24837 .max_w_80()
24838 .min_h(rems_from_px(124.))
24839 .justify_between()
24840 .child(
24841 v_flex()
24842 .flex_1()
24843 .text_ui_sm(cx)
24844 .child(Label::new("Conflict with Accept Keybinding"))
24845 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24846 )
24847 .child(
24848 h_flex()
24849 .pb_1()
24850 .gap_1()
24851 .items_end()
24852 .w_full()
24853 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24854 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24855 }))
24856 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24857 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24858 })),
24859 )
24860 })
24861 }
24862}
24863
24864#[derive(Debug, Clone, Copy, PartialEq)]
24865pub struct LineHighlight {
24866 pub background: Background,
24867 pub border: Option<gpui::Hsla>,
24868 pub include_gutter: bool,
24869 pub type_id: Option<TypeId>,
24870}
24871
24872struct LineManipulationResult {
24873 pub new_text: String,
24874 pub line_count_before: usize,
24875 pub line_count_after: usize,
24876}
24877
24878fn render_diff_hunk_controls(
24879 row: u32,
24880 status: &DiffHunkStatus,
24881 hunk_range: Range<Anchor>,
24882 is_created_file: bool,
24883 line_height: Pixels,
24884 editor: &Entity<Editor>,
24885 _window: &mut Window,
24886 cx: &mut App,
24887) -> AnyElement {
24888 h_flex()
24889 .h(line_height)
24890 .mr_1()
24891 .gap_1()
24892 .px_0p5()
24893 .pb_1()
24894 .border_x_1()
24895 .border_b_1()
24896 .border_color(cx.theme().colors().border_variant)
24897 .rounded_b_lg()
24898 .bg(cx.theme().colors().editor_background)
24899 .gap_1()
24900 .block_mouse_except_scroll()
24901 .shadow_md()
24902 .child(if status.has_secondary_hunk() {
24903 Button::new(("stage", row as u64), "Stage")
24904 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24905 .tooltip({
24906 let focus_handle = editor.focus_handle(cx);
24907 move |_window, cx| {
24908 Tooltip::for_action_in(
24909 "Stage Hunk",
24910 &::git::ToggleStaged,
24911 &focus_handle,
24912 cx,
24913 )
24914 }
24915 })
24916 .on_click({
24917 let editor = editor.clone();
24918 move |_event, _window, cx| {
24919 editor.update(cx, |editor, cx| {
24920 editor.stage_or_unstage_diff_hunks(
24921 true,
24922 vec![hunk_range.start..hunk_range.start],
24923 cx,
24924 );
24925 });
24926 }
24927 })
24928 } else {
24929 Button::new(("unstage", row as u64), "Unstage")
24930 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24931 .tooltip({
24932 let focus_handle = editor.focus_handle(cx);
24933 move |_window, cx| {
24934 Tooltip::for_action_in(
24935 "Unstage Hunk",
24936 &::git::ToggleStaged,
24937 &focus_handle,
24938 cx,
24939 )
24940 }
24941 })
24942 .on_click({
24943 let editor = editor.clone();
24944 move |_event, _window, cx| {
24945 editor.update(cx, |editor, cx| {
24946 editor.stage_or_unstage_diff_hunks(
24947 false,
24948 vec![hunk_range.start..hunk_range.start],
24949 cx,
24950 );
24951 });
24952 }
24953 })
24954 })
24955 .child(
24956 Button::new(("restore", row as u64), "Restore")
24957 .tooltip({
24958 let focus_handle = editor.focus_handle(cx);
24959 move |_window, cx| {
24960 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
24961 }
24962 })
24963 .on_click({
24964 let editor = editor.clone();
24965 move |_event, window, cx| {
24966 editor.update(cx, |editor, cx| {
24967 let snapshot = editor.snapshot(window, cx);
24968 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24969 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24970 });
24971 }
24972 })
24973 .disabled(is_created_file),
24974 )
24975 .when(
24976 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24977 |el| {
24978 el.child(
24979 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24980 .shape(IconButtonShape::Square)
24981 .icon_size(IconSize::Small)
24982 // .disabled(!has_multiple_hunks)
24983 .tooltip({
24984 let focus_handle = editor.focus_handle(cx);
24985 move |_window, cx| {
24986 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
24987 }
24988 })
24989 .on_click({
24990 let editor = editor.clone();
24991 move |_event, window, cx| {
24992 editor.update(cx, |editor, cx| {
24993 let snapshot = editor.snapshot(window, cx);
24994 let position =
24995 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24996 editor.go_to_hunk_before_or_after_position(
24997 &snapshot,
24998 position,
24999 Direction::Next,
25000 window,
25001 cx,
25002 );
25003 editor.expand_selected_diff_hunks(cx);
25004 });
25005 }
25006 }),
25007 )
25008 .child(
25009 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25010 .shape(IconButtonShape::Square)
25011 .icon_size(IconSize::Small)
25012 // .disabled(!has_multiple_hunks)
25013 .tooltip({
25014 let focus_handle = editor.focus_handle(cx);
25015 move |_window, cx| {
25016 Tooltip::for_action_in(
25017 "Previous Hunk",
25018 &GoToPreviousHunk,
25019 &focus_handle,
25020 cx,
25021 )
25022 }
25023 })
25024 .on_click({
25025 let editor = editor.clone();
25026 move |_event, window, cx| {
25027 editor.update(cx, |editor, cx| {
25028 let snapshot = editor.snapshot(window, cx);
25029 let point =
25030 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25031 editor.go_to_hunk_before_or_after_position(
25032 &snapshot,
25033 point,
25034 Direction::Prev,
25035 window,
25036 cx,
25037 );
25038 editor.expand_selected_diff_hunks(cx);
25039 });
25040 }
25041 }),
25042 )
25043 },
25044 )
25045 .into_any_element()
25046}
25047
25048pub fn multibuffer_context_lines(cx: &App) -> u32 {
25049 EditorSettings::try_get(cx)
25050 .map(|settings| settings.excerpt_context_lines)
25051 .unwrap_or(2)
25052 .min(32)
25053}