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(display_map.clone(), multi_buffer.clone());
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.selections.display_map(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 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3370 if !other_selections.is_empty() {
3371 self.selections.change_with(cx, |selections| {
3372 selections.select_anchors(other_selections);
3373 });
3374 }
3375
3376 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3377 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3378 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3379 if other_selections.is_empty() {
3380 return;
3381 }
3382 this.selections.change_with(cx, |selections| {
3383 selections.select_anchors(other_selections);
3384 });
3385 }
3386 });
3387
3388 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3389 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3390 let these_selections = this.selections.disjoint_anchors().to_vec();
3391 if these_selections.is_empty() {
3392 return;
3393 }
3394 other.update(cx, |other_editor, cx| {
3395 other_editor.selections.change_with(cx, |selections| {
3396 selections.select_anchors(these_selections);
3397 })
3398 });
3399 }
3400 });
3401
3402 Subscription::join(other_subscription, this_subscription)
3403 }
3404
3405 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3406 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3407 /// effects of selection change occur at the end of the transaction.
3408 pub fn change_selections<R>(
3409 &mut self,
3410 effects: SelectionEffects,
3411 window: &mut Window,
3412 cx: &mut Context<Self>,
3413 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3414 ) -> R {
3415 if let Some(state) = &mut self.deferred_selection_effects_state {
3416 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3417 state.effects.completions = effects.completions;
3418 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3419 let (changed, result) = self.selections.change_with(cx, change);
3420 state.changed |= changed;
3421 return result;
3422 }
3423 let mut state = DeferredSelectionEffectsState {
3424 changed: false,
3425 effects,
3426 old_cursor_position: self.selections.newest_anchor().head(),
3427 history_entry: SelectionHistoryEntry {
3428 selections: self.selections.disjoint_anchors_arc(),
3429 select_next_state: self.select_next_state.clone(),
3430 select_prev_state: self.select_prev_state.clone(),
3431 add_selections_state: self.add_selections_state.clone(),
3432 },
3433 };
3434 let (changed, result) = self.selections.change_with(cx, change);
3435 state.changed = state.changed || changed;
3436 if self.defer_selection_effects {
3437 self.deferred_selection_effects_state = Some(state);
3438 } else {
3439 self.apply_selection_effects(state, window, cx);
3440 }
3441 result
3442 }
3443
3444 /// Defers the effects of selection change, so that the effects of multiple calls to
3445 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3446 /// to selection history and the state of popovers based on selection position aren't
3447 /// erroneously updated.
3448 pub fn with_selection_effects_deferred<R>(
3449 &mut self,
3450 window: &mut Window,
3451 cx: &mut Context<Self>,
3452 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3453 ) -> R {
3454 let already_deferred = self.defer_selection_effects;
3455 self.defer_selection_effects = true;
3456 let result = update(self, window, cx);
3457 if !already_deferred {
3458 self.defer_selection_effects = false;
3459 if let Some(state) = self.deferred_selection_effects_state.take() {
3460 self.apply_selection_effects(state, window, cx);
3461 }
3462 }
3463 result
3464 }
3465
3466 fn apply_selection_effects(
3467 &mut self,
3468 state: DeferredSelectionEffectsState,
3469 window: &mut Window,
3470 cx: &mut Context<Self>,
3471 ) {
3472 if state.changed {
3473 self.selection_history.push(state.history_entry);
3474
3475 if let Some(autoscroll) = state.effects.scroll {
3476 self.request_autoscroll(autoscroll, cx);
3477 }
3478
3479 let old_cursor_position = &state.old_cursor_position;
3480
3481 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3482
3483 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3484 self.show_signature_help(&ShowSignatureHelp, window, cx);
3485 }
3486 }
3487 }
3488
3489 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3490 where
3491 I: IntoIterator<Item = (Range<S>, T)>,
3492 S: ToOffset,
3493 T: Into<Arc<str>>,
3494 {
3495 if self.read_only(cx) {
3496 return;
3497 }
3498
3499 self.buffer
3500 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3501 }
3502
3503 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3504 where
3505 I: IntoIterator<Item = (Range<S>, T)>,
3506 S: ToOffset,
3507 T: Into<Arc<str>>,
3508 {
3509 if self.read_only(cx) {
3510 return;
3511 }
3512
3513 self.buffer.update(cx, |buffer, cx| {
3514 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3515 });
3516 }
3517
3518 pub fn edit_with_block_indent<I, S, T>(
3519 &mut self,
3520 edits: I,
3521 original_indent_columns: Vec<Option<u32>>,
3522 cx: &mut Context<Self>,
3523 ) where
3524 I: IntoIterator<Item = (Range<S>, T)>,
3525 S: ToOffset,
3526 T: Into<Arc<str>>,
3527 {
3528 if self.read_only(cx) {
3529 return;
3530 }
3531
3532 self.buffer.update(cx, |buffer, cx| {
3533 buffer.edit(
3534 edits,
3535 Some(AutoindentMode::Block {
3536 original_indent_columns,
3537 }),
3538 cx,
3539 )
3540 });
3541 }
3542
3543 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3544 self.hide_context_menu(window, cx);
3545
3546 match phase {
3547 SelectPhase::Begin {
3548 position,
3549 add,
3550 click_count,
3551 } => self.begin_selection(position, add, click_count, window, cx),
3552 SelectPhase::BeginColumnar {
3553 position,
3554 goal_column,
3555 reset,
3556 mode,
3557 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3558 SelectPhase::Extend {
3559 position,
3560 click_count,
3561 } => self.extend_selection(position, click_count, window, cx),
3562 SelectPhase::Update {
3563 position,
3564 goal_column,
3565 scroll_delta,
3566 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3567 SelectPhase::End => self.end_selection(window, cx),
3568 }
3569 }
3570
3571 fn extend_selection(
3572 &mut self,
3573 position: DisplayPoint,
3574 click_count: usize,
3575 window: &mut Window,
3576 cx: &mut Context<Self>,
3577 ) {
3578 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3579 let tail = self.selections.newest::<usize>(&display_map).tail();
3580 let click_count = click_count.max(match self.selections.select_mode() {
3581 SelectMode::Character => 1,
3582 SelectMode::Word(_) => 2,
3583 SelectMode::Line(_) => 3,
3584 SelectMode::All => 4,
3585 });
3586 self.begin_selection(position, false, click_count, window, cx);
3587
3588 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3589
3590 let current_selection = match self.selections.select_mode() {
3591 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3592 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3593 };
3594
3595 let mut pending_selection = self
3596 .selections
3597 .pending_anchor()
3598 .cloned()
3599 .expect("extend_selection not called with pending selection");
3600
3601 if pending_selection
3602 .start
3603 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3604 == Ordering::Greater
3605 {
3606 pending_selection.start = current_selection.start;
3607 }
3608 if pending_selection
3609 .end
3610 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3611 == Ordering::Less
3612 {
3613 pending_selection.end = current_selection.end;
3614 pending_selection.reversed = true;
3615 }
3616
3617 let mut pending_mode = self.selections.pending_mode().unwrap();
3618 match &mut pending_mode {
3619 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3620 _ => {}
3621 }
3622
3623 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3624 SelectionEffects::scroll(Autoscroll::fit())
3625 } else {
3626 SelectionEffects::no_scroll()
3627 };
3628
3629 self.change_selections(effects, window, cx, |s| {
3630 s.set_pending(pending_selection.clone(), pending_mode);
3631 s.set_is_extending(true);
3632 });
3633 }
3634
3635 fn begin_selection(
3636 &mut self,
3637 position: DisplayPoint,
3638 add: bool,
3639 click_count: usize,
3640 window: &mut Window,
3641 cx: &mut Context<Self>,
3642 ) {
3643 if !self.focus_handle.is_focused(window) {
3644 self.last_focused_descendant = None;
3645 window.focus(&self.focus_handle);
3646 }
3647
3648 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3649 let buffer = display_map.buffer_snapshot();
3650 let position = display_map.clip_point(position, Bias::Left);
3651
3652 let start;
3653 let end;
3654 let mode;
3655 let mut auto_scroll;
3656 match click_count {
3657 1 => {
3658 start = buffer.anchor_before(position.to_point(&display_map));
3659 end = start;
3660 mode = SelectMode::Character;
3661 auto_scroll = true;
3662 }
3663 2 => {
3664 let position = display_map
3665 .clip_point(position, Bias::Left)
3666 .to_offset(&display_map, Bias::Left);
3667 let (range, _) = buffer.surrounding_word(position, None);
3668 start = buffer.anchor_before(range.start);
3669 end = buffer.anchor_before(range.end);
3670 mode = SelectMode::Word(start..end);
3671 auto_scroll = true;
3672 }
3673 3 => {
3674 let position = display_map
3675 .clip_point(position, Bias::Left)
3676 .to_point(&display_map);
3677 let line_start = display_map.prev_line_boundary(position).0;
3678 let next_line_start = buffer.clip_point(
3679 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3680 Bias::Left,
3681 );
3682 start = buffer.anchor_before(line_start);
3683 end = buffer.anchor_before(next_line_start);
3684 mode = SelectMode::Line(start..end);
3685 auto_scroll = true;
3686 }
3687 _ => {
3688 start = buffer.anchor_before(0);
3689 end = buffer.anchor_before(buffer.len());
3690 mode = SelectMode::All;
3691 auto_scroll = false;
3692 }
3693 }
3694 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3695
3696 let point_to_delete: Option<usize> = {
3697 let selected_points: Vec<Selection<Point>> =
3698 self.selections.disjoint_in_range(start..end, &display_map);
3699
3700 if !add || click_count > 1 {
3701 None
3702 } else if !selected_points.is_empty() {
3703 Some(selected_points[0].id)
3704 } else {
3705 let clicked_point_already_selected =
3706 self.selections.disjoint_anchors().iter().find(|selection| {
3707 selection.start.to_point(buffer) == start.to_point(buffer)
3708 || selection.end.to_point(buffer) == end.to_point(buffer)
3709 });
3710
3711 clicked_point_already_selected.map(|selection| selection.id)
3712 }
3713 };
3714
3715 let selections_count = self.selections.count();
3716 let effects = if auto_scroll {
3717 SelectionEffects::default()
3718 } else {
3719 SelectionEffects::no_scroll()
3720 };
3721
3722 self.change_selections(effects, window, cx, |s| {
3723 if let Some(point_to_delete) = point_to_delete {
3724 s.delete(point_to_delete);
3725
3726 if selections_count == 1 {
3727 s.set_pending_anchor_range(start..end, mode);
3728 }
3729 } else {
3730 if !add {
3731 s.clear_disjoint();
3732 }
3733
3734 s.set_pending_anchor_range(start..end, mode);
3735 }
3736 });
3737 }
3738
3739 fn begin_columnar_selection(
3740 &mut self,
3741 position: DisplayPoint,
3742 goal_column: u32,
3743 reset: bool,
3744 mode: ColumnarMode,
3745 window: &mut Window,
3746 cx: &mut Context<Self>,
3747 ) {
3748 if !self.focus_handle.is_focused(window) {
3749 self.last_focused_descendant = None;
3750 window.focus(&self.focus_handle);
3751 }
3752
3753 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3754
3755 if reset {
3756 let pointer_position = display_map
3757 .buffer_snapshot()
3758 .anchor_before(position.to_point(&display_map));
3759
3760 self.change_selections(
3761 SelectionEffects::scroll(Autoscroll::newest()),
3762 window,
3763 cx,
3764 |s| {
3765 s.clear_disjoint();
3766 s.set_pending_anchor_range(
3767 pointer_position..pointer_position,
3768 SelectMode::Character,
3769 );
3770 },
3771 );
3772 };
3773
3774 let tail = self.selections.newest::<Point>(&display_map).tail();
3775 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3776 self.columnar_selection_state = match mode {
3777 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3778 selection_tail: selection_anchor,
3779 display_point: if reset {
3780 if position.column() != goal_column {
3781 Some(DisplayPoint::new(position.row(), goal_column))
3782 } else {
3783 None
3784 }
3785 } else {
3786 None
3787 },
3788 }),
3789 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3790 selection_tail: selection_anchor,
3791 }),
3792 };
3793
3794 if !reset {
3795 self.select_columns(position, goal_column, &display_map, window, cx);
3796 }
3797 }
3798
3799 fn update_selection(
3800 &mut self,
3801 position: DisplayPoint,
3802 goal_column: u32,
3803 scroll_delta: gpui::Point<f32>,
3804 window: &mut Window,
3805 cx: &mut Context<Self>,
3806 ) {
3807 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3808
3809 if self.columnar_selection_state.is_some() {
3810 self.select_columns(position, goal_column, &display_map, window, cx);
3811 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3812 let buffer = display_map.buffer_snapshot();
3813 let head;
3814 let tail;
3815 let mode = self.selections.pending_mode().unwrap();
3816 match &mode {
3817 SelectMode::Character => {
3818 head = position.to_point(&display_map);
3819 tail = pending.tail().to_point(buffer);
3820 }
3821 SelectMode::Word(original_range) => {
3822 let offset = display_map
3823 .clip_point(position, Bias::Left)
3824 .to_offset(&display_map, Bias::Left);
3825 let original_range = original_range.to_offset(buffer);
3826
3827 let head_offset = if buffer.is_inside_word(offset, None)
3828 || original_range.contains(&offset)
3829 {
3830 let (word_range, _) = buffer.surrounding_word(offset, None);
3831 if word_range.start < original_range.start {
3832 word_range.start
3833 } else {
3834 word_range.end
3835 }
3836 } else {
3837 offset
3838 };
3839
3840 head = head_offset.to_point(buffer);
3841 if head_offset <= original_range.start {
3842 tail = original_range.end.to_point(buffer);
3843 } else {
3844 tail = original_range.start.to_point(buffer);
3845 }
3846 }
3847 SelectMode::Line(original_range) => {
3848 let original_range = original_range.to_point(display_map.buffer_snapshot());
3849
3850 let position = display_map
3851 .clip_point(position, Bias::Left)
3852 .to_point(&display_map);
3853 let line_start = display_map.prev_line_boundary(position).0;
3854 let next_line_start = buffer.clip_point(
3855 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3856 Bias::Left,
3857 );
3858
3859 if line_start < original_range.start {
3860 head = line_start
3861 } else {
3862 head = next_line_start
3863 }
3864
3865 if head <= original_range.start {
3866 tail = original_range.end;
3867 } else {
3868 tail = original_range.start;
3869 }
3870 }
3871 SelectMode::All => {
3872 return;
3873 }
3874 };
3875
3876 if head < tail {
3877 pending.start = buffer.anchor_before(head);
3878 pending.end = buffer.anchor_before(tail);
3879 pending.reversed = true;
3880 } else {
3881 pending.start = buffer.anchor_before(tail);
3882 pending.end = buffer.anchor_before(head);
3883 pending.reversed = false;
3884 }
3885
3886 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3887 s.set_pending(pending.clone(), mode);
3888 });
3889 } else {
3890 log::error!("update_selection dispatched with no pending selection");
3891 return;
3892 }
3893
3894 self.apply_scroll_delta(scroll_delta, window, cx);
3895 cx.notify();
3896 }
3897
3898 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3899 self.columnar_selection_state.take();
3900 if let Some(pending_mode) = self.selections.pending_mode() {
3901 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3902 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3903 s.select(selections);
3904 s.clear_pending();
3905 if s.is_extending() {
3906 s.set_is_extending(false);
3907 } else {
3908 s.set_select_mode(pending_mode);
3909 }
3910 });
3911 }
3912 }
3913
3914 fn select_columns(
3915 &mut self,
3916 head: DisplayPoint,
3917 goal_column: u32,
3918 display_map: &DisplaySnapshot,
3919 window: &mut Window,
3920 cx: &mut Context<Self>,
3921 ) {
3922 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3923 return;
3924 };
3925
3926 let tail = match columnar_state {
3927 ColumnarSelectionState::FromMouse {
3928 selection_tail,
3929 display_point,
3930 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3931 ColumnarSelectionState::FromSelection { selection_tail } => {
3932 selection_tail.to_display_point(display_map)
3933 }
3934 };
3935
3936 let start_row = cmp::min(tail.row(), head.row());
3937 let end_row = cmp::max(tail.row(), head.row());
3938 let start_column = cmp::min(tail.column(), goal_column);
3939 let end_column = cmp::max(tail.column(), goal_column);
3940 let reversed = start_column < tail.column();
3941
3942 let selection_ranges = (start_row.0..=end_row.0)
3943 .map(DisplayRow)
3944 .filter_map(|row| {
3945 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3946 || start_column <= display_map.line_len(row))
3947 && !display_map.is_block_line(row)
3948 {
3949 let start = display_map
3950 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3951 .to_point(display_map);
3952 let end = display_map
3953 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3954 .to_point(display_map);
3955 if reversed {
3956 Some(end..start)
3957 } else {
3958 Some(start..end)
3959 }
3960 } else {
3961 None
3962 }
3963 })
3964 .collect::<Vec<_>>();
3965 if selection_ranges.is_empty() {
3966 return;
3967 }
3968
3969 let ranges = match columnar_state {
3970 ColumnarSelectionState::FromMouse { .. } => {
3971 let mut non_empty_ranges = selection_ranges
3972 .iter()
3973 .filter(|selection_range| selection_range.start != selection_range.end)
3974 .peekable();
3975 if non_empty_ranges.peek().is_some() {
3976 non_empty_ranges.cloned().collect()
3977 } else {
3978 selection_ranges
3979 }
3980 }
3981 _ => selection_ranges,
3982 };
3983
3984 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3985 s.select_ranges(ranges);
3986 });
3987 cx.notify();
3988 }
3989
3990 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3991 self.selections
3992 .all_adjusted(snapshot)
3993 .iter()
3994 .any(|selection| !selection.is_empty())
3995 }
3996
3997 pub fn has_pending_nonempty_selection(&self) -> bool {
3998 let pending_nonempty_selection = match self.selections.pending_anchor() {
3999 Some(Selection { start, end, .. }) => start != end,
4000 None => false,
4001 };
4002
4003 pending_nonempty_selection
4004 || (self.columnar_selection_state.is_some()
4005 && self.selections.disjoint_anchors().len() > 1)
4006 }
4007
4008 pub fn has_pending_selection(&self) -> bool {
4009 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4010 }
4011
4012 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4013 self.selection_mark_mode = false;
4014 self.selection_drag_state = SelectionDragState::None;
4015
4016 if self.clear_expanded_diff_hunks(cx) {
4017 cx.notify();
4018 return;
4019 }
4020 if self.dismiss_menus_and_popups(true, window, cx) {
4021 return;
4022 }
4023
4024 if self.mode.is_full()
4025 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4026 {
4027 return;
4028 }
4029
4030 cx.propagate();
4031 }
4032
4033 pub fn dismiss_menus_and_popups(
4034 &mut self,
4035 is_user_requested: bool,
4036 window: &mut Window,
4037 cx: &mut Context<Self>,
4038 ) -> bool {
4039 if self.take_rename(false, window, cx).is_some() {
4040 return true;
4041 }
4042
4043 if self.hide_blame_popover(true, cx) {
4044 return true;
4045 }
4046
4047 if hide_hover(self, cx) {
4048 return true;
4049 }
4050
4051 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
4052 return true;
4053 }
4054
4055 if self.hide_context_menu(window, cx).is_some() {
4056 return true;
4057 }
4058
4059 if self.mouse_context_menu.take().is_some() {
4060 return true;
4061 }
4062
4063 if is_user_requested && self.discard_edit_prediction(true, cx) {
4064 return true;
4065 }
4066
4067 if self.snippet_stack.pop().is_some() {
4068 return true;
4069 }
4070
4071 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4072 self.dismiss_diagnostics(cx);
4073 return true;
4074 }
4075
4076 false
4077 }
4078
4079 fn linked_editing_ranges_for(
4080 &self,
4081 selection: Range<text::Anchor>,
4082 cx: &App,
4083 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4084 if self.linked_edit_ranges.is_empty() {
4085 return None;
4086 }
4087 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4088 selection.end.buffer_id.and_then(|end_buffer_id| {
4089 if selection.start.buffer_id != Some(end_buffer_id) {
4090 return None;
4091 }
4092 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4093 let snapshot = buffer.read(cx).snapshot();
4094 self.linked_edit_ranges
4095 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4096 .map(|ranges| (ranges, snapshot, buffer))
4097 })?;
4098 use text::ToOffset as TO;
4099 // find offset from the start of current range to current cursor position
4100 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4101
4102 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4103 let start_difference = start_offset - start_byte_offset;
4104 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4105 let end_difference = end_offset - start_byte_offset;
4106 // Current range has associated linked ranges.
4107 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4108 for range in linked_ranges.iter() {
4109 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4110 let end_offset = start_offset + end_difference;
4111 let start_offset = start_offset + start_difference;
4112 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4113 continue;
4114 }
4115 if self.selections.disjoint_anchor_ranges().any(|s| {
4116 if s.start.buffer_id != selection.start.buffer_id
4117 || s.end.buffer_id != selection.end.buffer_id
4118 {
4119 return false;
4120 }
4121 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4122 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4123 }) {
4124 continue;
4125 }
4126 let start = buffer_snapshot.anchor_after(start_offset);
4127 let end = buffer_snapshot.anchor_after(end_offset);
4128 linked_edits
4129 .entry(buffer.clone())
4130 .or_default()
4131 .push(start..end);
4132 }
4133 Some(linked_edits)
4134 }
4135
4136 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4137 let text: Arc<str> = text.into();
4138
4139 if self.read_only(cx) {
4140 return;
4141 }
4142
4143 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4144
4145 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4146 let mut bracket_inserted = false;
4147 let mut edits = Vec::new();
4148 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4149 let mut new_selections = Vec::with_capacity(selections.len());
4150 let mut new_autoclose_regions = Vec::new();
4151 let snapshot = self.buffer.read(cx).read(cx);
4152 let mut clear_linked_edit_ranges = false;
4153
4154 for (selection, autoclose_region) in
4155 self.selections_with_autoclose_regions(selections, &snapshot)
4156 {
4157 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4158 // Determine if the inserted text matches the opening or closing
4159 // bracket of any of this language's bracket pairs.
4160 let mut bracket_pair = None;
4161 let mut is_bracket_pair_start = false;
4162 let mut is_bracket_pair_end = false;
4163 if !text.is_empty() {
4164 let mut bracket_pair_matching_end = None;
4165 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4166 // and they are removing the character that triggered IME popup.
4167 for (pair, enabled) in scope.brackets() {
4168 if !pair.close && !pair.surround {
4169 continue;
4170 }
4171
4172 if enabled && pair.start.ends_with(text.as_ref()) {
4173 let prefix_len = pair.start.len() - text.len();
4174 let preceding_text_matches_prefix = prefix_len == 0
4175 || (selection.start.column >= (prefix_len as u32)
4176 && snapshot.contains_str_at(
4177 Point::new(
4178 selection.start.row,
4179 selection.start.column - (prefix_len as u32),
4180 ),
4181 &pair.start[..prefix_len],
4182 ));
4183 if preceding_text_matches_prefix {
4184 bracket_pair = Some(pair.clone());
4185 is_bracket_pair_start = true;
4186 break;
4187 }
4188 }
4189 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4190 {
4191 // take first bracket pair matching end, but don't break in case a later bracket
4192 // pair matches start
4193 bracket_pair_matching_end = Some(pair.clone());
4194 }
4195 }
4196 if let Some(end) = bracket_pair_matching_end
4197 && bracket_pair.is_none()
4198 {
4199 bracket_pair = Some(end);
4200 is_bracket_pair_end = true;
4201 }
4202 }
4203
4204 if let Some(bracket_pair) = bracket_pair {
4205 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4206 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4207 let auto_surround =
4208 self.use_auto_surround && snapshot_settings.use_auto_surround;
4209 if selection.is_empty() {
4210 if is_bracket_pair_start {
4211 // If the inserted text is a suffix of an opening bracket and the
4212 // selection is preceded by the rest of the opening bracket, then
4213 // insert the closing bracket.
4214 let following_text_allows_autoclose = snapshot
4215 .chars_at(selection.start)
4216 .next()
4217 .is_none_or(|c| scope.should_autoclose_before(c));
4218
4219 let preceding_text_allows_autoclose = selection.start.column == 0
4220 || snapshot
4221 .reversed_chars_at(selection.start)
4222 .next()
4223 .is_none_or(|c| {
4224 bracket_pair.start != bracket_pair.end
4225 || !snapshot
4226 .char_classifier_at(selection.start)
4227 .is_word(c)
4228 });
4229
4230 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4231 && bracket_pair.start.len() == 1
4232 {
4233 let target = bracket_pair.start.chars().next().unwrap();
4234 let current_line_count = snapshot
4235 .reversed_chars_at(selection.start)
4236 .take_while(|&c| c != '\n')
4237 .filter(|&c| c == target)
4238 .count();
4239 current_line_count % 2 == 1
4240 } else {
4241 false
4242 };
4243
4244 if autoclose
4245 && bracket_pair.close
4246 && following_text_allows_autoclose
4247 && preceding_text_allows_autoclose
4248 && !is_closing_quote
4249 {
4250 let anchor = snapshot.anchor_before(selection.end);
4251 new_selections.push((selection.map(|_| anchor), text.len()));
4252 new_autoclose_regions.push((
4253 anchor,
4254 text.len(),
4255 selection.id,
4256 bracket_pair.clone(),
4257 ));
4258 edits.push((
4259 selection.range(),
4260 format!("{}{}", text, bracket_pair.end).into(),
4261 ));
4262 bracket_inserted = true;
4263 continue;
4264 }
4265 }
4266
4267 if let Some(region) = autoclose_region {
4268 // If the selection is followed by an auto-inserted closing bracket,
4269 // then don't insert that closing bracket again; just move the selection
4270 // past the closing bracket.
4271 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4272 && text.as_ref() == region.pair.end.as_str()
4273 && snapshot.contains_str_at(region.range.end, text.as_ref());
4274 if should_skip {
4275 let anchor = snapshot.anchor_after(selection.end);
4276 new_selections
4277 .push((selection.map(|_| anchor), region.pair.end.len()));
4278 continue;
4279 }
4280 }
4281
4282 let always_treat_brackets_as_autoclosed = snapshot
4283 .language_settings_at(selection.start, cx)
4284 .always_treat_brackets_as_autoclosed;
4285 if always_treat_brackets_as_autoclosed
4286 && is_bracket_pair_end
4287 && snapshot.contains_str_at(selection.end, text.as_ref())
4288 {
4289 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4290 // and the inserted text is a closing bracket and the selection is followed
4291 // by the closing bracket then move the selection past the closing bracket.
4292 let anchor = snapshot.anchor_after(selection.end);
4293 new_selections.push((selection.map(|_| anchor), text.len()));
4294 continue;
4295 }
4296 }
4297 // If an opening bracket is 1 character long and is typed while
4298 // text is selected, then surround that text with the bracket pair.
4299 else if auto_surround
4300 && bracket_pair.surround
4301 && is_bracket_pair_start
4302 && bracket_pair.start.chars().count() == 1
4303 {
4304 edits.push((selection.start..selection.start, text.clone()));
4305 edits.push((
4306 selection.end..selection.end,
4307 bracket_pair.end.as_str().into(),
4308 ));
4309 bracket_inserted = true;
4310 new_selections.push((
4311 Selection {
4312 id: selection.id,
4313 start: snapshot.anchor_after(selection.start),
4314 end: snapshot.anchor_before(selection.end),
4315 reversed: selection.reversed,
4316 goal: selection.goal,
4317 },
4318 0,
4319 ));
4320 continue;
4321 }
4322 }
4323 }
4324
4325 if self.auto_replace_emoji_shortcode
4326 && selection.is_empty()
4327 && text.as_ref().ends_with(':')
4328 && let Some(possible_emoji_short_code) =
4329 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4330 && !possible_emoji_short_code.is_empty()
4331 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4332 {
4333 let emoji_shortcode_start = Point::new(
4334 selection.start.row,
4335 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4336 );
4337
4338 // Remove shortcode from buffer
4339 edits.push((
4340 emoji_shortcode_start..selection.start,
4341 "".to_string().into(),
4342 ));
4343 new_selections.push((
4344 Selection {
4345 id: selection.id,
4346 start: snapshot.anchor_after(emoji_shortcode_start),
4347 end: snapshot.anchor_before(selection.start),
4348 reversed: selection.reversed,
4349 goal: selection.goal,
4350 },
4351 0,
4352 ));
4353
4354 // Insert emoji
4355 let selection_start_anchor = snapshot.anchor_after(selection.start);
4356 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4357 edits.push((selection.start..selection.end, emoji.to_string().into()));
4358
4359 continue;
4360 }
4361
4362 // If not handling any auto-close operation, then just replace the selected
4363 // text with the given input and move the selection to the end of the
4364 // newly inserted text.
4365 let anchor = snapshot.anchor_after(selection.end);
4366 if !self.linked_edit_ranges.is_empty() {
4367 let start_anchor = snapshot.anchor_before(selection.start);
4368
4369 let is_word_char = text.chars().next().is_none_or(|char| {
4370 let classifier = snapshot
4371 .char_classifier_at(start_anchor.to_offset(&snapshot))
4372 .scope_context(Some(CharScopeContext::LinkedEdit));
4373 classifier.is_word(char)
4374 });
4375
4376 if is_word_char {
4377 if let Some(ranges) = self
4378 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4379 {
4380 for (buffer, edits) in ranges {
4381 linked_edits
4382 .entry(buffer.clone())
4383 .or_default()
4384 .extend(edits.into_iter().map(|range| (range, text.clone())));
4385 }
4386 }
4387 } else {
4388 clear_linked_edit_ranges = true;
4389 }
4390 }
4391
4392 new_selections.push((selection.map(|_| anchor), 0));
4393 edits.push((selection.start..selection.end, text.clone()));
4394 }
4395
4396 drop(snapshot);
4397
4398 self.transact(window, cx, |this, window, cx| {
4399 if clear_linked_edit_ranges {
4400 this.linked_edit_ranges.clear();
4401 }
4402 let initial_buffer_versions =
4403 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4404
4405 this.buffer.update(cx, |buffer, cx| {
4406 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4407 });
4408 for (buffer, edits) in linked_edits {
4409 buffer.update(cx, |buffer, cx| {
4410 let snapshot = buffer.snapshot();
4411 let edits = edits
4412 .into_iter()
4413 .map(|(range, text)| {
4414 use text::ToPoint as TP;
4415 let end_point = TP::to_point(&range.end, &snapshot);
4416 let start_point = TP::to_point(&range.start, &snapshot);
4417 (start_point..end_point, text)
4418 })
4419 .sorted_by_key(|(range, _)| range.start);
4420 buffer.edit(edits, None, cx);
4421 })
4422 }
4423 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4424 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4425 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4426 let new_selections =
4427 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4428 .zip(new_selection_deltas)
4429 .map(|(selection, delta)| Selection {
4430 id: selection.id,
4431 start: selection.start + delta,
4432 end: selection.end + delta,
4433 reversed: selection.reversed,
4434 goal: SelectionGoal::None,
4435 })
4436 .collect::<Vec<_>>();
4437
4438 let mut i = 0;
4439 for (position, delta, selection_id, pair) in new_autoclose_regions {
4440 let position = position.to_offset(map.buffer_snapshot()) + delta;
4441 let start = map.buffer_snapshot().anchor_before(position);
4442 let end = map.buffer_snapshot().anchor_after(position);
4443 while let Some(existing_state) = this.autoclose_regions.get(i) {
4444 match existing_state
4445 .range
4446 .start
4447 .cmp(&start, map.buffer_snapshot())
4448 {
4449 Ordering::Less => i += 1,
4450 Ordering::Greater => break,
4451 Ordering::Equal => {
4452 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4453 Ordering::Less => i += 1,
4454 Ordering::Equal => break,
4455 Ordering::Greater => break,
4456 }
4457 }
4458 }
4459 }
4460 this.autoclose_regions.insert(
4461 i,
4462 AutocloseRegion {
4463 selection_id,
4464 range: start..end,
4465 pair,
4466 },
4467 );
4468 }
4469
4470 let had_active_edit_prediction = this.has_active_edit_prediction();
4471 this.change_selections(
4472 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4473 window,
4474 cx,
4475 |s| s.select(new_selections),
4476 );
4477
4478 if !bracket_inserted
4479 && let Some(on_type_format_task) =
4480 this.trigger_on_type_formatting(text.to_string(), window, cx)
4481 {
4482 on_type_format_task.detach_and_log_err(cx);
4483 }
4484
4485 let editor_settings = EditorSettings::get_global(cx);
4486 if bracket_inserted
4487 && (editor_settings.auto_signature_help
4488 || editor_settings.show_signature_help_after_edits)
4489 {
4490 this.show_signature_help(&ShowSignatureHelp, window, cx);
4491 }
4492
4493 let trigger_in_words =
4494 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4495 if this.hard_wrap.is_some() {
4496 let latest: Range<Point> = this.selections.newest(&map).range();
4497 if latest.is_empty()
4498 && this
4499 .buffer()
4500 .read(cx)
4501 .snapshot(cx)
4502 .line_len(MultiBufferRow(latest.start.row))
4503 == latest.start.column
4504 {
4505 this.rewrap_impl(
4506 RewrapOptions {
4507 override_language_settings: true,
4508 preserve_existing_whitespace: true,
4509 },
4510 cx,
4511 )
4512 }
4513 }
4514 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4515 refresh_linked_ranges(this, window, cx);
4516 this.refresh_edit_prediction(true, false, window, cx);
4517 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4518 });
4519 }
4520
4521 fn find_possible_emoji_shortcode_at_position(
4522 snapshot: &MultiBufferSnapshot,
4523 position: Point,
4524 ) -> Option<String> {
4525 let mut chars = Vec::new();
4526 let mut found_colon = false;
4527 for char in snapshot.reversed_chars_at(position).take(100) {
4528 // Found a possible emoji shortcode in the middle of the buffer
4529 if found_colon {
4530 if char.is_whitespace() {
4531 chars.reverse();
4532 return Some(chars.iter().collect());
4533 }
4534 // If the previous character is not a whitespace, we are in the middle of a word
4535 // and we only want to complete the shortcode if the word is made up of other emojis
4536 let mut containing_word = String::new();
4537 for ch in snapshot
4538 .reversed_chars_at(position)
4539 .skip(chars.len() + 1)
4540 .take(100)
4541 {
4542 if ch.is_whitespace() {
4543 break;
4544 }
4545 containing_word.push(ch);
4546 }
4547 let containing_word = containing_word.chars().rev().collect::<String>();
4548 if util::word_consists_of_emojis(containing_word.as_str()) {
4549 chars.reverse();
4550 return Some(chars.iter().collect());
4551 }
4552 }
4553
4554 if char.is_whitespace() || !char.is_ascii() {
4555 return None;
4556 }
4557 if char == ':' {
4558 found_colon = true;
4559 } else {
4560 chars.push(char);
4561 }
4562 }
4563 // Found a possible emoji shortcode at the beginning of the buffer
4564 chars.reverse();
4565 Some(chars.iter().collect())
4566 }
4567
4568 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4569 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4570 self.transact(window, cx, |this, window, cx| {
4571 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4572 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4573 let multi_buffer = this.buffer.read(cx);
4574 let buffer = multi_buffer.snapshot(cx);
4575 selections
4576 .iter()
4577 .map(|selection| {
4578 let start_point = selection.start.to_point(&buffer);
4579 let mut existing_indent =
4580 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4581 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4582 let start = selection.start;
4583 let end = selection.end;
4584 let selection_is_empty = start == end;
4585 let language_scope = buffer.language_scope_at(start);
4586 let (
4587 comment_delimiter,
4588 doc_delimiter,
4589 insert_extra_newline,
4590 indent_on_newline,
4591 indent_on_extra_newline,
4592 ) = if let Some(language) = &language_scope {
4593 let mut insert_extra_newline =
4594 insert_extra_newline_brackets(&buffer, start..end, language)
4595 || insert_extra_newline_tree_sitter(&buffer, start..end);
4596
4597 // Comment extension on newline is allowed only for cursor selections
4598 let comment_delimiter = maybe!({
4599 if !selection_is_empty {
4600 return None;
4601 }
4602
4603 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4604 return None;
4605 }
4606
4607 let delimiters = language.line_comment_prefixes();
4608 let max_len_of_delimiter =
4609 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4610 let (snapshot, range) =
4611 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4612
4613 let num_of_whitespaces = snapshot
4614 .chars_for_range(range.clone())
4615 .take_while(|c| c.is_whitespace())
4616 .count();
4617 let comment_candidate = snapshot
4618 .chars_for_range(range.clone())
4619 .skip(num_of_whitespaces)
4620 .take(max_len_of_delimiter)
4621 .collect::<String>();
4622 let (delimiter, trimmed_len) = delimiters
4623 .iter()
4624 .filter_map(|delimiter| {
4625 let prefix = delimiter.trim_end();
4626 if comment_candidate.starts_with(prefix) {
4627 Some((delimiter, prefix.len()))
4628 } else {
4629 None
4630 }
4631 })
4632 .max_by_key(|(_, len)| *len)?;
4633
4634 if let Some(BlockCommentConfig {
4635 start: block_start, ..
4636 }) = language.block_comment()
4637 {
4638 let block_start_trimmed = block_start.trim_end();
4639 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4640 let line_content = snapshot
4641 .chars_for_range(range)
4642 .skip(num_of_whitespaces)
4643 .take(block_start_trimmed.len())
4644 .collect::<String>();
4645
4646 if line_content.starts_with(block_start_trimmed) {
4647 return None;
4648 }
4649 }
4650 }
4651
4652 let cursor_is_placed_after_comment_marker =
4653 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4654 if cursor_is_placed_after_comment_marker {
4655 Some(delimiter.clone())
4656 } else {
4657 None
4658 }
4659 });
4660
4661 let mut indent_on_newline = IndentSize::spaces(0);
4662 let mut indent_on_extra_newline = IndentSize::spaces(0);
4663
4664 let doc_delimiter = maybe!({
4665 if !selection_is_empty {
4666 return None;
4667 }
4668
4669 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4670 return None;
4671 }
4672
4673 let BlockCommentConfig {
4674 start: start_tag,
4675 end: end_tag,
4676 prefix: delimiter,
4677 tab_size: len,
4678 } = language.documentation_comment()?;
4679 let is_within_block_comment = buffer
4680 .language_scope_at(start_point)
4681 .is_some_and(|scope| scope.override_name() == Some("comment"));
4682 if !is_within_block_comment {
4683 return None;
4684 }
4685
4686 let (snapshot, range) =
4687 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4688
4689 let num_of_whitespaces = snapshot
4690 .chars_for_range(range.clone())
4691 .take_while(|c| c.is_whitespace())
4692 .count();
4693
4694 // 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.
4695 let column = start_point.column;
4696 let cursor_is_after_start_tag = {
4697 let start_tag_len = start_tag.len();
4698 let start_tag_line = snapshot
4699 .chars_for_range(range.clone())
4700 .skip(num_of_whitespaces)
4701 .take(start_tag_len)
4702 .collect::<String>();
4703 if start_tag_line.starts_with(start_tag.as_ref()) {
4704 num_of_whitespaces + start_tag_len <= column as usize
4705 } else {
4706 false
4707 }
4708 };
4709
4710 let cursor_is_after_delimiter = {
4711 let delimiter_trim = delimiter.trim_end();
4712 let delimiter_line = snapshot
4713 .chars_for_range(range.clone())
4714 .skip(num_of_whitespaces)
4715 .take(delimiter_trim.len())
4716 .collect::<String>();
4717 if delimiter_line.starts_with(delimiter_trim) {
4718 num_of_whitespaces + delimiter_trim.len() <= column as usize
4719 } else {
4720 false
4721 }
4722 };
4723
4724 let cursor_is_before_end_tag_if_exists = {
4725 let mut char_position = 0u32;
4726 let mut end_tag_offset = None;
4727
4728 'outer: for chunk in snapshot.text_for_range(range) {
4729 if let Some(byte_pos) = chunk.find(&**end_tag) {
4730 let chars_before_match =
4731 chunk[..byte_pos].chars().count() as u32;
4732 end_tag_offset =
4733 Some(char_position + chars_before_match);
4734 break 'outer;
4735 }
4736 char_position += chunk.chars().count() as u32;
4737 }
4738
4739 if let Some(end_tag_offset) = end_tag_offset {
4740 let cursor_is_before_end_tag = column <= end_tag_offset;
4741 if cursor_is_after_start_tag {
4742 if cursor_is_before_end_tag {
4743 insert_extra_newline = true;
4744 }
4745 let cursor_is_at_start_of_end_tag =
4746 column == end_tag_offset;
4747 if cursor_is_at_start_of_end_tag {
4748 indent_on_extra_newline.len = *len;
4749 }
4750 }
4751 cursor_is_before_end_tag
4752 } else {
4753 true
4754 }
4755 };
4756
4757 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4758 && cursor_is_before_end_tag_if_exists
4759 {
4760 if cursor_is_after_start_tag {
4761 indent_on_newline.len = *len;
4762 }
4763 Some(delimiter.clone())
4764 } else {
4765 None
4766 }
4767 });
4768
4769 (
4770 comment_delimiter,
4771 doc_delimiter,
4772 insert_extra_newline,
4773 indent_on_newline,
4774 indent_on_extra_newline,
4775 )
4776 } else {
4777 (
4778 None,
4779 None,
4780 false,
4781 IndentSize::default(),
4782 IndentSize::default(),
4783 )
4784 };
4785
4786 let prevent_auto_indent = doc_delimiter.is_some();
4787 let delimiter = comment_delimiter.or(doc_delimiter);
4788
4789 let capacity_for_delimiter =
4790 delimiter.as_deref().map(str::len).unwrap_or_default();
4791 let mut new_text = String::with_capacity(
4792 1 + capacity_for_delimiter
4793 + existing_indent.len as usize
4794 + indent_on_newline.len as usize
4795 + indent_on_extra_newline.len as usize,
4796 );
4797 new_text.push('\n');
4798 new_text.extend(existing_indent.chars());
4799 new_text.extend(indent_on_newline.chars());
4800
4801 if let Some(delimiter) = &delimiter {
4802 new_text.push_str(delimiter);
4803 }
4804
4805 if insert_extra_newline {
4806 new_text.push('\n');
4807 new_text.extend(existing_indent.chars());
4808 new_text.extend(indent_on_extra_newline.chars());
4809 }
4810
4811 let anchor = buffer.anchor_after(end);
4812 let new_selection = selection.map(|_| anchor);
4813 (
4814 ((start..end, new_text), prevent_auto_indent),
4815 (insert_extra_newline, new_selection),
4816 )
4817 })
4818 .unzip()
4819 };
4820
4821 let mut auto_indent_edits = Vec::new();
4822 let mut edits = Vec::new();
4823 for (edit, prevent_auto_indent) in edits_with_flags {
4824 if prevent_auto_indent {
4825 edits.push(edit);
4826 } else {
4827 auto_indent_edits.push(edit);
4828 }
4829 }
4830 if !edits.is_empty() {
4831 this.edit(edits, cx);
4832 }
4833 if !auto_indent_edits.is_empty() {
4834 this.edit_with_autoindent(auto_indent_edits, cx);
4835 }
4836
4837 let buffer = this.buffer.read(cx).snapshot(cx);
4838 let new_selections = selection_info
4839 .into_iter()
4840 .map(|(extra_newline_inserted, new_selection)| {
4841 let mut cursor = new_selection.end.to_point(&buffer);
4842 if extra_newline_inserted {
4843 cursor.row -= 1;
4844 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4845 }
4846 new_selection.map(|_| cursor)
4847 })
4848 .collect();
4849
4850 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4851 this.refresh_edit_prediction(true, false, window, cx);
4852 });
4853 }
4854
4855 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4856 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4857
4858 let buffer = self.buffer.read(cx);
4859 let snapshot = buffer.snapshot(cx);
4860
4861 let mut edits = Vec::new();
4862 let mut rows = Vec::new();
4863
4864 for (rows_inserted, selection) in self
4865 .selections
4866 .all_adjusted(&self.display_snapshot(cx))
4867 .into_iter()
4868 .enumerate()
4869 {
4870 let cursor = selection.head();
4871 let row = cursor.row;
4872
4873 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4874
4875 let newline = "\n".to_string();
4876 edits.push((start_of_line..start_of_line, newline));
4877
4878 rows.push(row + rows_inserted as u32);
4879 }
4880
4881 self.transact(window, cx, |editor, window, cx| {
4882 editor.edit(edits, cx);
4883
4884 editor.change_selections(Default::default(), window, cx, |s| {
4885 let mut index = 0;
4886 s.move_cursors_with(|map, _, _| {
4887 let row = rows[index];
4888 index += 1;
4889
4890 let point = Point::new(row, 0);
4891 let boundary = map.next_line_boundary(point).1;
4892 let clipped = map.clip_point(boundary, Bias::Left);
4893
4894 (clipped, SelectionGoal::None)
4895 });
4896 });
4897
4898 let mut indent_edits = Vec::new();
4899 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4900 for row in rows {
4901 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4902 for (row, indent) in indents {
4903 if indent.len == 0 {
4904 continue;
4905 }
4906
4907 let text = match indent.kind {
4908 IndentKind::Space => " ".repeat(indent.len as usize),
4909 IndentKind::Tab => "\t".repeat(indent.len as usize),
4910 };
4911 let point = Point::new(row.0, 0);
4912 indent_edits.push((point..point, text));
4913 }
4914 }
4915 editor.edit(indent_edits, cx);
4916 });
4917 }
4918
4919 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4920 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4921
4922 let buffer = self.buffer.read(cx);
4923 let snapshot = buffer.snapshot(cx);
4924
4925 let mut edits = Vec::new();
4926 let mut rows = Vec::new();
4927 let mut rows_inserted = 0;
4928
4929 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4930 let cursor = selection.head();
4931 let row = cursor.row;
4932
4933 let point = Point::new(row + 1, 0);
4934 let start_of_line = snapshot.clip_point(point, Bias::Left);
4935
4936 let newline = "\n".to_string();
4937 edits.push((start_of_line..start_of_line, newline));
4938
4939 rows_inserted += 1;
4940 rows.push(row + rows_inserted);
4941 }
4942
4943 self.transact(window, cx, |editor, window, cx| {
4944 editor.edit(edits, cx);
4945
4946 editor.change_selections(Default::default(), window, cx, |s| {
4947 let mut index = 0;
4948 s.move_cursors_with(|map, _, _| {
4949 let row = rows[index];
4950 index += 1;
4951
4952 let point = Point::new(row, 0);
4953 let boundary = map.next_line_boundary(point).1;
4954 let clipped = map.clip_point(boundary, Bias::Left);
4955
4956 (clipped, SelectionGoal::None)
4957 });
4958 });
4959
4960 let mut indent_edits = Vec::new();
4961 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4962 for row in rows {
4963 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4964 for (row, indent) in indents {
4965 if indent.len == 0 {
4966 continue;
4967 }
4968
4969 let text = match indent.kind {
4970 IndentKind::Space => " ".repeat(indent.len as usize),
4971 IndentKind::Tab => "\t".repeat(indent.len as usize),
4972 };
4973 let point = Point::new(row.0, 0);
4974 indent_edits.push((point..point, text));
4975 }
4976 }
4977 editor.edit(indent_edits, cx);
4978 });
4979 }
4980
4981 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4982 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4983 original_indent_columns: Vec::new(),
4984 });
4985 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4986 }
4987
4988 fn insert_with_autoindent_mode(
4989 &mut self,
4990 text: &str,
4991 autoindent_mode: Option<AutoindentMode>,
4992 window: &mut Window,
4993 cx: &mut Context<Self>,
4994 ) {
4995 if self.read_only(cx) {
4996 return;
4997 }
4998
4999 let text: Arc<str> = text.into();
5000 self.transact(window, cx, |this, window, cx| {
5001 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5002 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5003 let anchors = {
5004 let snapshot = buffer.read(cx);
5005 old_selections
5006 .iter()
5007 .map(|s| {
5008 let anchor = snapshot.anchor_after(s.head());
5009 s.map(|_| anchor)
5010 })
5011 .collect::<Vec<_>>()
5012 };
5013 buffer.edit(
5014 old_selections
5015 .iter()
5016 .map(|s| (s.start..s.end, text.clone())),
5017 autoindent_mode,
5018 cx,
5019 );
5020 anchors
5021 });
5022
5023 this.change_selections(Default::default(), window, cx, |s| {
5024 s.select_anchors(selection_anchors);
5025 });
5026
5027 cx.notify();
5028 });
5029 }
5030
5031 fn trigger_completion_on_input(
5032 &mut self,
5033 text: &str,
5034 trigger_in_words: bool,
5035 window: &mut Window,
5036 cx: &mut Context<Self>,
5037 ) {
5038 let completions_source = self
5039 .context_menu
5040 .borrow()
5041 .as_ref()
5042 .and_then(|menu| match menu {
5043 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5044 CodeContextMenu::CodeActions(_) => None,
5045 });
5046
5047 match completions_source {
5048 Some(CompletionsMenuSource::Words { .. }) => {
5049 self.open_or_update_completions_menu(
5050 Some(CompletionsMenuSource::Words {
5051 ignore_threshold: false,
5052 }),
5053 None,
5054 window,
5055 cx,
5056 );
5057 }
5058 Some(CompletionsMenuSource::Normal)
5059 | Some(CompletionsMenuSource::SnippetChoices)
5060 | None
5061 if self.is_completion_trigger(
5062 text,
5063 trigger_in_words,
5064 completions_source.is_some(),
5065 cx,
5066 ) =>
5067 {
5068 self.show_completions(
5069 &ShowCompletions {
5070 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
5071 },
5072 window,
5073 cx,
5074 )
5075 }
5076 _ => {
5077 self.hide_context_menu(window, cx);
5078 }
5079 }
5080 }
5081
5082 fn is_completion_trigger(
5083 &self,
5084 text: &str,
5085 trigger_in_words: bool,
5086 menu_is_open: bool,
5087 cx: &mut Context<Self>,
5088 ) -> bool {
5089 let position = self.selections.newest_anchor().head();
5090 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
5091 return false;
5092 };
5093
5094 if let Some(completion_provider) = &self.completion_provider {
5095 completion_provider.is_completion_trigger(
5096 &buffer,
5097 position.text_anchor,
5098 text,
5099 trigger_in_words,
5100 menu_is_open,
5101 cx,
5102 )
5103 } else {
5104 false
5105 }
5106 }
5107
5108 /// If any empty selections is touching the start of its innermost containing autoclose
5109 /// region, expand it to select the brackets.
5110 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5111 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5112 let buffer = self.buffer.read(cx).read(cx);
5113 let new_selections = self
5114 .selections_with_autoclose_regions(selections, &buffer)
5115 .map(|(mut selection, region)| {
5116 if !selection.is_empty() {
5117 return selection;
5118 }
5119
5120 if let Some(region) = region {
5121 let mut range = region.range.to_offset(&buffer);
5122 if selection.start == range.start && range.start >= region.pair.start.len() {
5123 range.start -= region.pair.start.len();
5124 if buffer.contains_str_at(range.start, ®ion.pair.start)
5125 && buffer.contains_str_at(range.end, ®ion.pair.end)
5126 {
5127 range.end += region.pair.end.len();
5128 selection.start = range.start;
5129 selection.end = range.end;
5130
5131 return selection;
5132 }
5133 }
5134 }
5135
5136 let always_treat_brackets_as_autoclosed = buffer
5137 .language_settings_at(selection.start, cx)
5138 .always_treat_brackets_as_autoclosed;
5139
5140 if !always_treat_brackets_as_autoclosed {
5141 return selection;
5142 }
5143
5144 if let Some(scope) = buffer.language_scope_at(selection.start) {
5145 for (pair, enabled) in scope.brackets() {
5146 if !enabled || !pair.close {
5147 continue;
5148 }
5149
5150 if buffer.contains_str_at(selection.start, &pair.end) {
5151 let pair_start_len = pair.start.len();
5152 if buffer.contains_str_at(
5153 selection.start.saturating_sub(pair_start_len),
5154 &pair.start,
5155 ) {
5156 selection.start -= pair_start_len;
5157 selection.end += pair.end.len();
5158
5159 return selection;
5160 }
5161 }
5162 }
5163 }
5164
5165 selection
5166 })
5167 .collect();
5168
5169 drop(buffer);
5170 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5171 selections.select(new_selections)
5172 });
5173 }
5174
5175 /// Iterate the given selections, and for each one, find the smallest surrounding
5176 /// autoclose region. This uses the ordering of the selections and the autoclose
5177 /// regions to avoid repeated comparisons.
5178 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5179 &'a self,
5180 selections: impl IntoIterator<Item = Selection<D>>,
5181 buffer: &'a MultiBufferSnapshot,
5182 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5183 let mut i = 0;
5184 let mut regions = self.autoclose_regions.as_slice();
5185 selections.into_iter().map(move |selection| {
5186 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5187
5188 let mut enclosing = None;
5189 while let Some(pair_state) = regions.get(i) {
5190 if pair_state.range.end.to_offset(buffer) < range.start {
5191 regions = ®ions[i + 1..];
5192 i = 0;
5193 } else if pair_state.range.start.to_offset(buffer) > range.end {
5194 break;
5195 } else {
5196 if pair_state.selection_id == selection.id {
5197 enclosing = Some(pair_state);
5198 }
5199 i += 1;
5200 }
5201 }
5202
5203 (selection, enclosing)
5204 })
5205 }
5206
5207 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5208 fn invalidate_autoclose_regions(
5209 &mut self,
5210 mut selections: &[Selection<Anchor>],
5211 buffer: &MultiBufferSnapshot,
5212 ) {
5213 self.autoclose_regions.retain(|state| {
5214 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5215 return false;
5216 }
5217
5218 let mut i = 0;
5219 while let Some(selection) = selections.get(i) {
5220 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5221 selections = &selections[1..];
5222 continue;
5223 }
5224 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5225 break;
5226 }
5227 if selection.id == state.selection_id {
5228 return true;
5229 } else {
5230 i += 1;
5231 }
5232 }
5233 false
5234 });
5235 }
5236
5237 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5238 let offset = position.to_offset(buffer);
5239 let (word_range, kind) =
5240 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5241 if offset > word_range.start && kind == Some(CharKind::Word) {
5242 Some(
5243 buffer
5244 .text_for_range(word_range.start..offset)
5245 .collect::<String>(),
5246 )
5247 } else {
5248 None
5249 }
5250 }
5251
5252 pub fn visible_excerpts(
5253 &self,
5254 cx: &mut Context<Editor>,
5255 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5256 let Some(project) = self.project() else {
5257 return HashMap::default();
5258 };
5259 let project = project.read(cx);
5260 let multi_buffer = self.buffer().read(cx);
5261 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5262 let multi_buffer_visible_start = self
5263 .scroll_manager
5264 .anchor()
5265 .anchor
5266 .to_point(&multi_buffer_snapshot);
5267 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5268 multi_buffer_visible_start
5269 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5270 Bias::Left,
5271 );
5272 multi_buffer_snapshot
5273 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5274 .into_iter()
5275 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5276 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5277 let buffer_file = project::File::from_dyn(buffer.file())?;
5278 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5279 let worktree_entry = buffer_worktree
5280 .read(cx)
5281 .entry_for_id(buffer_file.project_entry_id()?)?;
5282 if worktree_entry.is_ignored {
5283 None
5284 } else {
5285 Some((
5286 excerpt_id,
5287 (
5288 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5289 buffer.version().clone(),
5290 excerpt_visible_range,
5291 ),
5292 ))
5293 }
5294 })
5295 .collect()
5296 }
5297
5298 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5299 TextLayoutDetails {
5300 text_system: window.text_system().clone(),
5301 editor_style: self.style.clone().unwrap(),
5302 rem_size: window.rem_size(),
5303 scroll_anchor: self.scroll_manager.anchor(),
5304 visible_rows: self.visible_line_count(),
5305 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5306 }
5307 }
5308
5309 fn trigger_on_type_formatting(
5310 &self,
5311 input: String,
5312 window: &mut Window,
5313 cx: &mut Context<Self>,
5314 ) -> Option<Task<Result<()>>> {
5315 if input.len() != 1 {
5316 return None;
5317 }
5318
5319 let project = self.project()?;
5320 let position = self.selections.newest_anchor().head();
5321 let (buffer, buffer_position) = self
5322 .buffer
5323 .read(cx)
5324 .text_anchor_for_position(position, cx)?;
5325
5326 let settings = language_settings::language_settings(
5327 buffer
5328 .read(cx)
5329 .language_at(buffer_position)
5330 .map(|l| l.name()),
5331 buffer.read(cx).file(),
5332 cx,
5333 );
5334 if !settings.use_on_type_format {
5335 return None;
5336 }
5337
5338 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5339 // hence we do LSP request & edit on host side only — add formats to host's history.
5340 let push_to_lsp_host_history = true;
5341 // If this is not the host, append its history with new edits.
5342 let push_to_client_history = project.read(cx).is_via_collab();
5343
5344 let on_type_formatting = project.update(cx, |project, cx| {
5345 project.on_type_format(
5346 buffer.clone(),
5347 buffer_position,
5348 input,
5349 push_to_lsp_host_history,
5350 cx,
5351 )
5352 });
5353 Some(cx.spawn_in(window, async move |editor, cx| {
5354 if let Some(transaction) = on_type_formatting.await? {
5355 if push_to_client_history {
5356 buffer
5357 .update(cx, |buffer, _| {
5358 buffer.push_transaction(transaction, Instant::now());
5359 buffer.finalize_last_transaction();
5360 })
5361 .ok();
5362 }
5363 editor.update(cx, |editor, cx| {
5364 editor.refresh_document_highlights(cx);
5365 })?;
5366 }
5367 Ok(())
5368 }))
5369 }
5370
5371 pub fn show_word_completions(
5372 &mut self,
5373 _: &ShowWordCompletions,
5374 window: &mut Window,
5375 cx: &mut Context<Self>,
5376 ) {
5377 self.open_or_update_completions_menu(
5378 Some(CompletionsMenuSource::Words {
5379 ignore_threshold: true,
5380 }),
5381 None,
5382 window,
5383 cx,
5384 );
5385 }
5386
5387 pub fn show_completions(
5388 &mut self,
5389 options: &ShowCompletions,
5390 window: &mut Window,
5391 cx: &mut Context<Self>,
5392 ) {
5393 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5394 }
5395
5396 fn open_or_update_completions_menu(
5397 &mut self,
5398 requested_source: Option<CompletionsMenuSource>,
5399 trigger: Option<&str>,
5400 window: &mut Window,
5401 cx: &mut Context<Self>,
5402 ) {
5403 if self.pending_rename.is_some() {
5404 return;
5405 }
5406
5407 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5408
5409 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5410 // inserted and selected. To handle that case, the start of the selection is used so that
5411 // the menu starts with all choices.
5412 let position = self
5413 .selections
5414 .newest_anchor()
5415 .start
5416 .bias_right(&multibuffer_snapshot);
5417 if position.diff_base_anchor.is_some() {
5418 return;
5419 }
5420 let buffer_position = multibuffer_snapshot.anchor_before(position);
5421 let Some(buffer) = buffer_position
5422 .buffer_id
5423 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5424 else {
5425 return;
5426 };
5427 let buffer_snapshot = buffer.read(cx).snapshot();
5428
5429 let query: Option<Arc<String>> =
5430 Self::completion_query(&multibuffer_snapshot, buffer_position)
5431 .map(|query| query.into());
5432
5433 drop(multibuffer_snapshot);
5434
5435 // Hide the current completions menu when query is empty. Without this, cached
5436 // completions from before the trigger char may be reused (#32774).
5437 if query.is_none() {
5438 let menu_is_open = matches!(
5439 self.context_menu.borrow().as_ref(),
5440 Some(CodeContextMenu::Completions(_))
5441 );
5442 if menu_is_open {
5443 self.hide_context_menu(window, cx);
5444 }
5445 }
5446
5447 let mut ignore_word_threshold = false;
5448 let provider = match requested_source {
5449 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5450 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5451 ignore_word_threshold = ignore_threshold;
5452 None
5453 }
5454 Some(CompletionsMenuSource::SnippetChoices) => {
5455 log::error!("bug: SnippetChoices requested_source is not handled");
5456 None
5457 }
5458 };
5459
5460 let sort_completions = provider
5461 .as_ref()
5462 .is_some_and(|provider| provider.sort_completions());
5463
5464 let filter_completions = provider
5465 .as_ref()
5466 .is_none_or(|provider| provider.filter_completions());
5467
5468 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5469 if filter_completions {
5470 menu.filter(query.clone(), provider.clone(), window, cx);
5471 }
5472 // When `is_incomplete` is false, no need to re-query completions when the current query
5473 // is a suffix of the initial query.
5474 if !menu.is_incomplete {
5475 // If the new query is a suffix of the old query (typing more characters) and
5476 // the previous result was complete, the existing completions can be filtered.
5477 //
5478 // Note that this is always true for snippet completions.
5479 let query_matches = match (&menu.initial_query, &query) {
5480 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5481 (None, _) => true,
5482 _ => false,
5483 };
5484 if query_matches {
5485 let position_matches = if menu.initial_position == position {
5486 true
5487 } else {
5488 let snapshot = self.buffer.read(cx).read(cx);
5489 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5490 };
5491 if position_matches {
5492 return;
5493 }
5494 }
5495 }
5496 };
5497
5498 let trigger_kind = match trigger {
5499 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5500 CompletionTriggerKind::TRIGGER_CHARACTER
5501 }
5502 _ => CompletionTriggerKind::INVOKED,
5503 };
5504 let completion_context = CompletionContext {
5505 trigger_character: trigger.and_then(|trigger| {
5506 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5507 Some(String::from(trigger))
5508 } else {
5509 None
5510 }
5511 }),
5512 trigger_kind,
5513 };
5514
5515 let Anchor {
5516 excerpt_id: buffer_excerpt_id,
5517 text_anchor: buffer_position,
5518 ..
5519 } = buffer_position;
5520
5521 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5522 buffer_snapshot.surrounding_word(buffer_position, None)
5523 {
5524 let word_to_exclude = buffer_snapshot
5525 .text_for_range(word_range.clone())
5526 .collect::<String>();
5527 (
5528 buffer_snapshot.anchor_before(word_range.start)
5529 ..buffer_snapshot.anchor_after(buffer_position),
5530 Some(word_to_exclude),
5531 )
5532 } else {
5533 (buffer_position..buffer_position, None)
5534 };
5535
5536 let language = buffer_snapshot
5537 .language_at(buffer_position)
5538 .map(|language| language.name());
5539
5540 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5541 .completions
5542 .clone();
5543
5544 let show_completion_documentation = buffer_snapshot
5545 .settings_at(buffer_position, cx)
5546 .show_completion_documentation;
5547
5548 // The document can be large, so stay in reasonable bounds when searching for words,
5549 // otherwise completion pop-up might be slow to appear.
5550 const WORD_LOOKUP_ROWS: u32 = 5_000;
5551 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5552 let min_word_search = buffer_snapshot.clip_point(
5553 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5554 Bias::Left,
5555 );
5556 let max_word_search = buffer_snapshot.clip_point(
5557 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5558 Bias::Right,
5559 );
5560 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5561 ..buffer_snapshot.point_to_offset(max_word_search);
5562
5563 let skip_digits = query
5564 .as_ref()
5565 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5566
5567 let omit_word_completions = !self.word_completions_enabled
5568 || (!ignore_word_threshold
5569 && match &query {
5570 Some(query) => query.chars().count() < completion_settings.words_min_length,
5571 None => completion_settings.words_min_length != 0,
5572 });
5573
5574 let (mut words, provider_responses) = match &provider {
5575 Some(provider) => {
5576 let provider_responses = provider.completions(
5577 buffer_excerpt_id,
5578 &buffer,
5579 buffer_position,
5580 completion_context,
5581 window,
5582 cx,
5583 );
5584
5585 let words = match (omit_word_completions, completion_settings.words) {
5586 (true, _) | (_, WordsCompletionMode::Disabled) => {
5587 Task::ready(BTreeMap::default())
5588 }
5589 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5590 .background_spawn(async move {
5591 buffer_snapshot.words_in_range(WordsQuery {
5592 fuzzy_contents: None,
5593 range: word_search_range,
5594 skip_digits,
5595 })
5596 }),
5597 };
5598
5599 (words, provider_responses)
5600 }
5601 None => {
5602 let words = if omit_word_completions {
5603 Task::ready(BTreeMap::default())
5604 } else {
5605 cx.background_spawn(async move {
5606 buffer_snapshot.words_in_range(WordsQuery {
5607 fuzzy_contents: None,
5608 range: word_search_range,
5609 skip_digits,
5610 })
5611 })
5612 };
5613 (words, Task::ready(Ok(Vec::new())))
5614 }
5615 };
5616
5617 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5618
5619 let id = post_inc(&mut self.next_completion_id);
5620 let task = cx.spawn_in(window, async move |editor, cx| {
5621 let Ok(()) = editor.update(cx, |this, _| {
5622 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5623 }) else {
5624 return;
5625 };
5626
5627 // TODO: Ideally completions from different sources would be selectively re-queried, so
5628 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5629 let mut completions = Vec::new();
5630 let mut is_incomplete = false;
5631 let mut display_options: Option<CompletionDisplayOptions> = None;
5632 if let Some(provider_responses) = provider_responses.await.log_err()
5633 && !provider_responses.is_empty()
5634 {
5635 for response in provider_responses {
5636 completions.extend(response.completions);
5637 is_incomplete = is_incomplete || response.is_incomplete;
5638 match display_options.as_mut() {
5639 None => {
5640 display_options = Some(response.display_options);
5641 }
5642 Some(options) => options.merge(&response.display_options),
5643 }
5644 }
5645 if completion_settings.words == WordsCompletionMode::Fallback {
5646 words = Task::ready(BTreeMap::default());
5647 }
5648 }
5649 let display_options = display_options.unwrap_or_default();
5650
5651 let mut words = words.await;
5652 if let Some(word_to_exclude) = &word_to_exclude {
5653 words.remove(word_to_exclude);
5654 }
5655 for lsp_completion in &completions {
5656 words.remove(&lsp_completion.new_text);
5657 }
5658 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5659 replace_range: word_replace_range.clone(),
5660 new_text: word.clone(),
5661 label: CodeLabel::plain(word, None),
5662 icon_path: None,
5663 documentation: None,
5664 source: CompletionSource::BufferWord {
5665 word_range,
5666 resolved: false,
5667 },
5668 insert_text_mode: Some(InsertTextMode::AS_IS),
5669 confirm: None,
5670 }));
5671
5672 let menu = if completions.is_empty() {
5673 None
5674 } else {
5675 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5676 let languages = editor
5677 .workspace
5678 .as_ref()
5679 .and_then(|(workspace, _)| workspace.upgrade())
5680 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5681 let menu = CompletionsMenu::new(
5682 id,
5683 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5684 sort_completions,
5685 show_completion_documentation,
5686 position,
5687 query.clone(),
5688 is_incomplete,
5689 buffer.clone(),
5690 completions.into(),
5691 display_options,
5692 snippet_sort_order,
5693 languages,
5694 language,
5695 cx,
5696 );
5697
5698 let query = if filter_completions { query } else { None };
5699 let matches_task = if let Some(query) = query {
5700 menu.do_async_filtering(query, cx)
5701 } else {
5702 Task::ready(menu.unfiltered_matches())
5703 };
5704 (menu, matches_task)
5705 }) else {
5706 return;
5707 };
5708
5709 let matches = matches_task.await;
5710
5711 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5712 // Newer menu already set, so exit.
5713 if let Some(CodeContextMenu::Completions(prev_menu)) =
5714 editor.context_menu.borrow().as_ref()
5715 && prev_menu.id > id
5716 {
5717 return;
5718 };
5719
5720 // Only valid to take prev_menu because it the new menu is immediately set
5721 // below, or the menu is hidden.
5722 if let Some(CodeContextMenu::Completions(prev_menu)) =
5723 editor.context_menu.borrow_mut().take()
5724 {
5725 let position_matches =
5726 if prev_menu.initial_position == menu.initial_position {
5727 true
5728 } else {
5729 let snapshot = editor.buffer.read(cx).read(cx);
5730 prev_menu.initial_position.to_offset(&snapshot)
5731 == menu.initial_position.to_offset(&snapshot)
5732 };
5733 if position_matches {
5734 // Preserve markdown cache before `set_filter_results` because it will
5735 // try to populate the documentation cache.
5736 menu.preserve_markdown_cache(prev_menu);
5737 }
5738 };
5739
5740 menu.set_filter_results(matches, provider, window, cx);
5741 }) else {
5742 return;
5743 };
5744
5745 menu.visible().then_some(menu)
5746 };
5747
5748 editor
5749 .update_in(cx, |editor, window, cx| {
5750 if editor.focus_handle.is_focused(window)
5751 && let Some(menu) = menu
5752 {
5753 *editor.context_menu.borrow_mut() =
5754 Some(CodeContextMenu::Completions(menu));
5755
5756 crate::hover_popover::hide_hover(editor, cx);
5757 if editor.show_edit_predictions_in_menu() {
5758 editor.update_visible_edit_prediction(window, cx);
5759 } else {
5760 editor.discard_edit_prediction(false, cx);
5761 }
5762
5763 cx.notify();
5764 return;
5765 }
5766
5767 if editor.completion_tasks.len() <= 1 {
5768 // If there are no more completion tasks and the last menu was empty, we should hide it.
5769 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5770 // If it was already hidden and we don't show edit predictions in the menu,
5771 // we should also show the edit prediction when available.
5772 if was_hidden && editor.show_edit_predictions_in_menu() {
5773 editor.update_visible_edit_prediction(window, cx);
5774 }
5775 }
5776 })
5777 .ok();
5778 });
5779
5780 self.completion_tasks.push((id, task));
5781 }
5782
5783 #[cfg(feature = "test-support")]
5784 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5785 let menu = self.context_menu.borrow();
5786 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5787 let completions = menu.completions.borrow();
5788 Some(completions.to_vec())
5789 } else {
5790 None
5791 }
5792 }
5793
5794 pub fn with_completions_menu_matching_id<R>(
5795 &self,
5796 id: CompletionId,
5797 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5798 ) -> R {
5799 let mut context_menu = self.context_menu.borrow_mut();
5800 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5801 return f(None);
5802 };
5803 if completions_menu.id != id {
5804 return f(None);
5805 }
5806 f(Some(completions_menu))
5807 }
5808
5809 pub fn confirm_completion(
5810 &mut self,
5811 action: &ConfirmCompletion,
5812 window: &mut Window,
5813 cx: &mut Context<Self>,
5814 ) -> Option<Task<Result<()>>> {
5815 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5816 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5817 }
5818
5819 pub fn confirm_completion_insert(
5820 &mut self,
5821 _: &ConfirmCompletionInsert,
5822 window: &mut Window,
5823 cx: &mut Context<Self>,
5824 ) -> Option<Task<Result<()>>> {
5825 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5826 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5827 }
5828
5829 pub fn confirm_completion_replace(
5830 &mut self,
5831 _: &ConfirmCompletionReplace,
5832 window: &mut Window,
5833 cx: &mut Context<Self>,
5834 ) -> Option<Task<Result<()>>> {
5835 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5836 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5837 }
5838
5839 pub fn compose_completion(
5840 &mut self,
5841 action: &ComposeCompletion,
5842 window: &mut Window,
5843 cx: &mut Context<Self>,
5844 ) -> Option<Task<Result<()>>> {
5845 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5846 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5847 }
5848
5849 fn do_completion(
5850 &mut self,
5851 item_ix: Option<usize>,
5852 intent: CompletionIntent,
5853 window: &mut Window,
5854 cx: &mut Context<Editor>,
5855 ) -> Option<Task<Result<()>>> {
5856 use language::ToOffset as _;
5857
5858 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5859 else {
5860 return None;
5861 };
5862
5863 let candidate_id = {
5864 let entries = completions_menu.entries.borrow();
5865 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5866 if self.show_edit_predictions_in_menu() {
5867 self.discard_edit_prediction(true, cx);
5868 }
5869 mat.candidate_id
5870 };
5871
5872 let completion = completions_menu
5873 .completions
5874 .borrow()
5875 .get(candidate_id)?
5876 .clone();
5877 cx.stop_propagation();
5878
5879 let buffer_handle = completions_menu.buffer.clone();
5880
5881 let CompletionEdit {
5882 new_text,
5883 snippet,
5884 replace_range,
5885 } = process_completion_for_edit(
5886 &completion,
5887 intent,
5888 &buffer_handle,
5889 &completions_menu.initial_position.text_anchor,
5890 cx,
5891 );
5892
5893 let buffer = buffer_handle.read(cx);
5894 let snapshot = self.buffer.read(cx).snapshot(cx);
5895 let newest_anchor = self.selections.newest_anchor();
5896 let replace_range_multibuffer = {
5897 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5898 excerpt.map_range_from_buffer(replace_range.clone())
5899 };
5900 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5901 return None;
5902 }
5903
5904 let old_text = buffer
5905 .text_for_range(replace_range.clone())
5906 .collect::<String>();
5907 let lookbehind = newest_anchor
5908 .start
5909 .text_anchor
5910 .to_offset(buffer)
5911 .saturating_sub(replace_range.start);
5912 let lookahead = replace_range
5913 .end
5914 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5915 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5916 let suffix = &old_text[lookbehind.min(old_text.len())..];
5917
5918 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5919 let mut ranges = Vec::new();
5920 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5921
5922 for selection in &selections {
5923 let range = if selection.id == newest_anchor.id {
5924 replace_range_multibuffer.clone()
5925 } else {
5926 let mut range = selection.range();
5927
5928 // if prefix is present, don't duplicate it
5929 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5930 range.start = range.start.saturating_sub(lookbehind);
5931
5932 // if suffix is also present, mimic the newest cursor and replace it
5933 if selection.id != newest_anchor.id
5934 && snapshot.contains_str_at(range.end, suffix)
5935 {
5936 range.end += lookahead;
5937 }
5938 }
5939 range
5940 };
5941
5942 ranges.push(range.clone());
5943
5944 if !self.linked_edit_ranges.is_empty() {
5945 let start_anchor = snapshot.anchor_before(range.start);
5946 let end_anchor = snapshot.anchor_after(range.end);
5947 if let Some(ranges) = self
5948 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5949 {
5950 for (buffer, edits) in ranges {
5951 linked_edits
5952 .entry(buffer.clone())
5953 .or_default()
5954 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5955 }
5956 }
5957 }
5958 }
5959
5960 let common_prefix_len = old_text
5961 .chars()
5962 .zip(new_text.chars())
5963 .take_while(|(a, b)| a == b)
5964 .map(|(a, _)| a.len_utf8())
5965 .sum::<usize>();
5966
5967 cx.emit(EditorEvent::InputHandled {
5968 utf16_range_to_replace: None,
5969 text: new_text[common_prefix_len..].into(),
5970 });
5971
5972 self.transact(window, cx, |editor, window, cx| {
5973 if let Some(mut snippet) = snippet {
5974 snippet.text = new_text.to_string();
5975 editor
5976 .insert_snippet(&ranges, snippet, window, cx)
5977 .log_err();
5978 } else {
5979 editor.buffer.update(cx, |multi_buffer, cx| {
5980 let auto_indent = match completion.insert_text_mode {
5981 Some(InsertTextMode::AS_IS) => None,
5982 _ => editor.autoindent_mode.clone(),
5983 };
5984 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5985 multi_buffer.edit(edits, auto_indent, cx);
5986 });
5987 }
5988 for (buffer, edits) in linked_edits {
5989 buffer.update(cx, |buffer, cx| {
5990 let snapshot = buffer.snapshot();
5991 let edits = edits
5992 .into_iter()
5993 .map(|(range, text)| {
5994 use text::ToPoint as TP;
5995 let end_point = TP::to_point(&range.end, &snapshot);
5996 let start_point = TP::to_point(&range.start, &snapshot);
5997 (start_point..end_point, text)
5998 })
5999 .sorted_by_key(|(range, _)| range.start);
6000 buffer.edit(edits, None, cx);
6001 })
6002 }
6003
6004 editor.refresh_edit_prediction(true, false, window, cx);
6005 });
6006 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6007
6008 let show_new_completions_on_confirm = completion
6009 .confirm
6010 .as_ref()
6011 .is_some_and(|confirm| confirm(intent, window, cx));
6012 if show_new_completions_on_confirm {
6013 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6014 }
6015
6016 let provider = self.completion_provider.as_ref()?;
6017 drop(completion);
6018 let apply_edits = provider.apply_additional_edits_for_completion(
6019 buffer_handle,
6020 completions_menu.completions.clone(),
6021 candidate_id,
6022 true,
6023 cx,
6024 );
6025
6026 let editor_settings = EditorSettings::get_global(cx);
6027 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6028 // After the code completion is finished, users often want to know what signatures are needed.
6029 // so we should automatically call signature_help
6030 self.show_signature_help(&ShowSignatureHelp, window, cx);
6031 }
6032
6033 Some(cx.foreground_executor().spawn(async move {
6034 apply_edits.await?;
6035 Ok(())
6036 }))
6037 }
6038
6039 pub fn toggle_code_actions(
6040 &mut self,
6041 action: &ToggleCodeActions,
6042 window: &mut Window,
6043 cx: &mut Context<Self>,
6044 ) {
6045 let quick_launch = action.quick_launch;
6046 let mut context_menu = self.context_menu.borrow_mut();
6047 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6048 if code_actions.deployed_from == action.deployed_from {
6049 // Toggle if we're selecting the same one
6050 *context_menu = None;
6051 cx.notify();
6052 return;
6053 } else {
6054 // Otherwise, clear it and start a new one
6055 *context_menu = None;
6056 cx.notify();
6057 }
6058 }
6059 drop(context_menu);
6060 let snapshot = self.snapshot(window, cx);
6061 let deployed_from = action.deployed_from.clone();
6062 let action = action.clone();
6063 self.completion_tasks.clear();
6064 self.discard_edit_prediction(false, cx);
6065
6066 let multibuffer_point = match &action.deployed_from {
6067 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6068 DisplayPoint::new(*row, 0).to_point(&snapshot)
6069 }
6070 _ => self
6071 .selections
6072 .newest::<Point>(&snapshot.display_snapshot)
6073 .head(),
6074 };
6075 let Some((buffer, buffer_row)) = snapshot
6076 .buffer_snapshot()
6077 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6078 .and_then(|(buffer_snapshot, range)| {
6079 self.buffer()
6080 .read(cx)
6081 .buffer(buffer_snapshot.remote_id())
6082 .map(|buffer| (buffer, range.start.row))
6083 })
6084 else {
6085 return;
6086 };
6087 let buffer_id = buffer.read(cx).remote_id();
6088 let tasks = self
6089 .tasks
6090 .get(&(buffer_id, buffer_row))
6091 .map(|t| Arc::new(t.to_owned()));
6092
6093 if !self.focus_handle.is_focused(window) {
6094 return;
6095 }
6096 let project = self.project.clone();
6097
6098 let code_actions_task = match deployed_from {
6099 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6100 _ => self.code_actions(buffer_row, window, cx),
6101 };
6102
6103 let runnable_task = match deployed_from {
6104 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6105 _ => {
6106 let mut task_context_task = Task::ready(None);
6107 if let Some(tasks) = &tasks
6108 && let Some(project) = project
6109 {
6110 task_context_task =
6111 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6112 }
6113
6114 cx.spawn_in(window, {
6115 let buffer = buffer.clone();
6116 async move |editor, cx| {
6117 let task_context = task_context_task.await;
6118
6119 let resolved_tasks =
6120 tasks
6121 .zip(task_context.clone())
6122 .map(|(tasks, task_context)| ResolvedTasks {
6123 templates: tasks.resolve(&task_context).collect(),
6124 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6125 multibuffer_point.row,
6126 tasks.column,
6127 )),
6128 });
6129 let debug_scenarios = editor
6130 .update(cx, |editor, cx| {
6131 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6132 })?
6133 .await;
6134 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6135 }
6136 })
6137 }
6138 };
6139
6140 cx.spawn_in(window, async move |editor, cx| {
6141 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6142 let code_actions = code_actions_task.await;
6143 let spawn_straight_away = quick_launch
6144 && resolved_tasks
6145 .as_ref()
6146 .is_some_and(|tasks| tasks.templates.len() == 1)
6147 && code_actions
6148 .as_ref()
6149 .is_none_or(|actions| actions.is_empty())
6150 && debug_scenarios.is_empty();
6151
6152 editor.update_in(cx, |editor, window, cx| {
6153 crate::hover_popover::hide_hover(editor, cx);
6154 let actions = CodeActionContents::new(
6155 resolved_tasks,
6156 code_actions,
6157 debug_scenarios,
6158 task_context.unwrap_or_default(),
6159 );
6160
6161 // Don't show the menu if there are no actions available
6162 if actions.is_empty() {
6163 cx.notify();
6164 return Task::ready(Ok(()));
6165 }
6166
6167 *editor.context_menu.borrow_mut() =
6168 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6169 buffer,
6170 actions,
6171 selected_item: Default::default(),
6172 scroll_handle: UniformListScrollHandle::default(),
6173 deployed_from,
6174 }));
6175 cx.notify();
6176 if spawn_straight_away
6177 && let Some(task) = editor.confirm_code_action(
6178 &ConfirmCodeAction { item_ix: Some(0) },
6179 window,
6180 cx,
6181 )
6182 {
6183 return task;
6184 }
6185
6186 Task::ready(Ok(()))
6187 })
6188 })
6189 .detach_and_log_err(cx);
6190 }
6191
6192 fn debug_scenarios(
6193 &mut self,
6194 resolved_tasks: &Option<ResolvedTasks>,
6195 buffer: &Entity<Buffer>,
6196 cx: &mut App,
6197 ) -> Task<Vec<task::DebugScenario>> {
6198 maybe!({
6199 let project = self.project()?;
6200 let dap_store = project.read(cx).dap_store();
6201 let mut scenarios = vec![];
6202 let resolved_tasks = resolved_tasks.as_ref()?;
6203 let buffer = buffer.read(cx);
6204 let language = buffer.language()?;
6205 let file = buffer.file();
6206 let debug_adapter = language_settings(language.name().into(), file, cx)
6207 .debuggers
6208 .first()
6209 .map(SharedString::from)
6210 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6211
6212 dap_store.update(cx, |dap_store, cx| {
6213 for (_, task) in &resolved_tasks.templates {
6214 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6215 task.original_task().clone(),
6216 debug_adapter.clone().into(),
6217 task.display_label().to_owned().into(),
6218 cx,
6219 );
6220 scenarios.push(maybe_scenario);
6221 }
6222 });
6223 Some(cx.background_spawn(async move {
6224 futures::future::join_all(scenarios)
6225 .await
6226 .into_iter()
6227 .flatten()
6228 .collect::<Vec<_>>()
6229 }))
6230 })
6231 .unwrap_or_else(|| Task::ready(vec![]))
6232 }
6233
6234 fn code_actions(
6235 &mut self,
6236 buffer_row: u32,
6237 window: &mut Window,
6238 cx: &mut Context<Self>,
6239 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6240 let mut task = self.code_actions_task.take();
6241 cx.spawn_in(window, async move |editor, cx| {
6242 while let Some(prev_task) = task {
6243 prev_task.await.log_err();
6244 task = editor
6245 .update(cx, |this, _| this.code_actions_task.take())
6246 .ok()?;
6247 }
6248
6249 editor
6250 .update(cx, |editor, cx| {
6251 editor
6252 .available_code_actions
6253 .clone()
6254 .and_then(|(location, code_actions)| {
6255 let snapshot = location.buffer.read(cx).snapshot();
6256 let point_range = location.range.to_point(&snapshot);
6257 let point_range = point_range.start.row..=point_range.end.row;
6258 if point_range.contains(&buffer_row) {
6259 Some(code_actions)
6260 } else {
6261 None
6262 }
6263 })
6264 })
6265 .ok()
6266 .flatten()
6267 })
6268 }
6269
6270 pub fn confirm_code_action(
6271 &mut self,
6272 action: &ConfirmCodeAction,
6273 window: &mut Window,
6274 cx: &mut Context<Self>,
6275 ) -> Option<Task<Result<()>>> {
6276 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6277
6278 let actions_menu =
6279 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6280 menu
6281 } else {
6282 return None;
6283 };
6284
6285 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6286 let action = actions_menu.actions.get(action_ix)?;
6287 let title = action.label();
6288 let buffer = actions_menu.buffer;
6289 let workspace = self.workspace()?;
6290
6291 match action {
6292 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6293 workspace.update(cx, |workspace, cx| {
6294 workspace.schedule_resolved_task(
6295 task_source_kind,
6296 resolved_task,
6297 false,
6298 window,
6299 cx,
6300 );
6301
6302 Some(Task::ready(Ok(())))
6303 })
6304 }
6305 CodeActionsItem::CodeAction {
6306 excerpt_id,
6307 action,
6308 provider,
6309 } => {
6310 let apply_code_action =
6311 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6312 let workspace = workspace.downgrade();
6313 Some(cx.spawn_in(window, async move |editor, cx| {
6314 let project_transaction = apply_code_action.await?;
6315 Self::open_project_transaction(
6316 &editor,
6317 workspace,
6318 project_transaction,
6319 title,
6320 cx,
6321 )
6322 .await
6323 }))
6324 }
6325 CodeActionsItem::DebugScenario(scenario) => {
6326 let context = actions_menu.actions.context;
6327
6328 workspace.update(cx, |workspace, cx| {
6329 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6330 workspace.start_debug_session(
6331 scenario,
6332 context,
6333 Some(buffer),
6334 None,
6335 window,
6336 cx,
6337 );
6338 });
6339 Some(Task::ready(Ok(())))
6340 }
6341 }
6342 }
6343
6344 pub async fn open_project_transaction(
6345 editor: &WeakEntity<Editor>,
6346 workspace: WeakEntity<Workspace>,
6347 transaction: ProjectTransaction,
6348 title: String,
6349 cx: &mut AsyncWindowContext,
6350 ) -> Result<()> {
6351 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6352 cx.update(|_, cx| {
6353 entries.sort_unstable_by_key(|(buffer, _)| {
6354 buffer.read(cx).file().map(|f| f.path().clone())
6355 });
6356 })?;
6357 if entries.is_empty() {
6358 return Ok(());
6359 }
6360
6361 // If the project transaction's edits are all contained within this editor, then
6362 // avoid opening a new editor to display them.
6363
6364 if let [(buffer, transaction)] = &*entries {
6365 let excerpt = editor.update(cx, |editor, cx| {
6366 editor
6367 .buffer()
6368 .read(cx)
6369 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6370 })?;
6371 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6372 && excerpted_buffer == *buffer
6373 {
6374 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6375 let excerpt_range = excerpt_range.to_offset(buffer);
6376 buffer
6377 .edited_ranges_for_transaction::<usize>(transaction)
6378 .all(|range| {
6379 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6380 })
6381 })?;
6382
6383 if all_edits_within_excerpt {
6384 return Ok(());
6385 }
6386 }
6387 }
6388
6389 let mut ranges_to_highlight = Vec::new();
6390 let excerpt_buffer = cx.new(|cx| {
6391 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6392 for (buffer_handle, transaction) in &entries {
6393 let edited_ranges = buffer_handle
6394 .read(cx)
6395 .edited_ranges_for_transaction::<Point>(transaction)
6396 .collect::<Vec<_>>();
6397 let (ranges, _) = multibuffer.set_excerpts_for_path(
6398 PathKey::for_buffer(buffer_handle, cx),
6399 buffer_handle.clone(),
6400 edited_ranges,
6401 multibuffer_context_lines(cx),
6402 cx,
6403 );
6404
6405 ranges_to_highlight.extend(ranges);
6406 }
6407 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6408 multibuffer
6409 })?;
6410
6411 workspace.update_in(cx, |workspace, window, cx| {
6412 let project = workspace.project().clone();
6413 let editor =
6414 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6415 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6416 editor.update(cx, |editor, cx| {
6417 editor.highlight_background::<Self>(
6418 &ranges_to_highlight,
6419 |theme| theme.colors().editor_highlighted_line_background,
6420 cx,
6421 );
6422 });
6423 })?;
6424
6425 Ok(())
6426 }
6427
6428 pub fn clear_code_action_providers(&mut self) {
6429 self.code_action_providers.clear();
6430 self.available_code_actions.take();
6431 }
6432
6433 pub fn add_code_action_provider(
6434 &mut self,
6435 provider: Rc<dyn CodeActionProvider>,
6436 window: &mut Window,
6437 cx: &mut Context<Self>,
6438 ) {
6439 if self
6440 .code_action_providers
6441 .iter()
6442 .any(|existing_provider| existing_provider.id() == provider.id())
6443 {
6444 return;
6445 }
6446
6447 self.code_action_providers.push(provider);
6448 self.refresh_code_actions(window, cx);
6449 }
6450
6451 pub fn remove_code_action_provider(
6452 &mut self,
6453 id: Arc<str>,
6454 window: &mut Window,
6455 cx: &mut Context<Self>,
6456 ) {
6457 self.code_action_providers
6458 .retain(|provider| provider.id() != id);
6459 self.refresh_code_actions(window, cx);
6460 }
6461
6462 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6463 !self.code_action_providers.is_empty()
6464 && EditorSettings::get_global(cx).toolbar.code_actions
6465 }
6466
6467 pub fn has_available_code_actions(&self) -> bool {
6468 self.available_code_actions
6469 .as_ref()
6470 .is_some_and(|(_, actions)| !actions.is_empty())
6471 }
6472
6473 fn render_inline_code_actions(
6474 &self,
6475 icon_size: ui::IconSize,
6476 display_row: DisplayRow,
6477 is_active: bool,
6478 cx: &mut Context<Self>,
6479 ) -> AnyElement {
6480 let show_tooltip = !self.context_menu_visible();
6481 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6482 .icon_size(icon_size)
6483 .shape(ui::IconButtonShape::Square)
6484 .icon_color(ui::Color::Hidden)
6485 .toggle_state(is_active)
6486 .when(show_tooltip, |this| {
6487 this.tooltip({
6488 let focus_handle = self.focus_handle.clone();
6489 move |_window, cx| {
6490 Tooltip::for_action_in(
6491 "Toggle Code Actions",
6492 &ToggleCodeActions {
6493 deployed_from: None,
6494 quick_launch: false,
6495 },
6496 &focus_handle,
6497 cx,
6498 )
6499 }
6500 })
6501 })
6502 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6503 window.focus(&editor.focus_handle(cx));
6504 editor.toggle_code_actions(
6505 &crate::actions::ToggleCodeActions {
6506 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6507 display_row,
6508 )),
6509 quick_launch: false,
6510 },
6511 window,
6512 cx,
6513 );
6514 }))
6515 .into_any_element()
6516 }
6517
6518 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6519 &self.context_menu
6520 }
6521
6522 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6523 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6524 cx.background_executor()
6525 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6526 .await;
6527
6528 let (start_buffer, start, _, end, newest_selection) = this
6529 .update(cx, |this, cx| {
6530 let newest_selection = this.selections.newest_anchor().clone();
6531 if newest_selection.head().diff_base_anchor.is_some() {
6532 return None;
6533 }
6534 let display_snapshot = this.display_snapshot(cx);
6535 let newest_selection_adjusted =
6536 this.selections.newest_adjusted(&display_snapshot);
6537 let buffer = this.buffer.read(cx);
6538
6539 let (start_buffer, start) =
6540 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6541 let (end_buffer, end) =
6542 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6543
6544 Some((start_buffer, start, end_buffer, end, newest_selection))
6545 })?
6546 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6547 .context(
6548 "Expected selection to lie in a single buffer when refreshing code actions",
6549 )?;
6550 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6551 let providers = this.code_action_providers.clone();
6552 let tasks = this
6553 .code_action_providers
6554 .iter()
6555 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6556 .collect::<Vec<_>>();
6557 (providers, tasks)
6558 })?;
6559
6560 let mut actions = Vec::new();
6561 for (provider, provider_actions) in
6562 providers.into_iter().zip(future::join_all(tasks).await)
6563 {
6564 if let Some(provider_actions) = provider_actions.log_err() {
6565 actions.extend(provider_actions.into_iter().map(|action| {
6566 AvailableCodeAction {
6567 excerpt_id: newest_selection.start.excerpt_id,
6568 action,
6569 provider: provider.clone(),
6570 }
6571 }));
6572 }
6573 }
6574
6575 this.update(cx, |this, cx| {
6576 this.available_code_actions = if actions.is_empty() {
6577 None
6578 } else {
6579 Some((
6580 Location {
6581 buffer: start_buffer,
6582 range: start..end,
6583 },
6584 actions.into(),
6585 ))
6586 };
6587 cx.notify();
6588 })
6589 }));
6590 }
6591
6592 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6593 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6594 self.show_git_blame_inline = false;
6595
6596 self.show_git_blame_inline_delay_task =
6597 Some(cx.spawn_in(window, async move |this, cx| {
6598 cx.background_executor().timer(delay).await;
6599
6600 this.update(cx, |this, cx| {
6601 this.show_git_blame_inline = true;
6602 cx.notify();
6603 })
6604 .log_err();
6605 }));
6606 }
6607 }
6608
6609 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6610 let snapshot = self.snapshot(window, cx);
6611 let cursor = self
6612 .selections
6613 .newest::<Point>(&snapshot.display_snapshot)
6614 .head();
6615 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6616 else {
6617 return;
6618 };
6619
6620 let Some(blame) = self.blame.as_ref() else {
6621 return;
6622 };
6623
6624 let row_info = RowInfo {
6625 buffer_id: Some(buffer.remote_id()),
6626 buffer_row: Some(point.row),
6627 ..Default::default()
6628 };
6629 let Some((buffer, blame_entry)) = blame
6630 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6631 .flatten()
6632 else {
6633 return;
6634 };
6635
6636 let anchor = self.selections.newest_anchor().head();
6637 let position = self.to_pixel_point(anchor, &snapshot, window);
6638 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6639 self.show_blame_popover(
6640 buffer,
6641 &blame_entry,
6642 position + last_bounds.origin,
6643 true,
6644 cx,
6645 );
6646 };
6647 }
6648
6649 fn show_blame_popover(
6650 &mut self,
6651 buffer: BufferId,
6652 blame_entry: &BlameEntry,
6653 position: gpui::Point<Pixels>,
6654 ignore_timeout: bool,
6655 cx: &mut Context<Self>,
6656 ) {
6657 if let Some(state) = &mut self.inline_blame_popover {
6658 state.hide_task.take();
6659 } else {
6660 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6661 let blame_entry = blame_entry.clone();
6662 let show_task = cx.spawn(async move |editor, cx| {
6663 if !ignore_timeout {
6664 cx.background_executor()
6665 .timer(std::time::Duration::from_millis(blame_popover_delay))
6666 .await;
6667 }
6668 editor
6669 .update(cx, |editor, cx| {
6670 editor.inline_blame_popover_show_task.take();
6671 let Some(blame) = editor.blame.as_ref() else {
6672 return;
6673 };
6674 let blame = blame.read(cx);
6675 let details = blame.details_for_entry(buffer, &blame_entry);
6676 let markdown = cx.new(|cx| {
6677 Markdown::new(
6678 details
6679 .as_ref()
6680 .map(|message| message.message.clone())
6681 .unwrap_or_default(),
6682 None,
6683 None,
6684 cx,
6685 )
6686 });
6687 editor.inline_blame_popover = Some(InlineBlamePopover {
6688 position,
6689 hide_task: None,
6690 popover_bounds: None,
6691 popover_state: InlineBlamePopoverState {
6692 scroll_handle: ScrollHandle::new(),
6693 commit_message: details,
6694 markdown,
6695 },
6696 keyboard_grace: ignore_timeout,
6697 });
6698 cx.notify();
6699 })
6700 .ok();
6701 });
6702 self.inline_blame_popover_show_task = Some(show_task);
6703 }
6704 }
6705
6706 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6707 self.inline_blame_popover_show_task.take();
6708 if let Some(state) = &mut self.inline_blame_popover {
6709 let hide_task = cx.spawn(async move |editor, cx| {
6710 if !ignore_timeout {
6711 cx.background_executor()
6712 .timer(std::time::Duration::from_millis(100))
6713 .await;
6714 }
6715 editor
6716 .update(cx, |editor, cx| {
6717 editor.inline_blame_popover.take();
6718 cx.notify();
6719 })
6720 .ok();
6721 });
6722 state.hide_task = Some(hide_task);
6723 true
6724 } else {
6725 false
6726 }
6727 }
6728
6729 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6730 if self.pending_rename.is_some() {
6731 return None;
6732 }
6733
6734 let provider = self.semantics_provider.clone()?;
6735 let buffer = self.buffer.read(cx);
6736 let newest_selection = self.selections.newest_anchor().clone();
6737 let cursor_position = newest_selection.head();
6738 let (cursor_buffer, cursor_buffer_position) =
6739 buffer.text_anchor_for_position(cursor_position, cx)?;
6740 let (tail_buffer, tail_buffer_position) =
6741 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6742 if cursor_buffer != tail_buffer {
6743 return None;
6744 }
6745
6746 let snapshot = cursor_buffer.read(cx).snapshot();
6747 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6748 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6749 if start_word_range != end_word_range {
6750 self.document_highlights_task.take();
6751 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6752 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6753 return None;
6754 }
6755
6756 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6757 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6758 cx.background_executor()
6759 .timer(Duration::from_millis(debounce))
6760 .await;
6761
6762 let highlights = if let Some(highlights) = cx
6763 .update(|cx| {
6764 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6765 })
6766 .ok()
6767 .flatten()
6768 {
6769 highlights.await.log_err()
6770 } else {
6771 None
6772 };
6773
6774 if let Some(highlights) = highlights {
6775 this.update(cx, |this, cx| {
6776 if this.pending_rename.is_some() {
6777 return;
6778 }
6779
6780 let buffer = this.buffer.read(cx);
6781 if buffer
6782 .text_anchor_for_position(cursor_position, cx)
6783 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6784 {
6785 return;
6786 }
6787
6788 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6789 let mut write_ranges = Vec::new();
6790 let mut read_ranges = Vec::new();
6791 for highlight in highlights {
6792 let buffer_id = cursor_buffer.read(cx).remote_id();
6793 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6794 {
6795 let start = highlight
6796 .range
6797 .start
6798 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6799 let end = highlight
6800 .range
6801 .end
6802 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6803 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6804 continue;
6805 }
6806
6807 let range =
6808 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6809 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6810 write_ranges.push(range);
6811 } else {
6812 read_ranges.push(range);
6813 }
6814 }
6815 }
6816
6817 this.highlight_background::<DocumentHighlightRead>(
6818 &read_ranges,
6819 |theme| theme.colors().editor_document_highlight_read_background,
6820 cx,
6821 );
6822 this.highlight_background::<DocumentHighlightWrite>(
6823 &write_ranges,
6824 |theme| theme.colors().editor_document_highlight_write_background,
6825 cx,
6826 );
6827 cx.notify();
6828 })
6829 .log_err();
6830 }
6831 }));
6832 None
6833 }
6834
6835 fn prepare_highlight_query_from_selection(
6836 &mut self,
6837 window: &Window,
6838 cx: &mut Context<Editor>,
6839 ) -> Option<(String, Range<Anchor>)> {
6840 if matches!(self.mode, EditorMode::SingleLine) {
6841 return None;
6842 }
6843 if !EditorSettings::get_global(cx).selection_highlight {
6844 return None;
6845 }
6846 if self.selections.count() != 1 || self.selections.line_mode() {
6847 return None;
6848 }
6849 let snapshot = self.snapshot(window, cx);
6850 let selection = self.selections.newest::<Point>(&snapshot);
6851 // If the selection spans multiple rows OR it is empty
6852 if selection.start.row != selection.end.row
6853 || selection.start.column == selection.end.column
6854 {
6855 return None;
6856 }
6857 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6858 let query = snapshot
6859 .buffer_snapshot()
6860 .text_for_range(selection_anchor_range.clone())
6861 .collect::<String>();
6862 if query.trim().is_empty() {
6863 return None;
6864 }
6865 Some((query, selection_anchor_range))
6866 }
6867
6868 fn update_selection_occurrence_highlights(
6869 &mut self,
6870 query_text: String,
6871 query_range: Range<Anchor>,
6872 multi_buffer_range_to_query: Range<Point>,
6873 use_debounce: bool,
6874 window: &mut Window,
6875 cx: &mut Context<Editor>,
6876 ) -> Task<()> {
6877 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6878 cx.spawn_in(window, async move |editor, cx| {
6879 if use_debounce {
6880 cx.background_executor()
6881 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6882 .await;
6883 }
6884 let match_task = cx.background_spawn(async move {
6885 let buffer_ranges = multi_buffer_snapshot
6886 .range_to_buffer_ranges(multi_buffer_range_to_query)
6887 .into_iter()
6888 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6889 let mut match_ranges = Vec::new();
6890 let Ok(regex) = project::search::SearchQuery::text(
6891 query_text.clone(),
6892 false,
6893 false,
6894 false,
6895 Default::default(),
6896 Default::default(),
6897 false,
6898 None,
6899 ) else {
6900 return Vec::default();
6901 };
6902 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6903 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6904 match_ranges.extend(
6905 regex
6906 .search(buffer_snapshot, Some(search_range.clone()))
6907 .await
6908 .into_iter()
6909 .filter_map(|match_range| {
6910 let match_start = buffer_snapshot
6911 .anchor_after(search_range.start + match_range.start);
6912 let match_end = buffer_snapshot
6913 .anchor_before(search_range.start + match_range.end);
6914 let match_anchor_range = Anchor::range_in_buffer(
6915 excerpt_id,
6916 buffer_snapshot.remote_id(),
6917 match_start..match_end,
6918 );
6919 (match_anchor_range != query_range).then_some(match_anchor_range)
6920 }),
6921 );
6922 }
6923 match_ranges
6924 });
6925 let match_ranges = match_task.await;
6926 editor
6927 .update_in(cx, |editor, _, cx| {
6928 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6929 if !match_ranges.is_empty() {
6930 editor.highlight_background::<SelectedTextHighlight>(
6931 &match_ranges,
6932 |theme| theme.colors().editor_document_highlight_bracket_background,
6933 cx,
6934 )
6935 }
6936 })
6937 .log_err();
6938 })
6939 }
6940
6941 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6942 struct NewlineFold;
6943 let type_id = std::any::TypeId::of::<NewlineFold>();
6944 if !self.mode.is_single_line() {
6945 return;
6946 }
6947 let snapshot = self.snapshot(window, cx);
6948 if snapshot.buffer_snapshot().max_point().row == 0 {
6949 return;
6950 }
6951 let task = cx.background_spawn(async move {
6952 let new_newlines = snapshot
6953 .buffer_chars_at(0)
6954 .filter_map(|(c, i)| {
6955 if c == '\n' {
6956 Some(
6957 snapshot.buffer_snapshot().anchor_after(i)
6958 ..snapshot.buffer_snapshot().anchor_before(i + 1),
6959 )
6960 } else {
6961 None
6962 }
6963 })
6964 .collect::<Vec<_>>();
6965 let existing_newlines = snapshot
6966 .folds_in_range(0..snapshot.buffer_snapshot().len())
6967 .filter_map(|fold| {
6968 if fold.placeholder.type_tag == Some(type_id) {
6969 Some(fold.range.start..fold.range.end)
6970 } else {
6971 None
6972 }
6973 })
6974 .collect::<Vec<_>>();
6975
6976 (new_newlines, existing_newlines)
6977 });
6978 self.folding_newlines = cx.spawn(async move |this, cx| {
6979 let (new_newlines, existing_newlines) = task.await;
6980 if new_newlines == existing_newlines {
6981 return;
6982 }
6983 let placeholder = FoldPlaceholder {
6984 render: Arc::new(move |_, _, cx| {
6985 div()
6986 .bg(cx.theme().status().hint_background)
6987 .border_b_1()
6988 .size_full()
6989 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6990 .border_color(cx.theme().status().hint)
6991 .child("\\n")
6992 .into_any()
6993 }),
6994 constrain_width: false,
6995 merge_adjacent: false,
6996 type_tag: Some(type_id),
6997 };
6998 let creases = new_newlines
6999 .into_iter()
7000 .map(|range| Crease::simple(range, placeholder.clone()))
7001 .collect();
7002 this.update(cx, |this, cx| {
7003 this.display_map.update(cx, |display_map, cx| {
7004 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7005 display_map.fold(creases, cx);
7006 });
7007 })
7008 .ok();
7009 });
7010 }
7011
7012 fn refresh_selected_text_highlights(
7013 &mut self,
7014 on_buffer_edit: bool,
7015 window: &mut Window,
7016 cx: &mut Context<Editor>,
7017 ) {
7018 let Some((query_text, query_range)) =
7019 self.prepare_highlight_query_from_selection(window, cx)
7020 else {
7021 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7022 self.quick_selection_highlight_task.take();
7023 self.debounced_selection_highlight_task.take();
7024 return;
7025 };
7026 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7027 if on_buffer_edit
7028 || self
7029 .quick_selection_highlight_task
7030 .as_ref()
7031 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7032 {
7033 let multi_buffer_visible_start = self
7034 .scroll_manager
7035 .anchor()
7036 .anchor
7037 .to_point(&multi_buffer_snapshot);
7038 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7039 multi_buffer_visible_start
7040 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7041 Bias::Left,
7042 );
7043 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7044 self.quick_selection_highlight_task = Some((
7045 query_range.clone(),
7046 self.update_selection_occurrence_highlights(
7047 query_text.clone(),
7048 query_range.clone(),
7049 multi_buffer_visible_range,
7050 false,
7051 window,
7052 cx,
7053 ),
7054 ));
7055 }
7056 if on_buffer_edit
7057 || self
7058 .debounced_selection_highlight_task
7059 .as_ref()
7060 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7061 {
7062 let multi_buffer_start = multi_buffer_snapshot
7063 .anchor_before(0)
7064 .to_point(&multi_buffer_snapshot);
7065 let multi_buffer_end = multi_buffer_snapshot
7066 .anchor_after(multi_buffer_snapshot.len())
7067 .to_point(&multi_buffer_snapshot);
7068 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7069 self.debounced_selection_highlight_task = Some((
7070 query_range.clone(),
7071 self.update_selection_occurrence_highlights(
7072 query_text,
7073 query_range,
7074 multi_buffer_full_range,
7075 true,
7076 window,
7077 cx,
7078 ),
7079 ));
7080 }
7081 }
7082
7083 pub fn refresh_edit_prediction(
7084 &mut self,
7085 debounce: bool,
7086 user_requested: bool,
7087 window: &mut Window,
7088 cx: &mut Context<Self>,
7089 ) -> Option<()> {
7090 if DisableAiSettings::get_global(cx).disable_ai {
7091 return None;
7092 }
7093
7094 let provider = self.edit_prediction_provider()?;
7095 let cursor = self.selections.newest_anchor().head();
7096 let (buffer, cursor_buffer_position) =
7097 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7098
7099 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7100 self.discard_edit_prediction(false, cx);
7101 return None;
7102 }
7103
7104 self.update_visible_edit_prediction(window, cx);
7105
7106 if !user_requested
7107 && (!self.should_show_edit_predictions()
7108 || !self.is_focused(window)
7109 || buffer.read(cx).is_empty())
7110 {
7111 self.discard_edit_prediction(false, cx);
7112 return None;
7113 }
7114
7115 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7116 Some(())
7117 }
7118
7119 fn show_edit_predictions_in_menu(&self) -> bool {
7120 match self.edit_prediction_settings {
7121 EditPredictionSettings::Disabled => false,
7122 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7123 }
7124 }
7125
7126 pub fn edit_predictions_enabled(&self) -> bool {
7127 match self.edit_prediction_settings {
7128 EditPredictionSettings::Disabled => false,
7129 EditPredictionSettings::Enabled { .. } => true,
7130 }
7131 }
7132
7133 fn edit_prediction_requires_modifier(&self) -> bool {
7134 match self.edit_prediction_settings {
7135 EditPredictionSettings::Disabled => false,
7136 EditPredictionSettings::Enabled {
7137 preview_requires_modifier,
7138 ..
7139 } => preview_requires_modifier,
7140 }
7141 }
7142
7143 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7144 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7145 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7146 self.discard_edit_prediction(false, cx);
7147 } else {
7148 let selection = self.selections.newest_anchor();
7149 let cursor = selection.head();
7150
7151 if let Some((buffer, cursor_buffer_position)) =
7152 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7153 {
7154 self.edit_prediction_settings =
7155 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7156 }
7157 }
7158 }
7159
7160 fn edit_prediction_settings_at_position(
7161 &self,
7162 buffer: &Entity<Buffer>,
7163 buffer_position: language::Anchor,
7164 cx: &App,
7165 ) -> EditPredictionSettings {
7166 if !self.mode.is_full()
7167 || !self.show_edit_predictions_override.unwrap_or(true)
7168 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7169 {
7170 return EditPredictionSettings::Disabled;
7171 }
7172
7173 let buffer = buffer.read(cx);
7174
7175 let file = buffer.file();
7176
7177 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7178 return EditPredictionSettings::Disabled;
7179 };
7180
7181 let by_provider = matches!(
7182 self.menu_edit_predictions_policy,
7183 MenuEditPredictionsPolicy::ByProvider
7184 );
7185
7186 let show_in_menu = by_provider
7187 && self
7188 .edit_prediction_provider
7189 .as_ref()
7190 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7191
7192 let preview_requires_modifier =
7193 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7194
7195 EditPredictionSettings::Enabled {
7196 show_in_menu,
7197 preview_requires_modifier,
7198 }
7199 }
7200
7201 fn should_show_edit_predictions(&self) -> bool {
7202 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7203 }
7204
7205 pub fn edit_prediction_preview_is_active(&self) -> bool {
7206 matches!(
7207 self.edit_prediction_preview,
7208 EditPredictionPreview::Active { .. }
7209 )
7210 }
7211
7212 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7213 let cursor = self.selections.newest_anchor().head();
7214 if let Some((buffer, cursor_position)) =
7215 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7216 {
7217 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7218 } else {
7219 false
7220 }
7221 }
7222
7223 pub fn supports_minimap(&self, cx: &App) -> bool {
7224 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7225 }
7226
7227 fn edit_predictions_enabled_in_buffer(
7228 &self,
7229 buffer: &Entity<Buffer>,
7230 buffer_position: language::Anchor,
7231 cx: &App,
7232 ) -> bool {
7233 maybe!({
7234 if self.read_only(cx) {
7235 return Some(false);
7236 }
7237 let provider = self.edit_prediction_provider()?;
7238 if !provider.is_enabled(buffer, buffer_position, cx) {
7239 return Some(false);
7240 }
7241 let buffer = buffer.read(cx);
7242 let Some(file) = buffer.file() else {
7243 return Some(true);
7244 };
7245 let settings = all_language_settings(Some(file), cx);
7246 Some(settings.edit_predictions_enabled_for_file(file, cx))
7247 })
7248 .unwrap_or(false)
7249 }
7250
7251 fn cycle_edit_prediction(
7252 &mut self,
7253 direction: Direction,
7254 window: &mut Window,
7255 cx: &mut Context<Self>,
7256 ) -> Option<()> {
7257 let provider = self.edit_prediction_provider()?;
7258 let cursor = self.selections.newest_anchor().head();
7259 let (buffer, cursor_buffer_position) =
7260 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7261 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7262 return None;
7263 }
7264
7265 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7266 self.update_visible_edit_prediction(window, cx);
7267
7268 Some(())
7269 }
7270
7271 pub fn show_edit_prediction(
7272 &mut self,
7273 _: &ShowEditPrediction,
7274 window: &mut Window,
7275 cx: &mut Context<Self>,
7276 ) {
7277 if !self.has_active_edit_prediction() {
7278 self.refresh_edit_prediction(false, true, window, cx);
7279 return;
7280 }
7281
7282 self.update_visible_edit_prediction(window, cx);
7283 }
7284
7285 pub fn display_cursor_names(
7286 &mut self,
7287 _: &DisplayCursorNames,
7288 window: &mut Window,
7289 cx: &mut Context<Self>,
7290 ) {
7291 self.show_cursor_names(window, cx);
7292 }
7293
7294 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7295 self.show_cursor_names = true;
7296 cx.notify();
7297 cx.spawn_in(window, async move |this, cx| {
7298 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7299 this.update(cx, |this, cx| {
7300 this.show_cursor_names = false;
7301 cx.notify()
7302 })
7303 .ok()
7304 })
7305 .detach();
7306 }
7307
7308 pub fn next_edit_prediction(
7309 &mut self,
7310 _: &NextEditPrediction,
7311 window: &mut Window,
7312 cx: &mut Context<Self>,
7313 ) {
7314 if self.has_active_edit_prediction() {
7315 self.cycle_edit_prediction(Direction::Next, window, cx);
7316 } else {
7317 let is_copilot_disabled = self
7318 .refresh_edit_prediction(false, true, window, cx)
7319 .is_none();
7320 if is_copilot_disabled {
7321 cx.propagate();
7322 }
7323 }
7324 }
7325
7326 pub fn previous_edit_prediction(
7327 &mut self,
7328 _: &PreviousEditPrediction,
7329 window: &mut Window,
7330 cx: &mut Context<Self>,
7331 ) {
7332 if self.has_active_edit_prediction() {
7333 self.cycle_edit_prediction(Direction::Prev, window, cx);
7334 } else {
7335 let is_copilot_disabled = self
7336 .refresh_edit_prediction(false, true, window, cx)
7337 .is_none();
7338 if is_copilot_disabled {
7339 cx.propagate();
7340 }
7341 }
7342 }
7343
7344 pub fn accept_edit_prediction(
7345 &mut self,
7346 _: &AcceptEditPrediction,
7347 window: &mut Window,
7348 cx: &mut Context<Self>,
7349 ) {
7350 if self.show_edit_predictions_in_menu() {
7351 self.hide_context_menu(window, cx);
7352 }
7353
7354 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7355 return;
7356 };
7357
7358 match &active_edit_prediction.completion {
7359 EditPrediction::MoveWithin { target, .. } => {
7360 let target = *target;
7361
7362 if let Some(position_map) = &self.last_position_map {
7363 if position_map
7364 .visible_row_range
7365 .contains(&target.to_display_point(&position_map.snapshot).row())
7366 || !self.edit_prediction_requires_modifier()
7367 {
7368 self.unfold_ranges(&[target..target], true, false, cx);
7369 // Note that this is also done in vim's handler of the Tab action.
7370 self.change_selections(
7371 SelectionEffects::scroll(Autoscroll::newest()),
7372 window,
7373 cx,
7374 |selections| {
7375 selections.select_anchor_ranges([target..target]);
7376 },
7377 );
7378 self.clear_row_highlights::<EditPredictionPreview>();
7379
7380 self.edit_prediction_preview
7381 .set_previous_scroll_position(None);
7382 } else {
7383 self.edit_prediction_preview
7384 .set_previous_scroll_position(Some(
7385 position_map.snapshot.scroll_anchor,
7386 ));
7387
7388 self.highlight_rows::<EditPredictionPreview>(
7389 target..target,
7390 cx.theme().colors().editor_highlighted_line_background,
7391 RowHighlightOptions {
7392 autoscroll: true,
7393 ..Default::default()
7394 },
7395 cx,
7396 );
7397 self.request_autoscroll(Autoscroll::fit(), cx);
7398 }
7399 }
7400 }
7401 EditPrediction::MoveOutside { snapshot, target } => {
7402 if let Some(workspace) = self.workspace() {
7403 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7404 .detach_and_log_err(cx);
7405 }
7406 }
7407 EditPrediction::Edit { edits, .. } => {
7408 self.report_edit_prediction_event(
7409 active_edit_prediction.completion_id.clone(),
7410 true,
7411 cx,
7412 );
7413
7414 if let Some(provider) = self.edit_prediction_provider() {
7415 provider.accept(cx);
7416 }
7417
7418 // Store the transaction ID and selections before applying the edit
7419 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7420
7421 let snapshot = self.buffer.read(cx).snapshot(cx);
7422 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7423
7424 self.buffer.update(cx, |buffer, cx| {
7425 buffer.edit(edits.iter().cloned(), None, cx)
7426 });
7427
7428 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7429 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7430 });
7431
7432 let selections = self.selections.disjoint_anchors_arc();
7433 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7434 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7435 if has_new_transaction {
7436 self.selection_history
7437 .insert_transaction(transaction_id_now, selections);
7438 }
7439 }
7440
7441 self.update_visible_edit_prediction(window, cx);
7442 if self.active_edit_prediction.is_none() {
7443 self.refresh_edit_prediction(true, true, window, cx);
7444 }
7445
7446 cx.notify();
7447 }
7448 }
7449
7450 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7451 }
7452
7453 pub fn accept_partial_edit_prediction(
7454 &mut self,
7455 _: &AcceptPartialEditPrediction,
7456 window: &mut Window,
7457 cx: &mut Context<Self>,
7458 ) {
7459 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7460 return;
7461 };
7462 if self.selections.count() != 1 {
7463 return;
7464 }
7465
7466 match &active_edit_prediction.completion {
7467 EditPrediction::MoveWithin { target, .. } => {
7468 let target = *target;
7469 self.change_selections(
7470 SelectionEffects::scroll(Autoscroll::newest()),
7471 window,
7472 cx,
7473 |selections| {
7474 selections.select_anchor_ranges([target..target]);
7475 },
7476 );
7477 }
7478 EditPrediction::MoveOutside { snapshot, target } => {
7479 if let Some(workspace) = self.workspace() {
7480 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7481 .detach_and_log_err(cx);
7482 }
7483 }
7484 EditPrediction::Edit { edits, .. } => {
7485 self.report_edit_prediction_event(
7486 active_edit_prediction.completion_id.clone(),
7487 true,
7488 cx,
7489 );
7490
7491 // Find an insertion that starts at the cursor position.
7492 let snapshot = self.buffer.read(cx).snapshot(cx);
7493 let cursor_offset = self
7494 .selections
7495 .newest::<usize>(&self.display_snapshot(cx))
7496 .head();
7497 let insertion = edits.iter().find_map(|(range, text)| {
7498 let range = range.to_offset(&snapshot);
7499 if range.is_empty() && range.start == cursor_offset {
7500 Some(text)
7501 } else {
7502 None
7503 }
7504 });
7505
7506 if let Some(text) = insertion {
7507 let mut partial_completion = text
7508 .chars()
7509 .by_ref()
7510 .take_while(|c| c.is_alphabetic())
7511 .collect::<String>();
7512 if partial_completion.is_empty() {
7513 partial_completion = text
7514 .chars()
7515 .by_ref()
7516 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7517 .collect::<String>();
7518 }
7519
7520 cx.emit(EditorEvent::InputHandled {
7521 utf16_range_to_replace: None,
7522 text: partial_completion.clone().into(),
7523 });
7524
7525 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7526
7527 self.refresh_edit_prediction(true, true, window, cx);
7528 cx.notify();
7529 } else {
7530 self.accept_edit_prediction(&Default::default(), window, cx);
7531 }
7532 }
7533 }
7534 }
7535
7536 fn discard_edit_prediction(
7537 &mut self,
7538 should_report_edit_prediction_event: bool,
7539 cx: &mut Context<Self>,
7540 ) -> bool {
7541 if should_report_edit_prediction_event {
7542 let completion_id = self
7543 .active_edit_prediction
7544 .as_ref()
7545 .and_then(|active_completion| active_completion.completion_id.clone());
7546
7547 self.report_edit_prediction_event(completion_id, false, cx);
7548 }
7549
7550 if let Some(provider) = self.edit_prediction_provider() {
7551 provider.discard(cx);
7552 }
7553
7554 self.take_active_edit_prediction(cx)
7555 }
7556
7557 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7558 let Some(provider) = self.edit_prediction_provider() else {
7559 return;
7560 };
7561
7562 let Some((_, buffer, _)) = self
7563 .buffer
7564 .read(cx)
7565 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7566 else {
7567 return;
7568 };
7569
7570 let extension = buffer
7571 .read(cx)
7572 .file()
7573 .and_then(|file| Some(file.path().extension()?.to_string()));
7574
7575 let event_type = match accepted {
7576 true => "Edit Prediction Accepted",
7577 false => "Edit Prediction Discarded",
7578 };
7579 telemetry::event!(
7580 event_type,
7581 provider = provider.name(),
7582 prediction_id = id,
7583 suggestion_accepted = accepted,
7584 file_extension = extension,
7585 );
7586 }
7587
7588 fn open_editor_at_anchor(
7589 snapshot: &language::BufferSnapshot,
7590 target: language::Anchor,
7591 workspace: &Entity<Workspace>,
7592 window: &mut Window,
7593 cx: &mut App,
7594 ) -> Task<Result<()>> {
7595 workspace.update(cx, |workspace, cx| {
7596 let path = snapshot.file().map(|file| file.full_path(cx));
7597 let Some(path) =
7598 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7599 else {
7600 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7601 };
7602 let target = text::ToPoint::to_point(&target, snapshot);
7603 let item = workspace.open_path(path, None, true, window, cx);
7604 window.spawn(cx, async move |cx| {
7605 let Some(editor) = item.await?.downcast::<Editor>() else {
7606 return Ok(());
7607 };
7608 editor
7609 .update_in(cx, |editor, window, cx| {
7610 editor.go_to_singleton_buffer_point(target, window, cx);
7611 })
7612 .ok();
7613 anyhow::Ok(())
7614 })
7615 })
7616 }
7617
7618 pub fn has_active_edit_prediction(&self) -> bool {
7619 self.active_edit_prediction.is_some()
7620 }
7621
7622 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7623 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7624 return false;
7625 };
7626
7627 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7628 self.clear_highlights::<EditPredictionHighlight>(cx);
7629 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7630 true
7631 }
7632
7633 /// Returns true when we're displaying the edit prediction popover below the cursor
7634 /// like we are not previewing and the LSP autocomplete menu is visible
7635 /// or we are in `when_holding_modifier` mode.
7636 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7637 if self.edit_prediction_preview_is_active()
7638 || !self.show_edit_predictions_in_menu()
7639 || !self.edit_predictions_enabled()
7640 {
7641 return false;
7642 }
7643
7644 if self.has_visible_completions_menu() {
7645 return true;
7646 }
7647
7648 has_completion && self.edit_prediction_requires_modifier()
7649 }
7650
7651 fn handle_modifiers_changed(
7652 &mut self,
7653 modifiers: Modifiers,
7654 position_map: &PositionMap,
7655 window: &mut Window,
7656 cx: &mut Context<Self>,
7657 ) {
7658 // Ensure that the edit prediction preview is updated, even when not
7659 // enabled, if there's an active edit prediction preview.
7660 if self.show_edit_predictions_in_menu()
7661 || matches!(
7662 self.edit_prediction_preview,
7663 EditPredictionPreview::Active { .. }
7664 )
7665 {
7666 self.update_edit_prediction_preview(&modifiers, window, cx);
7667 }
7668
7669 self.update_selection_mode(&modifiers, position_map, window, cx);
7670
7671 let mouse_position = window.mouse_position();
7672 if !position_map.text_hitbox.is_hovered(window) {
7673 return;
7674 }
7675
7676 self.update_hovered_link(
7677 position_map.point_for_position(mouse_position),
7678 &position_map.snapshot,
7679 modifiers,
7680 window,
7681 cx,
7682 )
7683 }
7684
7685 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7686 match EditorSettings::get_global(cx).multi_cursor_modifier {
7687 MultiCursorModifier::Alt => modifiers.secondary(),
7688 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7689 }
7690 }
7691
7692 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7693 match EditorSettings::get_global(cx).multi_cursor_modifier {
7694 MultiCursorModifier::Alt => modifiers.alt,
7695 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7696 }
7697 }
7698
7699 fn columnar_selection_mode(
7700 modifiers: &Modifiers,
7701 cx: &mut Context<Self>,
7702 ) -> Option<ColumnarMode> {
7703 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7704 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7705 Some(ColumnarMode::FromMouse)
7706 } else if Self::is_alt_pressed(modifiers, cx) {
7707 Some(ColumnarMode::FromSelection)
7708 } else {
7709 None
7710 }
7711 } else {
7712 None
7713 }
7714 }
7715
7716 fn update_selection_mode(
7717 &mut self,
7718 modifiers: &Modifiers,
7719 position_map: &PositionMap,
7720 window: &mut Window,
7721 cx: &mut Context<Self>,
7722 ) {
7723 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7724 return;
7725 };
7726 if self.selections.pending_anchor().is_none() {
7727 return;
7728 }
7729
7730 let mouse_position = window.mouse_position();
7731 let point_for_position = position_map.point_for_position(mouse_position);
7732 let position = point_for_position.previous_valid;
7733
7734 self.select(
7735 SelectPhase::BeginColumnar {
7736 position,
7737 reset: false,
7738 mode,
7739 goal_column: point_for_position.exact_unclipped.column(),
7740 },
7741 window,
7742 cx,
7743 );
7744 }
7745
7746 fn update_edit_prediction_preview(
7747 &mut self,
7748 modifiers: &Modifiers,
7749 window: &mut Window,
7750 cx: &mut Context<Self>,
7751 ) {
7752 let mut modifiers_held = false;
7753 if let Some(accept_keystroke) = self
7754 .accept_edit_prediction_keybind(false, window, cx)
7755 .keystroke()
7756 {
7757 modifiers_held = modifiers_held
7758 || (accept_keystroke.modifiers() == modifiers
7759 && accept_keystroke.modifiers().modified());
7760 };
7761 if let Some(accept_partial_keystroke) = self
7762 .accept_edit_prediction_keybind(true, window, cx)
7763 .keystroke()
7764 {
7765 modifiers_held = modifiers_held
7766 || (accept_partial_keystroke.modifiers() == modifiers
7767 && accept_partial_keystroke.modifiers().modified());
7768 }
7769
7770 if modifiers_held {
7771 if matches!(
7772 self.edit_prediction_preview,
7773 EditPredictionPreview::Inactive { .. }
7774 ) {
7775 self.edit_prediction_preview = EditPredictionPreview::Active {
7776 previous_scroll_position: None,
7777 since: Instant::now(),
7778 };
7779
7780 self.update_visible_edit_prediction(window, cx);
7781 cx.notify();
7782 }
7783 } else if let EditPredictionPreview::Active {
7784 previous_scroll_position,
7785 since,
7786 } = self.edit_prediction_preview
7787 {
7788 if let (Some(previous_scroll_position), Some(position_map)) =
7789 (previous_scroll_position, self.last_position_map.as_ref())
7790 {
7791 self.set_scroll_position(
7792 previous_scroll_position
7793 .scroll_position(&position_map.snapshot.display_snapshot),
7794 window,
7795 cx,
7796 );
7797 }
7798
7799 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7800 released_too_fast: since.elapsed() < Duration::from_millis(200),
7801 };
7802 self.clear_row_highlights::<EditPredictionPreview>();
7803 self.update_visible_edit_prediction(window, cx);
7804 cx.notify();
7805 }
7806 }
7807
7808 fn update_visible_edit_prediction(
7809 &mut self,
7810 _window: &mut Window,
7811 cx: &mut Context<Self>,
7812 ) -> Option<()> {
7813 if DisableAiSettings::get_global(cx).disable_ai {
7814 return None;
7815 }
7816
7817 if self.ime_transaction.is_some() {
7818 self.discard_edit_prediction(false, cx);
7819 return None;
7820 }
7821
7822 let selection = self.selections.newest_anchor();
7823 let cursor = selection.head();
7824 let multibuffer = self.buffer.read(cx).snapshot(cx);
7825 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7826 let excerpt_id = cursor.excerpt_id;
7827
7828 let show_in_menu = self.show_edit_predictions_in_menu();
7829 let completions_menu_has_precedence = !show_in_menu
7830 && (self.context_menu.borrow().is_some()
7831 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7832
7833 if completions_menu_has_precedence
7834 || !offset_selection.is_empty()
7835 || self
7836 .active_edit_prediction
7837 .as_ref()
7838 .is_some_and(|completion| {
7839 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7840 return false;
7841 };
7842 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7843 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7844 !invalidation_range.contains(&offset_selection.head())
7845 })
7846 {
7847 self.discard_edit_prediction(false, cx);
7848 return None;
7849 }
7850
7851 self.take_active_edit_prediction(cx);
7852 let Some(provider) = self.edit_prediction_provider() else {
7853 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7854 return None;
7855 };
7856
7857 let (buffer, cursor_buffer_position) =
7858 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7859
7860 self.edit_prediction_settings =
7861 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7862
7863 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7864
7865 if self.edit_prediction_indent_conflict {
7866 let cursor_point = cursor.to_point(&multibuffer);
7867
7868 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7869
7870 if let Some((_, indent)) = indents.iter().next()
7871 && indent.len == cursor_point.column
7872 {
7873 self.edit_prediction_indent_conflict = false;
7874 }
7875 }
7876
7877 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7878
7879 let (completion_id, edits, edit_preview) = match edit_prediction {
7880 edit_prediction::EditPrediction::Local {
7881 id,
7882 edits,
7883 edit_preview,
7884 } => (id, edits, edit_preview),
7885 edit_prediction::EditPrediction::Jump {
7886 id,
7887 snapshot,
7888 target,
7889 } => {
7890 self.stale_edit_prediction_in_menu = None;
7891 self.active_edit_prediction = Some(EditPredictionState {
7892 inlay_ids: vec![],
7893 completion: EditPrediction::MoveOutside { snapshot, target },
7894 completion_id: id,
7895 invalidation_range: None,
7896 });
7897 cx.notify();
7898 return Some(());
7899 }
7900 };
7901
7902 let edits = edits
7903 .into_iter()
7904 .flat_map(|(range, new_text)| {
7905 Some((
7906 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7907 new_text,
7908 ))
7909 })
7910 .collect::<Vec<_>>();
7911 if edits.is_empty() {
7912 return None;
7913 }
7914
7915 let first_edit_start = edits.first().unwrap().0.start;
7916 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7917 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7918
7919 let last_edit_end = edits.last().unwrap().0.end;
7920 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7921 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7922
7923 let cursor_row = cursor.to_point(&multibuffer).row;
7924
7925 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7926
7927 let mut inlay_ids = Vec::new();
7928 let invalidation_row_range;
7929 let move_invalidation_row_range = if cursor_row < edit_start_row {
7930 Some(cursor_row..edit_end_row)
7931 } else if cursor_row > edit_end_row {
7932 Some(edit_start_row..cursor_row)
7933 } else {
7934 None
7935 };
7936 let supports_jump = self
7937 .edit_prediction_provider
7938 .as_ref()
7939 .map(|provider| provider.provider.supports_jump_to_edit())
7940 .unwrap_or(true);
7941
7942 let is_move = supports_jump
7943 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7944 let completion = if is_move {
7945 invalidation_row_range =
7946 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7947 let target = first_edit_start;
7948 EditPrediction::MoveWithin { target, snapshot }
7949 } else {
7950 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7951 && !self.edit_predictions_hidden_for_vim_mode;
7952
7953 if show_completions_in_buffer {
7954 if edits
7955 .iter()
7956 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7957 {
7958 let mut inlays = Vec::new();
7959 for (range, new_text) in &edits {
7960 let inlay = Inlay::edit_prediction(
7961 post_inc(&mut self.next_inlay_id),
7962 range.start,
7963 new_text.as_ref(),
7964 );
7965 inlay_ids.push(inlay.id);
7966 inlays.push(inlay);
7967 }
7968
7969 self.splice_inlays(&[], inlays, cx);
7970 } else {
7971 let background_color = cx.theme().status().deleted_background;
7972 self.highlight_text::<EditPredictionHighlight>(
7973 edits.iter().map(|(range, _)| range.clone()).collect(),
7974 HighlightStyle {
7975 background_color: Some(background_color),
7976 ..Default::default()
7977 },
7978 cx,
7979 );
7980 }
7981 }
7982
7983 invalidation_row_range = edit_start_row..edit_end_row;
7984
7985 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7986 if provider.show_tab_accept_marker() {
7987 EditDisplayMode::TabAccept
7988 } else {
7989 EditDisplayMode::Inline
7990 }
7991 } else {
7992 EditDisplayMode::DiffPopover
7993 };
7994
7995 EditPrediction::Edit {
7996 edits,
7997 edit_preview,
7998 display_mode,
7999 snapshot,
8000 }
8001 };
8002
8003 let invalidation_range = multibuffer
8004 .anchor_before(Point::new(invalidation_row_range.start, 0))
8005 ..multibuffer.anchor_after(Point::new(
8006 invalidation_row_range.end,
8007 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8008 ));
8009
8010 self.stale_edit_prediction_in_menu = None;
8011 self.active_edit_prediction = Some(EditPredictionState {
8012 inlay_ids,
8013 completion,
8014 completion_id,
8015 invalidation_range: Some(invalidation_range),
8016 });
8017
8018 cx.notify();
8019
8020 Some(())
8021 }
8022
8023 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8024 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8025 }
8026
8027 fn clear_tasks(&mut self) {
8028 self.tasks.clear()
8029 }
8030
8031 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8032 if self.tasks.insert(key, value).is_some() {
8033 // This case should hopefully be rare, but just in case...
8034 log::error!(
8035 "multiple different run targets found on a single line, only the last target will be rendered"
8036 )
8037 }
8038 }
8039
8040 /// Get all display points of breakpoints that will be rendered within editor
8041 ///
8042 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8043 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8044 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8045 fn active_breakpoints(
8046 &self,
8047 range: Range<DisplayRow>,
8048 window: &mut Window,
8049 cx: &mut Context<Self>,
8050 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8051 let mut breakpoint_display_points = HashMap::default();
8052
8053 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8054 return breakpoint_display_points;
8055 };
8056
8057 let snapshot = self.snapshot(window, cx);
8058
8059 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8060 let Some(project) = self.project() else {
8061 return breakpoint_display_points;
8062 };
8063
8064 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8065 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8066
8067 for (buffer_snapshot, range, excerpt_id) in
8068 multi_buffer_snapshot.range_to_buffer_ranges(range)
8069 {
8070 let Some(buffer) = project
8071 .read(cx)
8072 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8073 else {
8074 continue;
8075 };
8076 let breakpoints = breakpoint_store.read(cx).breakpoints(
8077 &buffer,
8078 Some(
8079 buffer_snapshot.anchor_before(range.start)
8080 ..buffer_snapshot.anchor_after(range.end),
8081 ),
8082 buffer_snapshot,
8083 cx,
8084 );
8085 for (breakpoint, state) in breakpoints {
8086 let multi_buffer_anchor =
8087 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8088 let position = multi_buffer_anchor
8089 .to_point(&multi_buffer_snapshot)
8090 .to_display_point(&snapshot);
8091
8092 breakpoint_display_points.insert(
8093 position.row(),
8094 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8095 );
8096 }
8097 }
8098
8099 breakpoint_display_points
8100 }
8101
8102 fn breakpoint_context_menu(
8103 &self,
8104 anchor: Anchor,
8105 window: &mut Window,
8106 cx: &mut Context<Self>,
8107 ) -> Entity<ui::ContextMenu> {
8108 let weak_editor = cx.weak_entity();
8109 let focus_handle = self.focus_handle(cx);
8110
8111 let row = self
8112 .buffer
8113 .read(cx)
8114 .snapshot(cx)
8115 .summary_for_anchor::<Point>(&anchor)
8116 .row;
8117
8118 let breakpoint = self
8119 .breakpoint_at_row(row, window, cx)
8120 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8121
8122 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8123 "Edit Log Breakpoint"
8124 } else {
8125 "Set Log Breakpoint"
8126 };
8127
8128 let condition_breakpoint_msg = if breakpoint
8129 .as_ref()
8130 .is_some_and(|bp| bp.1.condition.is_some())
8131 {
8132 "Edit Condition Breakpoint"
8133 } else {
8134 "Set Condition Breakpoint"
8135 };
8136
8137 let hit_condition_breakpoint_msg = if breakpoint
8138 .as_ref()
8139 .is_some_and(|bp| bp.1.hit_condition.is_some())
8140 {
8141 "Edit Hit Condition Breakpoint"
8142 } else {
8143 "Set Hit Condition Breakpoint"
8144 };
8145
8146 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8147 "Unset Breakpoint"
8148 } else {
8149 "Set Breakpoint"
8150 };
8151
8152 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8153
8154 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8155 BreakpointState::Enabled => Some("Disable"),
8156 BreakpointState::Disabled => Some("Enable"),
8157 });
8158
8159 let (anchor, breakpoint) =
8160 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8161
8162 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8163 menu.on_blur_subscription(Subscription::new(|| {}))
8164 .context(focus_handle)
8165 .when(run_to_cursor, |this| {
8166 let weak_editor = weak_editor.clone();
8167 this.entry("Run to cursor", None, move |window, cx| {
8168 weak_editor
8169 .update(cx, |editor, cx| {
8170 editor.change_selections(
8171 SelectionEffects::no_scroll(),
8172 window,
8173 cx,
8174 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8175 );
8176 })
8177 .ok();
8178
8179 window.dispatch_action(Box::new(RunToCursor), cx);
8180 })
8181 .separator()
8182 })
8183 .when_some(toggle_state_msg, |this, msg| {
8184 this.entry(msg, None, {
8185 let weak_editor = weak_editor.clone();
8186 let breakpoint = breakpoint.clone();
8187 move |_window, cx| {
8188 weak_editor
8189 .update(cx, |this, cx| {
8190 this.edit_breakpoint_at_anchor(
8191 anchor,
8192 breakpoint.as_ref().clone(),
8193 BreakpointEditAction::InvertState,
8194 cx,
8195 );
8196 })
8197 .log_err();
8198 }
8199 })
8200 })
8201 .entry(set_breakpoint_msg, None, {
8202 let weak_editor = weak_editor.clone();
8203 let breakpoint = breakpoint.clone();
8204 move |_window, cx| {
8205 weak_editor
8206 .update(cx, |this, cx| {
8207 this.edit_breakpoint_at_anchor(
8208 anchor,
8209 breakpoint.as_ref().clone(),
8210 BreakpointEditAction::Toggle,
8211 cx,
8212 );
8213 })
8214 .log_err();
8215 }
8216 })
8217 .entry(log_breakpoint_msg, None, {
8218 let breakpoint = breakpoint.clone();
8219 let weak_editor = weak_editor.clone();
8220 move |window, cx| {
8221 weak_editor
8222 .update(cx, |this, cx| {
8223 this.add_edit_breakpoint_block(
8224 anchor,
8225 breakpoint.as_ref(),
8226 BreakpointPromptEditAction::Log,
8227 window,
8228 cx,
8229 );
8230 })
8231 .log_err();
8232 }
8233 })
8234 .entry(condition_breakpoint_msg, None, {
8235 let breakpoint = breakpoint.clone();
8236 let weak_editor = weak_editor.clone();
8237 move |window, cx| {
8238 weak_editor
8239 .update(cx, |this, cx| {
8240 this.add_edit_breakpoint_block(
8241 anchor,
8242 breakpoint.as_ref(),
8243 BreakpointPromptEditAction::Condition,
8244 window,
8245 cx,
8246 );
8247 })
8248 .log_err();
8249 }
8250 })
8251 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8252 weak_editor
8253 .update(cx, |this, cx| {
8254 this.add_edit_breakpoint_block(
8255 anchor,
8256 breakpoint.as_ref(),
8257 BreakpointPromptEditAction::HitCondition,
8258 window,
8259 cx,
8260 );
8261 })
8262 .log_err();
8263 })
8264 })
8265 }
8266
8267 fn render_breakpoint(
8268 &self,
8269 position: Anchor,
8270 row: DisplayRow,
8271 breakpoint: &Breakpoint,
8272 state: Option<BreakpointSessionState>,
8273 cx: &mut Context<Self>,
8274 ) -> IconButton {
8275 let is_rejected = state.is_some_and(|s| !s.verified);
8276 // Is it a breakpoint that shows up when hovering over gutter?
8277 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8278 (false, false),
8279 |PhantomBreakpointIndicator {
8280 is_active,
8281 display_row,
8282 collides_with_existing_breakpoint,
8283 }| {
8284 (
8285 is_active && display_row == row,
8286 collides_with_existing_breakpoint,
8287 )
8288 },
8289 );
8290
8291 let (color, icon) = {
8292 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8293 (false, false) => ui::IconName::DebugBreakpoint,
8294 (true, false) => ui::IconName::DebugLogBreakpoint,
8295 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8296 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8297 };
8298
8299 let color = if is_phantom {
8300 Color::Hint
8301 } else if is_rejected {
8302 Color::Disabled
8303 } else {
8304 Color::Debugger
8305 };
8306
8307 (color, icon)
8308 };
8309
8310 let breakpoint = Arc::from(breakpoint.clone());
8311
8312 let alt_as_text = gpui::Keystroke {
8313 modifiers: Modifiers::secondary_key(),
8314 ..Default::default()
8315 };
8316 let primary_action_text = if breakpoint.is_disabled() {
8317 "Enable breakpoint"
8318 } else if is_phantom && !collides_with_existing {
8319 "Set breakpoint"
8320 } else {
8321 "Unset breakpoint"
8322 };
8323 let focus_handle = self.focus_handle.clone();
8324
8325 let meta = if is_rejected {
8326 SharedString::from("No executable code is associated with this line.")
8327 } else if collides_with_existing && !breakpoint.is_disabled() {
8328 SharedString::from(format!(
8329 "{alt_as_text}-click to disable,\nright-click for more options."
8330 ))
8331 } else {
8332 SharedString::from("Right-click for more options.")
8333 };
8334 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8335 .icon_size(IconSize::XSmall)
8336 .size(ui::ButtonSize::None)
8337 .when(is_rejected, |this| {
8338 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8339 })
8340 .icon_color(color)
8341 .style(ButtonStyle::Transparent)
8342 .on_click(cx.listener({
8343 move |editor, event: &ClickEvent, window, cx| {
8344 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8345 BreakpointEditAction::InvertState
8346 } else {
8347 BreakpointEditAction::Toggle
8348 };
8349
8350 window.focus(&editor.focus_handle(cx));
8351 editor.edit_breakpoint_at_anchor(
8352 position,
8353 breakpoint.as_ref().clone(),
8354 edit_action,
8355 cx,
8356 );
8357 }
8358 }))
8359 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8360 editor.set_breakpoint_context_menu(
8361 row,
8362 Some(position),
8363 event.position(),
8364 window,
8365 cx,
8366 );
8367 }))
8368 .tooltip(move |_window, cx| {
8369 Tooltip::with_meta_in(
8370 primary_action_text,
8371 Some(&ToggleBreakpoint),
8372 meta.clone(),
8373 &focus_handle,
8374 cx,
8375 )
8376 })
8377 }
8378
8379 fn build_tasks_context(
8380 project: &Entity<Project>,
8381 buffer: &Entity<Buffer>,
8382 buffer_row: u32,
8383 tasks: &Arc<RunnableTasks>,
8384 cx: &mut Context<Self>,
8385 ) -> Task<Option<task::TaskContext>> {
8386 let position = Point::new(buffer_row, tasks.column);
8387 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8388 let location = Location {
8389 buffer: buffer.clone(),
8390 range: range_start..range_start,
8391 };
8392 // Fill in the environmental variables from the tree-sitter captures
8393 let mut captured_task_variables = TaskVariables::default();
8394 for (capture_name, value) in tasks.extra_variables.clone() {
8395 captured_task_variables.insert(
8396 task::VariableName::Custom(capture_name.into()),
8397 value.clone(),
8398 );
8399 }
8400 project.update(cx, |project, cx| {
8401 project.task_store().update(cx, |task_store, cx| {
8402 task_store.task_context_for_location(captured_task_variables, location, cx)
8403 })
8404 })
8405 }
8406
8407 pub fn spawn_nearest_task(
8408 &mut self,
8409 action: &SpawnNearestTask,
8410 window: &mut Window,
8411 cx: &mut Context<Self>,
8412 ) {
8413 let Some((workspace, _)) = self.workspace.clone() else {
8414 return;
8415 };
8416 let Some(project) = self.project.clone() else {
8417 return;
8418 };
8419
8420 // Try to find a closest, enclosing node using tree-sitter that has a task
8421 let Some((buffer, buffer_row, tasks)) = self
8422 .find_enclosing_node_task(cx)
8423 // Or find the task that's closest in row-distance.
8424 .or_else(|| self.find_closest_task(cx))
8425 else {
8426 return;
8427 };
8428
8429 let reveal_strategy = action.reveal;
8430 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8431 cx.spawn_in(window, async move |_, cx| {
8432 let context = task_context.await?;
8433 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8434
8435 let resolved = &mut resolved_task.resolved;
8436 resolved.reveal = reveal_strategy;
8437
8438 workspace
8439 .update_in(cx, |workspace, window, cx| {
8440 workspace.schedule_resolved_task(
8441 task_source_kind,
8442 resolved_task,
8443 false,
8444 window,
8445 cx,
8446 );
8447 })
8448 .ok()
8449 })
8450 .detach();
8451 }
8452
8453 fn find_closest_task(
8454 &mut self,
8455 cx: &mut Context<Self>,
8456 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8457 let cursor_row = self
8458 .selections
8459 .newest_adjusted(&self.display_snapshot(cx))
8460 .head()
8461 .row;
8462
8463 let ((buffer_id, row), tasks) = self
8464 .tasks
8465 .iter()
8466 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8467
8468 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8469 let tasks = Arc::new(tasks.to_owned());
8470 Some((buffer, *row, tasks))
8471 }
8472
8473 fn find_enclosing_node_task(
8474 &mut self,
8475 cx: &mut Context<Self>,
8476 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8477 let snapshot = self.buffer.read(cx).snapshot(cx);
8478 let offset = self
8479 .selections
8480 .newest::<usize>(&self.display_snapshot(cx))
8481 .head();
8482 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8483 let buffer_id = excerpt.buffer().remote_id();
8484
8485 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8486 let mut cursor = layer.node().walk();
8487
8488 while cursor.goto_first_child_for_byte(offset).is_some() {
8489 if cursor.node().end_byte() == offset {
8490 cursor.goto_next_sibling();
8491 }
8492 }
8493
8494 // Ascend to the smallest ancestor that contains the range and has a task.
8495 loop {
8496 let node = cursor.node();
8497 let node_range = node.byte_range();
8498 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8499
8500 // Check if this node contains our offset
8501 if node_range.start <= offset && node_range.end >= offset {
8502 // If it contains offset, check for task
8503 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8504 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8505 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8506 }
8507 }
8508
8509 if !cursor.goto_parent() {
8510 break;
8511 }
8512 }
8513 None
8514 }
8515
8516 fn render_run_indicator(
8517 &self,
8518 _style: &EditorStyle,
8519 is_active: bool,
8520 row: DisplayRow,
8521 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8522 cx: &mut Context<Self>,
8523 ) -> IconButton {
8524 let color = Color::Muted;
8525 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8526
8527 IconButton::new(
8528 ("run_indicator", row.0 as usize),
8529 ui::IconName::PlayOutlined,
8530 )
8531 .shape(ui::IconButtonShape::Square)
8532 .icon_size(IconSize::XSmall)
8533 .icon_color(color)
8534 .toggle_state(is_active)
8535 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8536 let quick_launch = match e {
8537 ClickEvent::Keyboard(_) => true,
8538 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8539 };
8540
8541 window.focus(&editor.focus_handle(cx));
8542 editor.toggle_code_actions(
8543 &ToggleCodeActions {
8544 deployed_from: Some(CodeActionSource::RunMenu(row)),
8545 quick_launch,
8546 },
8547 window,
8548 cx,
8549 );
8550 }))
8551 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8552 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8553 }))
8554 }
8555
8556 pub fn context_menu_visible(&self) -> bool {
8557 !self.edit_prediction_preview_is_active()
8558 && self
8559 .context_menu
8560 .borrow()
8561 .as_ref()
8562 .is_some_and(|menu| menu.visible())
8563 }
8564
8565 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8566 self.context_menu
8567 .borrow()
8568 .as_ref()
8569 .map(|menu| menu.origin())
8570 }
8571
8572 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8573 self.context_menu_options = Some(options);
8574 }
8575
8576 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8577 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8578
8579 fn render_edit_prediction_popover(
8580 &mut self,
8581 text_bounds: &Bounds<Pixels>,
8582 content_origin: gpui::Point<Pixels>,
8583 right_margin: Pixels,
8584 editor_snapshot: &EditorSnapshot,
8585 visible_row_range: Range<DisplayRow>,
8586 scroll_top: ScrollOffset,
8587 scroll_bottom: ScrollOffset,
8588 line_layouts: &[LineWithInvisibles],
8589 line_height: Pixels,
8590 scroll_position: gpui::Point<ScrollOffset>,
8591 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8592 newest_selection_head: Option<DisplayPoint>,
8593 editor_width: Pixels,
8594 style: &EditorStyle,
8595 window: &mut Window,
8596 cx: &mut App,
8597 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8598 if self.mode().is_minimap() {
8599 return None;
8600 }
8601 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8602
8603 if self.edit_prediction_visible_in_cursor_popover(true) {
8604 return None;
8605 }
8606
8607 match &active_edit_prediction.completion {
8608 EditPrediction::MoveWithin { target, .. } => {
8609 let target_display_point = target.to_display_point(editor_snapshot);
8610
8611 if self.edit_prediction_requires_modifier() {
8612 if !self.edit_prediction_preview_is_active() {
8613 return None;
8614 }
8615
8616 self.render_edit_prediction_modifier_jump_popover(
8617 text_bounds,
8618 content_origin,
8619 visible_row_range,
8620 line_layouts,
8621 line_height,
8622 scroll_pixel_position,
8623 newest_selection_head,
8624 target_display_point,
8625 window,
8626 cx,
8627 )
8628 } else {
8629 self.render_edit_prediction_eager_jump_popover(
8630 text_bounds,
8631 content_origin,
8632 editor_snapshot,
8633 visible_row_range,
8634 scroll_top,
8635 scroll_bottom,
8636 line_height,
8637 scroll_pixel_position,
8638 target_display_point,
8639 editor_width,
8640 window,
8641 cx,
8642 )
8643 }
8644 }
8645 EditPrediction::Edit {
8646 display_mode: EditDisplayMode::Inline,
8647 ..
8648 } => None,
8649 EditPrediction::Edit {
8650 display_mode: EditDisplayMode::TabAccept,
8651 edits,
8652 ..
8653 } => {
8654 let range = &edits.first()?.0;
8655 let target_display_point = range.end.to_display_point(editor_snapshot);
8656
8657 self.render_edit_prediction_end_of_line_popover(
8658 "Accept",
8659 editor_snapshot,
8660 visible_row_range,
8661 target_display_point,
8662 line_height,
8663 scroll_pixel_position,
8664 content_origin,
8665 editor_width,
8666 window,
8667 cx,
8668 )
8669 }
8670 EditPrediction::Edit {
8671 edits,
8672 edit_preview,
8673 display_mode: EditDisplayMode::DiffPopover,
8674 snapshot,
8675 } => self.render_edit_prediction_diff_popover(
8676 text_bounds,
8677 content_origin,
8678 right_margin,
8679 editor_snapshot,
8680 visible_row_range,
8681 line_layouts,
8682 line_height,
8683 scroll_position,
8684 scroll_pixel_position,
8685 newest_selection_head,
8686 editor_width,
8687 style,
8688 edits,
8689 edit_preview,
8690 snapshot,
8691 window,
8692 cx,
8693 ),
8694 EditPrediction::MoveOutside { snapshot, .. } => {
8695 let file_name = snapshot
8696 .file()
8697 .map(|file| file.file_name(cx))
8698 .unwrap_or("untitled");
8699 let mut element = self
8700 .render_edit_prediction_line_popover(
8701 format!("Jump to {file_name}"),
8702 Some(IconName::ZedPredict),
8703 window,
8704 cx,
8705 )
8706 .into_any();
8707
8708 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8709 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8710 let origin_y = text_bounds.size.height - size.height - px(30.);
8711 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8712 element.prepaint_at(origin, window, cx);
8713
8714 Some((element, origin))
8715 }
8716 }
8717 }
8718
8719 fn render_edit_prediction_modifier_jump_popover(
8720 &mut self,
8721 text_bounds: &Bounds<Pixels>,
8722 content_origin: gpui::Point<Pixels>,
8723 visible_row_range: Range<DisplayRow>,
8724 line_layouts: &[LineWithInvisibles],
8725 line_height: Pixels,
8726 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8727 newest_selection_head: Option<DisplayPoint>,
8728 target_display_point: DisplayPoint,
8729 window: &mut Window,
8730 cx: &mut App,
8731 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8732 let scrolled_content_origin =
8733 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8734
8735 const SCROLL_PADDING_Y: Pixels = px(12.);
8736
8737 if target_display_point.row() < visible_row_range.start {
8738 return self.render_edit_prediction_scroll_popover(
8739 |_| SCROLL_PADDING_Y,
8740 IconName::ArrowUp,
8741 visible_row_range,
8742 line_layouts,
8743 newest_selection_head,
8744 scrolled_content_origin,
8745 window,
8746 cx,
8747 );
8748 } else if target_display_point.row() >= visible_row_range.end {
8749 return self.render_edit_prediction_scroll_popover(
8750 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8751 IconName::ArrowDown,
8752 visible_row_range,
8753 line_layouts,
8754 newest_selection_head,
8755 scrolled_content_origin,
8756 window,
8757 cx,
8758 );
8759 }
8760
8761 const POLE_WIDTH: Pixels = px(2.);
8762
8763 let line_layout =
8764 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8765 let target_column = target_display_point.column() as usize;
8766
8767 let target_x = line_layout.x_for_index(target_column);
8768 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8769 - scroll_pixel_position.y;
8770
8771 let flag_on_right = target_x < text_bounds.size.width / 2.;
8772
8773 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8774 border_color.l += 0.001;
8775
8776 let mut element = v_flex()
8777 .items_end()
8778 .when(flag_on_right, |el| el.items_start())
8779 .child(if flag_on_right {
8780 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8781 .rounded_bl(px(0.))
8782 .rounded_tl(px(0.))
8783 .border_l_2()
8784 .border_color(border_color)
8785 } else {
8786 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8787 .rounded_br(px(0.))
8788 .rounded_tr(px(0.))
8789 .border_r_2()
8790 .border_color(border_color)
8791 })
8792 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8793 .into_any();
8794
8795 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8796
8797 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8798 - point(
8799 if flag_on_right {
8800 POLE_WIDTH
8801 } else {
8802 size.width - POLE_WIDTH
8803 },
8804 size.height - line_height,
8805 );
8806
8807 origin.x = origin.x.max(content_origin.x);
8808
8809 element.prepaint_at(origin, window, cx);
8810
8811 Some((element, origin))
8812 }
8813
8814 fn render_edit_prediction_scroll_popover(
8815 &mut self,
8816 to_y: impl Fn(Size<Pixels>) -> Pixels,
8817 scroll_icon: IconName,
8818 visible_row_range: Range<DisplayRow>,
8819 line_layouts: &[LineWithInvisibles],
8820 newest_selection_head: Option<DisplayPoint>,
8821 scrolled_content_origin: gpui::Point<Pixels>,
8822 window: &mut Window,
8823 cx: &mut App,
8824 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8825 let mut element = self
8826 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8827 .into_any();
8828
8829 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8830
8831 let cursor = newest_selection_head?;
8832 let cursor_row_layout =
8833 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8834 let cursor_column = cursor.column() as usize;
8835
8836 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8837
8838 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8839
8840 element.prepaint_at(origin, window, cx);
8841 Some((element, origin))
8842 }
8843
8844 fn render_edit_prediction_eager_jump_popover(
8845 &mut self,
8846 text_bounds: &Bounds<Pixels>,
8847 content_origin: gpui::Point<Pixels>,
8848 editor_snapshot: &EditorSnapshot,
8849 visible_row_range: Range<DisplayRow>,
8850 scroll_top: ScrollOffset,
8851 scroll_bottom: ScrollOffset,
8852 line_height: Pixels,
8853 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8854 target_display_point: DisplayPoint,
8855 editor_width: Pixels,
8856 window: &mut Window,
8857 cx: &mut App,
8858 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8859 if target_display_point.row().as_f64() < scroll_top {
8860 let mut element = self
8861 .render_edit_prediction_line_popover(
8862 "Jump to Edit",
8863 Some(IconName::ArrowUp),
8864 window,
8865 cx,
8866 )
8867 .into_any();
8868
8869 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8870 let offset = point(
8871 (text_bounds.size.width - size.width) / 2.,
8872 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8873 );
8874
8875 let origin = text_bounds.origin + offset;
8876 element.prepaint_at(origin, window, cx);
8877 Some((element, origin))
8878 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8879 let mut element = self
8880 .render_edit_prediction_line_popover(
8881 "Jump to Edit",
8882 Some(IconName::ArrowDown),
8883 window,
8884 cx,
8885 )
8886 .into_any();
8887
8888 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8889 let offset = point(
8890 (text_bounds.size.width - size.width) / 2.,
8891 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8892 );
8893
8894 let origin = text_bounds.origin + offset;
8895 element.prepaint_at(origin, window, cx);
8896 Some((element, origin))
8897 } else {
8898 self.render_edit_prediction_end_of_line_popover(
8899 "Jump to Edit",
8900 editor_snapshot,
8901 visible_row_range,
8902 target_display_point,
8903 line_height,
8904 scroll_pixel_position,
8905 content_origin,
8906 editor_width,
8907 window,
8908 cx,
8909 )
8910 }
8911 }
8912
8913 fn render_edit_prediction_end_of_line_popover(
8914 self: &mut Editor,
8915 label: &'static str,
8916 editor_snapshot: &EditorSnapshot,
8917 visible_row_range: Range<DisplayRow>,
8918 target_display_point: DisplayPoint,
8919 line_height: Pixels,
8920 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8921 content_origin: gpui::Point<Pixels>,
8922 editor_width: Pixels,
8923 window: &mut Window,
8924 cx: &mut App,
8925 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8926 let target_line_end = DisplayPoint::new(
8927 target_display_point.row(),
8928 editor_snapshot.line_len(target_display_point.row()),
8929 );
8930
8931 let mut element = self
8932 .render_edit_prediction_line_popover(label, None, window, cx)
8933 .into_any();
8934
8935 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8936
8937 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8938
8939 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8940 let mut origin = start_point
8941 + line_origin
8942 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8943 origin.x = origin.x.max(content_origin.x);
8944
8945 let max_x = content_origin.x + editor_width - size.width;
8946
8947 if origin.x > max_x {
8948 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8949
8950 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8951 origin.y += offset;
8952 IconName::ArrowUp
8953 } else {
8954 origin.y -= offset;
8955 IconName::ArrowDown
8956 };
8957
8958 element = self
8959 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
8960 .into_any();
8961
8962 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8963
8964 origin.x = content_origin.x + editor_width - size.width - px(2.);
8965 }
8966
8967 element.prepaint_at(origin, window, cx);
8968 Some((element, origin))
8969 }
8970
8971 fn render_edit_prediction_diff_popover(
8972 self: &Editor,
8973 text_bounds: &Bounds<Pixels>,
8974 content_origin: gpui::Point<Pixels>,
8975 right_margin: Pixels,
8976 editor_snapshot: &EditorSnapshot,
8977 visible_row_range: Range<DisplayRow>,
8978 line_layouts: &[LineWithInvisibles],
8979 line_height: Pixels,
8980 scroll_position: gpui::Point<ScrollOffset>,
8981 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8982 newest_selection_head: Option<DisplayPoint>,
8983 editor_width: Pixels,
8984 style: &EditorStyle,
8985 edits: &Vec<(Range<Anchor>, Arc<str>)>,
8986 edit_preview: &Option<language::EditPreview>,
8987 snapshot: &language::BufferSnapshot,
8988 window: &mut Window,
8989 cx: &mut App,
8990 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8991 let edit_start = edits
8992 .first()
8993 .unwrap()
8994 .0
8995 .start
8996 .to_display_point(editor_snapshot);
8997 let edit_end = edits
8998 .last()
8999 .unwrap()
9000 .0
9001 .end
9002 .to_display_point(editor_snapshot);
9003
9004 let is_visible = visible_row_range.contains(&edit_start.row())
9005 || visible_row_range.contains(&edit_end.row());
9006 if !is_visible {
9007 return None;
9008 }
9009
9010 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9011 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9012 } else {
9013 // Fallback for providers without edit_preview
9014 crate::edit_prediction_fallback_text(edits, cx)
9015 };
9016
9017 let styled_text = highlighted_edits.to_styled_text(&style.text);
9018 let line_count = highlighted_edits.text.lines().count();
9019
9020 const BORDER_WIDTH: Pixels = px(1.);
9021
9022 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9023 let has_keybind = keybind.is_some();
9024
9025 let mut element = h_flex()
9026 .items_start()
9027 .child(
9028 h_flex()
9029 .bg(cx.theme().colors().editor_background)
9030 .border(BORDER_WIDTH)
9031 .shadow_xs()
9032 .border_color(cx.theme().colors().border)
9033 .rounded_l_lg()
9034 .when(line_count > 1, |el| el.rounded_br_lg())
9035 .pr_1()
9036 .child(styled_text),
9037 )
9038 .child(
9039 h_flex()
9040 .h(line_height + BORDER_WIDTH * 2.)
9041 .px_1p5()
9042 .gap_1()
9043 // Workaround: For some reason, there's a gap if we don't do this
9044 .ml(-BORDER_WIDTH)
9045 .shadow(vec![gpui::BoxShadow {
9046 color: gpui::black().opacity(0.05),
9047 offset: point(px(1.), px(1.)),
9048 blur_radius: px(2.),
9049 spread_radius: px(0.),
9050 }])
9051 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9052 .border(BORDER_WIDTH)
9053 .border_color(cx.theme().colors().border)
9054 .rounded_r_lg()
9055 .id("edit_prediction_diff_popover_keybind")
9056 .when(!has_keybind, |el| {
9057 let status_colors = cx.theme().status();
9058
9059 el.bg(status_colors.error_background)
9060 .border_color(status_colors.error.opacity(0.6))
9061 .child(Icon::new(IconName::Info).color(Color::Error))
9062 .cursor_default()
9063 .hoverable_tooltip(move |_window, cx| {
9064 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9065 })
9066 })
9067 .children(keybind),
9068 )
9069 .into_any();
9070
9071 let longest_row =
9072 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9073 let longest_line_width = if visible_row_range.contains(&longest_row) {
9074 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9075 } else {
9076 layout_line(
9077 longest_row,
9078 editor_snapshot,
9079 style,
9080 editor_width,
9081 |_| false,
9082 window,
9083 cx,
9084 )
9085 .width
9086 };
9087
9088 let viewport_bounds =
9089 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9090 right: -right_margin,
9091 ..Default::default()
9092 });
9093
9094 let x_after_longest = Pixels::from(
9095 ScrollPixelOffset::from(
9096 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9097 ) - scroll_pixel_position.x,
9098 );
9099
9100 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9101
9102 // Fully visible if it can be displayed within the window (allow overlapping other
9103 // panes). However, this is only allowed if the popover starts within text_bounds.
9104 let can_position_to_the_right = x_after_longest < text_bounds.right()
9105 && x_after_longest + element_bounds.width < viewport_bounds.right();
9106
9107 let mut origin = if can_position_to_the_right {
9108 point(
9109 x_after_longest,
9110 text_bounds.origin.y
9111 + Pixels::from(
9112 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9113 - scroll_pixel_position.y,
9114 ),
9115 )
9116 } else {
9117 let cursor_row = newest_selection_head.map(|head| head.row());
9118 let above_edit = edit_start
9119 .row()
9120 .0
9121 .checked_sub(line_count as u32)
9122 .map(DisplayRow);
9123 let below_edit = Some(edit_end.row() + 1);
9124 let above_cursor =
9125 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9126 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9127
9128 // Place the edit popover adjacent to the edit if there is a location
9129 // available that is onscreen and does not obscure the cursor. Otherwise,
9130 // place it adjacent to the cursor.
9131 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9132 .into_iter()
9133 .flatten()
9134 .find(|&start_row| {
9135 let end_row = start_row + line_count as u32;
9136 visible_row_range.contains(&start_row)
9137 && visible_row_range.contains(&end_row)
9138 && cursor_row
9139 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9140 })?;
9141
9142 content_origin
9143 + point(
9144 Pixels::from(-scroll_pixel_position.x),
9145 Pixels::from(
9146 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9147 ),
9148 )
9149 };
9150
9151 origin.x -= BORDER_WIDTH;
9152
9153 window.defer_draw(element, origin, 1);
9154
9155 // Do not return an element, since it will already be drawn due to defer_draw.
9156 None
9157 }
9158
9159 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9160 px(30.)
9161 }
9162
9163 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9164 if self.read_only(cx) {
9165 cx.theme().players().read_only()
9166 } else {
9167 self.style.as_ref().unwrap().local_player
9168 }
9169 }
9170
9171 fn render_edit_prediction_accept_keybind(
9172 &self,
9173 window: &mut Window,
9174 cx: &mut App,
9175 ) -> Option<AnyElement> {
9176 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9177 let accept_keystroke = accept_binding.keystroke()?;
9178
9179 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9180
9181 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9182 Color::Accent
9183 } else {
9184 Color::Muted
9185 };
9186
9187 h_flex()
9188 .px_0p5()
9189 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9190 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9191 .text_size(TextSize::XSmall.rems(cx))
9192 .child(h_flex().children(ui::render_modifiers(
9193 accept_keystroke.modifiers(),
9194 PlatformStyle::platform(),
9195 Some(modifiers_color),
9196 Some(IconSize::XSmall.rems().into()),
9197 true,
9198 )))
9199 .when(is_platform_style_mac, |parent| {
9200 parent.child(accept_keystroke.key().to_string())
9201 })
9202 .when(!is_platform_style_mac, |parent| {
9203 parent.child(
9204 Key::new(
9205 util::capitalize(accept_keystroke.key()),
9206 Some(Color::Default),
9207 )
9208 .size(Some(IconSize::XSmall.rems().into())),
9209 )
9210 })
9211 .into_any()
9212 .into()
9213 }
9214
9215 fn render_edit_prediction_line_popover(
9216 &self,
9217 label: impl Into<SharedString>,
9218 icon: Option<IconName>,
9219 window: &mut Window,
9220 cx: &mut App,
9221 ) -> Stateful<Div> {
9222 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9223
9224 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9225 let has_keybind = keybind.is_some();
9226
9227 h_flex()
9228 .id("ep-line-popover")
9229 .py_0p5()
9230 .pl_1()
9231 .pr(padding_right)
9232 .gap_1()
9233 .rounded_md()
9234 .border_1()
9235 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9236 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9237 .shadow_xs()
9238 .when(!has_keybind, |el| {
9239 let status_colors = cx.theme().status();
9240
9241 el.bg(status_colors.error_background)
9242 .border_color(status_colors.error.opacity(0.6))
9243 .pl_2()
9244 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9245 .cursor_default()
9246 .hoverable_tooltip(move |_window, cx| {
9247 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9248 })
9249 })
9250 .children(keybind)
9251 .child(
9252 Label::new(label)
9253 .size(LabelSize::Small)
9254 .when(!has_keybind, |el| {
9255 el.color(cx.theme().status().error.into()).strikethrough()
9256 }),
9257 )
9258 .when(!has_keybind, |el| {
9259 el.child(
9260 h_flex().ml_1().child(
9261 Icon::new(IconName::Info)
9262 .size(IconSize::Small)
9263 .color(cx.theme().status().error.into()),
9264 ),
9265 )
9266 })
9267 .when_some(icon, |element, icon| {
9268 element.child(
9269 div()
9270 .mt(px(1.5))
9271 .child(Icon::new(icon).size(IconSize::Small)),
9272 )
9273 })
9274 }
9275
9276 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9277 let accent_color = cx.theme().colors().text_accent;
9278 let editor_bg_color = cx.theme().colors().editor_background;
9279 editor_bg_color.blend(accent_color.opacity(0.1))
9280 }
9281
9282 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9283 let accent_color = cx.theme().colors().text_accent;
9284 let editor_bg_color = cx.theme().colors().editor_background;
9285 editor_bg_color.blend(accent_color.opacity(0.6))
9286 }
9287 fn get_prediction_provider_icon_name(
9288 provider: &Option<RegisteredEditPredictionProvider>,
9289 ) -> IconName {
9290 match provider {
9291 Some(provider) => match provider.provider.name() {
9292 "copilot" => IconName::Copilot,
9293 "supermaven" => IconName::Supermaven,
9294 _ => IconName::ZedPredict,
9295 },
9296 None => IconName::ZedPredict,
9297 }
9298 }
9299
9300 fn render_edit_prediction_cursor_popover(
9301 &self,
9302 min_width: Pixels,
9303 max_width: Pixels,
9304 cursor_point: Point,
9305 style: &EditorStyle,
9306 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9307 _window: &Window,
9308 cx: &mut Context<Editor>,
9309 ) -> Option<AnyElement> {
9310 let provider = self.edit_prediction_provider.as_ref()?;
9311 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9312
9313 let is_refreshing = provider.provider.is_refreshing(cx);
9314
9315 fn pending_completion_container(icon: IconName) -> Div {
9316 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9317 }
9318
9319 let completion = match &self.active_edit_prediction {
9320 Some(prediction) => {
9321 if !self.has_visible_completions_menu() {
9322 const RADIUS: Pixels = px(6.);
9323 const BORDER_WIDTH: Pixels = px(1.);
9324
9325 return Some(
9326 h_flex()
9327 .elevation_2(cx)
9328 .border(BORDER_WIDTH)
9329 .border_color(cx.theme().colors().border)
9330 .when(accept_keystroke.is_none(), |el| {
9331 el.border_color(cx.theme().status().error)
9332 })
9333 .rounded(RADIUS)
9334 .rounded_tl(px(0.))
9335 .overflow_hidden()
9336 .child(div().px_1p5().child(match &prediction.completion {
9337 EditPrediction::MoveWithin { target, snapshot } => {
9338 use text::ToPoint as _;
9339 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9340 {
9341 Icon::new(IconName::ZedPredictDown)
9342 } else {
9343 Icon::new(IconName::ZedPredictUp)
9344 }
9345 }
9346 EditPrediction::MoveOutside { .. } => {
9347 // TODO [zeta2] custom icon for external jump?
9348 Icon::new(provider_icon)
9349 }
9350 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9351 }))
9352 .child(
9353 h_flex()
9354 .gap_1()
9355 .py_1()
9356 .px_2()
9357 .rounded_r(RADIUS - BORDER_WIDTH)
9358 .border_l_1()
9359 .border_color(cx.theme().colors().border)
9360 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9361 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9362 el.child(
9363 Label::new("Hold")
9364 .size(LabelSize::Small)
9365 .when(accept_keystroke.is_none(), |el| {
9366 el.strikethrough()
9367 })
9368 .line_height_style(LineHeightStyle::UiLabel),
9369 )
9370 })
9371 .id("edit_prediction_cursor_popover_keybind")
9372 .when(accept_keystroke.is_none(), |el| {
9373 let status_colors = cx.theme().status();
9374
9375 el.bg(status_colors.error_background)
9376 .border_color(status_colors.error.opacity(0.6))
9377 .child(Icon::new(IconName::Info).color(Color::Error))
9378 .cursor_default()
9379 .hoverable_tooltip(move |_window, cx| {
9380 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9381 .into()
9382 })
9383 })
9384 .when_some(
9385 accept_keystroke.as_ref(),
9386 |el, accept_keystroke| {
9387 el.child(h_flex().children(ui::render_modifiers(
9388 accept_keystroke.modifiers(),
9389 PlatformStyle::platform(),
9390 Some(Color::Default),
9391 Some(IconSize::XSmall.rems().into()),
9392 false,
9393 )))
9394 },
9395 ),
9396 )
9397 .into_any(),
9398 );
9399 }
9400
9401 self.render_edit_prediction_cursor_popover_preview(
9402 prediction,
9403 cursor_point,
9404 style,
9405 cx,
9406 )?
9407 }
9408
9409 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9410 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9411 stale_completion,
9412 cursor_point,
9413 style,
9414 cx,
9415 )?,
9416
9417 None => pending_completion_container(provider_icon)
9418 .child(Label::new("...").size(LabelSize::Small)),
9419 },
9420
9421 None => pending_completion_container(provider_icon)
9422 .child(Label::new("...").size(LabelSize::Small)),
9423 };
9424
9425 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9426 completion
9427 .with_animation(
9428 "loading-completion",
9429 Animation::new(Duration::from_secs(2))
9430 .repeat()
9431 .with_easing(pulsating_between(0.4, 0.8)),
9432 |label, delta| label.opacity(delta),
9433 )
9434 .into_any_element()
9435 } else {
9436 completion.into_any_element()
9437 };
9438
9439 let has_completion = self.active_edit_prediction.is_some();
9440
9441 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9442 Some(
9443 h_flex()
9444 .min_w(min_width)
9445 .max_w(max_width)
9446 .flex_1()
9447 .elevation_2(cx)
9448 .border_color(cx.theme().colors().border)
9449 .child(
9450 div()
9451 .flex_1()
9452 .py_1()
9453 .px_2()
9454 .overflow_hidden()
9455 .child(completion),
9456 )
9457 .when_some(accept_keystroke, |el, accept_keystroke| {
9458 if !accept_keystroke.modifiers().modified() {
9459 return el;
9460 }
9461
9462 el.child(
9463 h_flex()
9464 .h_full()
9465 .border_l_1()
9466 .rounded_r_lg()
9467 .border_color(cx.theme().colors().border)
9468 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9469 .gap_1()
9470 .py_1()
9471 .px_2()
9472 .child(
9473 h_flex()
9474 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9475 .when(is_platform_style_mac, |parent| parent.gap_1())
9476 .child(h_flex().children(ui::render_modifiers(
9477 accept_keystroke.modifiers(),
9478 PlatformStyle::platform(),
9479 Some(if !has_completion {
9480 Color::Muted
9481 } else {
9482 Color::Default
9483 }),
9484 None,
9485 false,
9486 ))),
9487 )
9488 .child(Label::new("Preview").into_any_element())
9489 .opacity(if has_completion { 1.0 } else { 0.4 }),
9490 )
9491 })
9492 .into_any(),
9493 )
9494 }
9495
9496 fn render_edit_prediction_cursor_popover_preview(
9497 &self,
9498 completion: &EditPredictionState,
9499 cursor_point: Point,
9500 style: &EditorStyle,
9501 cx: &mut Context<Editor>,
9502 ) -> Option<Div> {
9503 use text::ToPoint as _;
9504
9505 fn render_relative_row_jump(
9506 prefix: impl Into<String>,
9507 current_row: u32,
9508 target_row: u32,
9509 ) -> Div {
9510 let (row_diff, arrow) = if target_row < current_row {
9511 (current_row - target_row, IconName::ArrowUp)
9512 } else {
9513 (target_row - current_row, IconName::ArrowDown)
9514 };
9515
9516 h_flex()
9517 .child(
9518 Label::new(format!("{}{}", prefix.into(), row_diff))
9519 .color(Color::Muted)
9520 .size(LabelSize::Small),
9521 )
9522 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9523 }
9524
9525 let supports_jump = self
9526 .edit_prediction_provider
9527 .as_ref()
9528 .map(|provider| provider.provider.supports_jump_to_edit())
9529 .unwrap_or(true);
9530
9531 match &completion.completion {
9532 EditPrediction::MoveWithin {
9533 target, snapshot, ..
9534 } => {
9535 if !supports_jump {
9536 return None;
9537 }
9538
9539 Some(
9540 h_flex()
9541 .px_2()
9542 .gap_2()
9543 .flex_1()
9544 .child(
9545 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9546 Icon::new(IconName::ZedPredictDown)
9547 } else {
9548 Icon::new(IconName::ZedPredictUp)
9549 },
9550 )
9551 .child(Label::new("Jump to Edit")),
9552 )
9553 }
9554 EditPrediction::MoveOutside { snapshot, .. } => {
9555 let file_name = snapshot
9556 .file()
9557 .map(|file| file.file_name(cx))
9558 .unwrap_or("untitled");
9559 Some(
9560 h_flex()
9561 .px_2()
9562 .gap_2()
9563 .flex_1()
9564 .child(Icon::new(IconName::ZedPredict))
9565 .child(Label::new(format!("Jump to {file_name}"))),
9566 )
9567 }
9568 EditPrediction::Edit {
9569 edits,
9570 edit_preview,
9571 snapshot,
9572 display_mode: _,
9573 } => {
9574 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9575
9576 let (highlighted_edits, has_more_lines) =
9577 if let Some(edit_preview) = edit_preview.as_ref() {
9578 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9579 .first_line_preview()
9580 } else {
9581 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9582 };
9583
9584 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9585 .with_default_highlights(&style.text, highlighted_edits.highlights);
9586
9587 let preview = h_flex()
9588 .gap_1()
9589 .min_w_16()
9590 .child(styled_text)
9591 .when(has_more_lines, |parent| parent.child("…"));
9592
9593 let left = if supports_jump && first_edit_row != cursor_point.row {
9594 render_relative_row_jump("", cursor_point.row, first_edit_row)
9595 .into_any_element()
9596 } else {
9597 let icon_name =
9598 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9599 Icon::new(icon_name).into_any_element()
9600 };
9601
9602 Some(
9603 h_flex()
9604 .h_full()
9605 .flex_1()
9606 .gap_2()
9607 .pr_1()
9608 .overflow_x_hidden()
9609 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9610 .child(left)
9611 .child(preview),
9612 )
9613 }
9614 }
9615 }
9616
9617 pub fn render_context_menu(
9618 &self,
9619 style: &EditorStyle,
9620 max_height_in_lines: u32,
9621 window: &mut Window,
9622 cx: &mut Context<Editor>,
9623 ) -> Option<AnyElement> {
9624 let menu = self.context_menu.borrow();
9625 let menu = menu.as_ref()?;
9626 if !menu.visible() {
9627 return None;
9628 };
9629 Some(menu.render(style, max_height_in_lines, window, cx))
9630 }
9631
9632 fn render_context_menu_aside(
9633 &mut self,
9634 max_size: Size<Pixels>,
9635 window: &mut Window,
9636 cx: &mut Context<Editor>,
9637 ) -> Option<AnyElement> {
9638 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9639 if menu.visible() {
9640 menu.render_aside(max_size, window, cx)
9641 } else {
9642 None
9643 }
9644 })
9645 }
9646
9647 fn hide_context_menu(
9648 &mut self,
9649 window: &mut Window,
9650 cx: &mut Context<Self>,
9651 ) -> Option<CodeContextMenu> {
9652 cx.notify();
9653 self.completion_tasks.clear();
9654 let context_menu = self.context_menu.borrow_mut().take();
9655 self.stale_edit_prediction_in_menu.take();
9656 self.update_visible_edit_prediction(window, cx);
9657 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9658 && let Some(completion_provider) = &self.completion_provider
9659 {
9660 completion_provider.selection_changed(None, window, cx);
9661 }
9662 context_menu
9663 }
9664
9665 fn show_snippet_choices(
9666 &mut self,
9667 choices: &Vec<String>,
9668 selection: Range<Anchor>,
9669 cx: &mut Context<Self>,
9670 ) {
9671 let Some((_, buffer, _)) = self
9672 .buffer()
9673 .read(cx)
9674 .excerpt_containing(selection.start, cx)
9675 else {
9676 return;
9677 };
9678 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9679 else {
9680 return;
9681 };
9682 if buffer != end_buffer {
9683 log::error!("expected anchor range to have matching buffer IDs");
9684 return;
9685 }
9686
9687 let id = post_inc(&mut self.next_completion_id);
9688 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9689 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9690 CompletionsMenu::new_snippet_choices(
9691 id,
9692 true,
9693 choices,
9694 selection,
9695 buffer,
9696 snippet_sort_order,
9697 ),
9698 ));
9699 }
9700
9701 pub fn insert_snippet(
9702 &mut self,
9703 insertion_ranges: &[Range<usize>],
9704 snippet: Snippet,
9705 window: &mut Window,
9706 cx: &mut Context<Self>,
9707 ) -> Result<()> {
9708 struct Tabstop<T> {
9709 is_end_tabstop: bool,
9710 ranges: Vec<Range<T>>,
9711 choices: Option<Vec<String>>,
9712 }
9713
9714 let tabstops = self.buffer.update(cx, |buffer, cx| {
9715 let snippet_text: Arc<str> = snippet.text.clone().into();
9716 let edits = insertion_ranges
9717 .iter()
9718 .cloned()
9719 .map(|range| (range, snippet_text.clone()));
9720 let autoindent_mode = AutoindentMode::Block {
9721 original_indent_columns: Vec::new(),
9722 };
9723 buffer.edit(edits, Some(autoindent_mode), cx);
9724
9725 let snapshot = &*buffer.read(cx);
9726 let snippet = &snippet;
9727 snippet
9728 .tabstops
9729 .iter()
9730 .map(|tabstop| {
9731 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9732 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9733 });
9734 let mut tabstop_ranges = tabstop
9735 .ranges
9736 .iter()
9737 .flat_map(|tabstop_range| {
9738 let mut delta = 0_isize;
9739 insertion_ranges.iter().map(move |insertion_range| {
9740 let insertion_start = insertion_range.start as isize + delta;
9741 delta +=
9742 snippet.text.len() as isize - insertion_range.len() as isize;
9743
9744 let start = ((insertion_start + tabstop_range.start) as usize)
9745 .min(snapshot.len());
9746 let end = ((insertion_start + tabstop_range.end) as usize)
9747 .min(snapshot.len());
9748 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9749 })
9750 })
9751 .collect::<Vec<_>>();
9752 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9753
9754 Tabstop {
9755 is_end_tabstop,
9756 ranges: tabstop_ranges,
9757 choices: tabstop.choices.clone(),
9758 }
9759 })
9760 .collect::<Vec<_>>()
9761 });
9762 if let Some(tabstop) = tabstops.first() {
9763 self.change_selections(Default::default(), window, cx, |s| {
9764 // Reverse order so that the first range is the newest created selection.
9765 // Completions will use it and autoscroll will prioritize it.
9766 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9767 });
9768
9769 if let Some(choices) = &tabstop.choices
9770 && let Some(selection) = tabstop.ranges.first()
9771 {
9772 self.show_snippet_choices(choices, selection.clone(), cx)
9773 }
9774
9775 // If we're already at the last tabstop and it's at the end of the snippet,
9776 // we're done, we don't need to keep the state around.
9777 if !tabstop.is_end_tabstop {
9778 let choices = tabstops
9779 .iter()
9780 .map(|tabstop| tabstop.choices.clone())
9781 .collect();
9782
9783 let ranges = tabstops
9784 .into_iter()
9785 .map(|tabstop| tabstop.ranges)
9786 .collect::<Vec<_>>();
9787
9788 self.snippet_stack.push(SnippetState {
9789 active_index: 0,
9790 ranges,
9791 choices,
9792 });
9793 }
9794
9795 // Check whether the just-entered snippet ends with an auto-closable bracket.
9796 if self.autoclose_regions.is_empty() {
9797 let snapshot = self.buffer.read(cx).snapshot(cx);
9798 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9799 let selection_head = selection.head();
9800 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9801 continue;
9802 };
9803
9804 let mut bracket_pair = None;
9805 let max_lookup_length = scope
9806 .brackets()
9807 .map(|(pair, _)| {
9808 pair.start
9809 .as_str()
9810 .chars()
9811 .count()
9812 .max(pair.end.as_str().chars().count())
9813 })
9814 .max();
9815 if let Some(max_lookup_length) = max_lookup_length {
9816 let next_text = snapshot
9817 .chars_at(selection_head)
9818 .take(max_lookup_length)
9819 .collect::<String>();
9820 let prev_text = snapshot
9821 .reversed_chars_at(selection_head)
9822 .take(max_lookup_length)
9823 .collect::<String>();
9824
9825 for (pair, enabled) in scope.brackets() {
9826 if enabled
9827 && pair.close
9828 && prev_text.starts_with(pair.start.as_str())
9829 && next_text.starts_with(pair.end.as_str())
9830 {
9831 bracket_pair = Some(pair.clone());
9832 break;
9833 }
9834 }
9835 }
9836
9837 if let Some(pair) = bracket_pair {
9838 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9839 let autoclose_enabled =
9840 self.use_autoclose && snapshot_settings.use_autoclose;
9841 if autoclose_enabled {
9842 let start = snapshot.anchor_after(selection_head);
9843 let end = snapshot.anchor_after(selection_head);
9844 self.autoclose_regions.push(AutocloseRegion {
9845 selection_id: selection.id,
9846 range: start..end,
9847 pair,
9848 });
9849 }
9850 }
9851 }
9852 }
9853 }
9854 Ok(())
9855 }
9856
9857 pub fn move_to_next_snippet_tabstop(
9858 &mut self,
9859 window: &mut Window,
9860 cx: &mut Context<Self>,
9861 ) -> bool {
9862 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9863 }
9864
9865 pub fn move_to_prev_snippet_tabstop(
9866 &mut self,
9867 window: &mut Window,
9868 cx: &mut Context<Self>,
9869 ) -> bool {
9870 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9871 }
9872
9873 pub fn move_to_snippet_tabstop(
9874 &mut self,
9875 bias: Bias,
9876 window: &mut Window,
9877 cx: &mut Context<Self>,
9878 ) -> bool {
9879 if let Some(mut snippet) = self.snippet_stack.pop() {
9880 match bias {
9881 Bias::Left => {
9882 if snippet.active_index > 0 {
9883 snippet.active_index -= 1;
9884 } else {
9885 self.snippet_stack.push(snippet);
9886 return false;
9887 }
9888 }
9889 Bias::Right => {
9890 if snippet.active_index + 1 < snippet.ranges.len() {
9891 snippet.active_index += 1;
9892 } else {
9893 self.snippet_stack.push(snippet);
9894 return false;
9895 }
9896 }
9897 }
9898 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9899 self.change_selections(Default::default(), window, cx, |s| {
9900 // Reverse order so that the first range is the newest created selection.
9901 // Completions will use it and autoscroll will prioritize it.
9902 s.select_ranges(current_ranges.iter().rev().cloned())
9903 });
9904
9905 if let Some(choices) = &snippet.choices[snippet.active_index]
9906 && let Some(selection) = current_ranges.first()
9907 {
9908 self.show_snippet_choices(choices, selection.clone(), cx);
9909 }
9910
9911 // If snippet state is not at the last tabstop, push it back on the stack
9912 if snippet.active_index + 1 < snippet.ranges.len() {
9913 self.snippet_stack.push(snippet);
9914 }
9915 return true;
9916 }
9917 }
9918
9919 false
9920 }
9921
9922 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9923 self.transact(window, cx, |this, window, cx| {
9924 this.select_all(&SelectAll, window, cx);
9925 this.insert("", window, cx);
9926 });
9927 }
9928
9929 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9930 if self.read_only(cx) {
9931 return;
9932 }
9933 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9934 self.transact(window, cx, |this, window, cx| {
9935 this.select_autoclose_pair(window, cx);
9936
9937 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9938
9939 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9940 if !this.linked_edit_ranges.is_empty() {
9941 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
9942 let snapshot = this.buffer.read(cx).snapshot(cx);
9943
9944 for selection in selections.iter() {
9945 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9946 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9947 if selection_start.buffer_id != selection_end.buffer_id {
9948 continue;
9949 }
9950 if let Some(ranges) =
9951 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9952 {
9953 for (buffer, entries) in ranges {
9954 linked_ranges.entry(buffer).or_default().extend(entries);
9955 }
9956 }
9957 }
9958 }
9959
9960 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
9961 for selection in &mut selections {
9962 if selection.is_empty() {
9963 let old_head = selection.head();
9964 let mut new_head =
9965 movement::left(&display_map, old_head.to_display_point(&display_map))
9966 .to_point(&display_map);
9967 if let Some((buffer, line_buffer_range)) = display_map
9968 .buffer_snapshot()
9969 .buffer_line_for_row(MultiBufferRow(old_head.row))
9970 {
9971 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9972 let indent_len = match indent_size.kind {
9973 IndentKind::Space => {
9974 buffer.settings_at(line_buffer_range.start, cx).tab_size
9975 }
9976 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9977 };
9978 if old_head.column <= indent_size.len && old_head.column > 0 {
9979 let indent_len = indent_len.get();
9980 new_head = cmp::min(
9981 new_head,
9982 MultiBufferPoint::new(
9983 old_head.row,
9984 ((old_head.column - 1) / indent_len) * indent_len,
9985 ),
9986 );
9987 }
9988 }
9989
9990 selection.set_head(new_head, SelectionGoal::None);
9991 }
9992 }
9993
9994 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9995 this.insert("", window, cx);
9996 let empty_str: Arc<str> = Arc::from("");
9997 for (buffer, edits) in linked_ranges {
9998 let snapshot = buffer.read(cx).snapshot();
9999 use text::ToPoint as TP;
10000
10001 let edits = edits
10002 .into_iter()
10003 .map(|range| {
10004 let end_point = TP::to_point(&range.end, &snapshot);
10005 let mut start_point = TP::to_point(&range.start, &snapshot);
10006
10007 if end_point == start_point {
10008 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10009 .saturating_sub(1);
10010 start_point =
10011 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10012 };
10013
10014 (start_point..end_point, empty_str.clone())
10015 })
10016 .sorted_by_key(|(range, _)| range.start)
10017 .collect::<Vec<_>>();
10018 buffer.update(cx, |this, cx| {
10019 this.edit(edits, None, cx);
10020 })
10021 }
10022 this.refresh_edit_prediction(true, false, window, cx);
10023 refresh_linked_ranges(this, window, cx);
10024 });
10025 }
10026
10027 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10028 if self.read_only(cx) {
10029 return;
10030 }
10031 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10032 self.transact(window, cx, |this, window, cx| {
10033 this.change_selections(Default::default(), window, cx, |s| {
10034 s.move_with(|map, selection| {
10035 if selection.is_empty() {
10036 let cursor = movement::right(map, selection.head());
10037 selection.end = cursor;
10038 selection.reversed = true;
10039 selection.goal = SelectionGoal::None;
10040 }
10041 })
10042 });
10043 this.insert("", window, cx);
10044 this.refresh_edit_prediction(true, false, window, cx);
10045 });
10046 }
10047
10048 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10049 if self.mode.is_single_line() {
10050 cx.propagate();
10051 return;
10052 }
10053
10054 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10055 if self.move_to_prev_snippet_tabstop(window, cx) {
10056 return;
10057 }
10058 self.outdent(&Outdent, window, cx);
10059 }
10060
10061 pub fn next_snippet_tabstop(
10062 &mut self,
10063 _: &NextSnippetTabstop,
10064 window: &mut Window,
10065 cx: &mut Context<Self>,
10066 ) {
10067 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10068 cx.propagate();
10069 return;
10070 }
10071
10072 if self.move_to_next_snippet_tabstop(window, cx) {
10073 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10074 return;
10075 }
10076 cx.propagate();
10077 }
10078
10079 pub fn previous_snippet_tabstop(
10080 &mut self,
10081 _: &PreviousSnippetTabstop,
10082 window: &mut Window,
10083 cx: &mut Context<Self>,
10084 ) {
10085 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10086 cx.propagate();
10087 return;
10088 }
10089
10090 if self.move_to_prev_snippet_tabstop(window, cx) {
10091 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10092 return;
10093 }
10094 cx.propagate();
10095 }
10096
10097 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10098 if self.mode.is_single_line() {
10099 cx.propagate();
10100 return;
10101 }
10102
10103 if self.move_to_next_snippet_tabstop(window, cx) {
10104 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10105 return;
10106 }
10107 if self.read_only(cx) {
10108 return;
10109 }
10110 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10111 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10112 let buffer = self.buffer.read(cx);
10113 let snapshot = buffer.snapshot(cx);
10114 let rows_iter = selections.iter().map(|s| s.head().row);
10115 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10116
10117 let has_some_cursor_in_whitespace = selections
10118 .iter()
10119 .filter(|selection| selection.is_empty())
10120 .any(|selection| {
10121 let cursor = selection.head();
10122 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10123 cursor.column < current_indent.len
10124 });
10125
10126 let mut edits = Vec::new();
10127 let mut prev_edited_row = 0;
10128 let mut row_delta = 0;
10129 for selection in &mut selections {
10130 if selection.start.row != prev_edited_row {
10131 row_delta = 0;
10132 }
10133 prev_edited_row = selection.end.row;
10134
10135 // If the selection is non-empty, then increase the indentation of the selected lines.
10136 if !selection.is_empty() {
10137 row_delta =
10138 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10139 continue;
10140 }
10141
10142 let cursor = selection.head();
10143 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10144 if let Some(suggested_indent) =
10145 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10146 {
10147 // Don't do anything if already at suggested indent
10148 // and there is any other cursor which is not
10149 if has_some_cursor_in_whitespace
10150 && cursor.column == current_indent.len
10151 && current_indent.len == suggested_indent.len
10152 {
10153 continue;
10154 }
10155
10156 // Adjust line and move cursor to suggested indent
10157 // if cursor is not at suggested indent
10158 if cursor.column < suggested_indent.len
10159 && cursor.column <= current_indent.len
10160 && current_indent.len <= suggested_indent.len
10161 {
10162 selection.start = Point::new(cursor.row, suggested_indent.len);
10163 selection.end = selection.start;
10164 if row_delta == 0 {
10165 edits.extend(Buffer::edit_for_indent_size_adjustment(
10166 cursor.row,
10167 current_indent,
10168 suggested_indent,
10169 ));
10170 row_delta = suggested_indent.len - current_indent.len;
10171 }
10172 continue;
10173 }
10174
10175 // If current indent is more than suggested indent
10176 // only move cursor to current indent and skip indent
10177 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10178 selection.start = Point::new(cursor.row, current_indent.len);
10179 selection.end = selection.start;
10180 continue;
10181 }
10182 }
10183
10184 // Otherwise, insert a hard or soft tab.
10185 let settings = buffer.language_settings_at(cursor, cx);
10186 let tab_size = if settings.hard_tabs {
10187 IndentSize::tab()
10188 } else {
10189 let tab_size = settings.tab_size.get();
10190 let indent_remainder = snapshot
10191 .text_for_range(Point::new(cursor.row, 0)..cursor)
10192 .flat_map(str::chars)
10193 .fold(row_delta % tab_size, |counter: u32, c| {
10194 if c == '\t' {
10195 0
10196 } else {
10197 (counter + 1) % tab_size
10198 }
10199 });
10200
10201 let chars_to_next_tab_stop = tab_size - indent_remainder;
10202 IndentSize::spaces(chars_to_next_tab_stop)
10203 };
10204 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10205 selection.end = selection.start;
10206 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10207 row_delta += tab_size.len;
10208 }
10209
10210 self.transact(window, cx, |this, window, cx| {
10211 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10212 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10213 this.refresh_edit_prediction(true, false, window, cx);
10214 });
10215 }
10216
10217 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10218 if self.read_only(cx) {
10219 return;
10220 }
10221 if self.mode.is_single_line() {
10222 cx.propagate();
10223 return;
10224 }
10225
10226 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10227 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10228 let mut prev_edited_row = 0;
10229 let mut row_delta = 0;
10230 let mut edits = Vec::new();
10231 let buffer = self.buffer.read(cx);
10232 let snapshot = buffer.snapshot(cx);
10233 for selection in &mut selections {
10234 if selection.start.row != prev_edited_row {
10235 row_delta = 0;
10236 }
10237 prev_edited_row = selection.end.row;
10238
10239 row_delta =
10240 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10241 }
10242
10243 self.transact(window, cx, |this, window, cx| {
10244 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10245 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10246 });
10247 }
10248
10249 fn indent_selection(
10250 buffer: &MultiBuffer,
10251 snapshot: &MultiBufferSnapshot,
10252 selection: &mut Selection<Point>,
10253 edits: &mut Vec<(Range<Point>, String)>,
10254 delta_for_start_row: u32,
10255 cx: &App,
10256 ) -> u32 {
10257 let settings = buffer.language_settings_at(selection.start, cx);
10258 let tab_size = settings.tab_size.get();
10259 let indent_kind = if settings.hard_tabs {
10260 IndentKind::Tab
10261 } else {
10262 IndentKind::Space
10263 };
10264 let mut start_row = selection.start.row;
10265 let mut end_row = selection.end.row + 1;
10266
10267 // If a selection ends at the beginning of a line, don't indent
10268 // that last line.
10269 if selection.end.column == 0 && selection.end.row > selection.start.row {
10270 end_row -= 1;
10271 }
10272
10273 // Avoid re-indenting a row that has already been indented by a
10274 // previous selection, but still update this selection's column
10275 // to reflect that indentation.
10276 if delta_for_start_row > 0 {
10277 start_row += 1;
10278 selection.start.column += delta_for_start_row;
10279 if selection.end.row == selection.start.row {
10280 selection.end.column += delta_for_start_row;
10281 }
10282 }
10283
10284 let mut delta_for_end_row = 0;
10285 let has_multiple_rows = start_row + 1 != end_row;
10286 for row in start_row..end_row {
10287 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10288 let indent_delta = match (current_indent.kind, indent_kind) {
10289 (IndentKind::Space, IndentKind::Space) => {
10290 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10291 IndentSize::spaces(columns_to_next_tab_stop)
10292 }
10293 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10294 (_, IndentKind::Tab) => IndentSize::tab(),
10295 };
10296
10297 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10298 0
10299 } else {
10300 selection.start.column
10301 };
10302 let row_start = Point::new(row, start);
10303 edits.push((
10304 row_start..row_start,
10305 indent_delta.chars().collect::<String>(),
10306 ));
10307
10308 // Update this selection's endpoints to reflect the indentation.
10309 if row == selection.start.row {
10310 selection.start.column += indent_delta.len;
10311 }
10312 if row == selection.end.row {
10313 selection.end.column += indent_delta.len;
10314 delta_for_end_row = indent_delta.len;
10315 }
10316 }
10317
10318 if selection.start.row == selection.end.row {
10319 delta_for_start_row + delta_for_end_row
10320 } else {
10321 delta_for_end_row
10322 }
10323 }
10324
10325 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10326 if self.read_only(cx) {
10327 return;
10328 }
10329 if self.mode.is_single_line() {
10330 cx.propagate();
10331 return;
10332 }
10333
10334 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10335 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10336 let selections = self.selections.all::<Point>(&display_map);
10337 let mut deletion_ranges = Vec::new();
10338 let mut last_outdent = None;
10339 {
10340 let buffer = self.buffer.read(cx);
10341 let snapshot = buffer.snapshot(cx);
10342 for selection in &selections {
10343 let settings = buffer.language_settings_at(selection.start, cx);
10344 let tab_size = settings.tab_size.get();
10345 let mut rows = selection.spanned_rows(false, &display_map);
10346
10347 // Avoid re-outdenting a row that has already been outdented by a
10348 // previous selection.
10349 if let Some(last_row) = last_outdent
10350 && last_row == rows.start
10351 {
10352 rows.start = rows.start.next_row();
10353 }
10354 let has_multiple_rows = rows.len() > 1;
10355 for row in rows.iter_rows() {
10356 let indent_size = snapshot.indent_size_for_line(row);
10357 if indent_size.len > 0 {
10358 let deletion_len = match indent_size.kind {
10359 IndentKind::Space => {
10360 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10361 if columns_to_prev_tab_stop == 0 {
10362 tab_size
10363 } else {
10364 columns_to_prev_tab_stop
10365 }
10366 }
10367 IndentKind::Tab => 1,
10368 };
10369 let start = if has_multiple_rows
10370 || deletion_len > selection.start.column
10371 || indent_size.len < selection.start.column
10372 {
10373 0
10374 } else {
10375 selection.start.column - deletion_len
10376 };
10377 deletion_ranges.push(
10378 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10379 );
10380 last_outdent = Some(row);
10381 }
10382 }
10383 }
10384 }
10385
10386 self.transact(window, cx, |this, window, cx| {
10387 this.buffer.update(cx, |buffer, cx| {
10388 let empty_str: Arc<str> = Arc::default();
10389 buffer.edit(
10390 deletion_ranges
10391 .into_iter()
10392 .map(|range| (range, empty_str.clone())),
10393 None,
10394 cx,
10395 );
10396 });
10397 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10398 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10399 });
10400 }
10401
10402 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10403 if self.read_only(cx) {
10404 return;
10405 }
10406 if self.mode.is_single_line() {
10407 cx.propagate();
10408 return;
10409 }
10410
10411 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10412 let selections = self
10413 .selections
10414 .all::<usize>(&self.display_snapshot(cx))
10415 .into_iter()
10416 .map(|s| s.range());
10417
10418 self.transact(window, cx, |this, window, cx| {
10419 this.buffer.update(cx, |buffer, cx| {
10420 buffer.autoindent_ranges(selections, cx);
10421 });
10422 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10423 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10424 });
10425 }
10426
10427 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10428 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10429 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10430 let selections = self.selections.all::<Point>(&display_map);
10431
10432 let mut new_cursors = Vec::new();
10433 let mut edit_ranges = Vec::new();
10434 let mut selections = selections.iter().peekable();
10435 while let Some(selection) = selections.next() {
10436 let mut rows = selection.spanned_rows(false, &display_map);
10437
10438 // Accumulate contiguous regions of rows that we want to delete.
10439 while let Some(next_selection) = selections.peek() {
10440 let next_rows = next_selection.spanned_rows(false, &display_map);
10441 if next_rows.start <= rows.end {
10442 rows.end = next_rows.end;
10443 selections.next().unwrap();
10444 } else {
10445 break;
10446 }
10447 }
10448
10449 let buffer = display_map.buffer_snapshot();
10450 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10451 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10452 // If there's a line after the range, delete the \n from the end of the row range
10453 (
10454 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10455 rows.end,
10456 )
10457 } else {
10458 // If there isn't a line after the range, delete the \n from the line before the
10459 // start of the row range
10460 edit_start = edit_start.saturating_sub(1);
10461 (buffer.len(), rows.start.previous_row())
10462 };
10463
10464 let text_layout_details = self.text_layout_details(window);
10465 let x = display_map.x_for_display_point(
10466 selection.head().to_display_point(&display_map),
10467 &text_layout_details,
10468 );
10469 let row = Point::new(target_row.0, 0)
10470 .to_display_point(&display_map)
10471 .row();
10472 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10473
10474 new_cursors.push((
10475 selection.id,
10476 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10477 SelectionGoal::None,
10478 ));
10479 edit_ranges.push(edit_start..edit_end);
10480 }
10481
10482 self.transact(window, cx, |this, window, cx| {
10483 let buffer = this.buffer.update(cx, |buffer, cx| {
10484 let empty_str: Arc<str> = Arc::default();
10485 buffer.edit(
10486 edit_ranges
10487 .into_iter()
10488 .map(|range| (range, empty_str.clone())),
10489 None,
10490 cx,
10491 );
10492 buffer.snapshot(cx)
10493 });
10494 let new_selections = new_cursors
10495 .into_iter()
10496 .map(|(id, cursor, goal)| {
10497 let cursor = cursor.to_point(&buffer);
10498 Selection {
10499 id,
10500 start: cursor,
10501 end: cursor,
10502 reversed: false,
10503 goal,
10504 }
10505 })
10506 .collect();
10507
10508 this.change_selections(Default::default(), window, cx, |s| {
10509 s.select(new_selections);
10510 });
10511 });
10512 }
10513
10514 pub fn join_lines_impl(
10515 &mut self,
10516 insert_whitespace: bool,
10517 window: &mut Window,
10518 cx: &mut Context<Self>,
10519 ) {
10520 if self.read_only(cx) {
10521 return;
10522 }
10523 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10524 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10525 let start = MultiBufferRow(selection.start.row);
10526 // Treat single line selections as if they include the next line. Otherwise this action
10527 // would do nothing for single line selections individual cursors.
10528 let end = if selection.start.row == selection.end.row {
10529 MultiBufferRow(selection.start.row + 1)
10530 } else {
10531 MultiBufferRow(selection.end.row)
10532 };
10533
10534 if let Some(last_row_range) = row_ranges.last_mut()
10535 && start <= last_row_range.end
10536 {
10537 last_row_range.end = end;
10538 continue;
10539 }
10540 row_ranges.push(start..end);
10541 }
10542
10543 let snapshot = self.buffer.read(cx).snapshot(cx);
10544 let mut cursor_positions = Vec::new();
10545 for row_range in &row_ranges {
10546 let anchor = snapshot.anchor_before(Point::new(
10547 row_range.end.previous_row().0,
10548 snapshot.line_len(row_range.end.previous_row()),
10549 ));
10550 cursor_positions.push(anchor..anchor);
10551 }
10552
10553 self.transact(window, cx, |this, window, cx| {
10554 for row_range in row_ranges.into_iter().rev() {
10555 for row in row_range.iter_rows().rev() {
10556 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10557 let next_line_row = row.next_row();
10558 let indent = snapshot.indent_size_for_line(next_line_row);
10559 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10560
10561 let replace =
10562 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10563 " "
10564 } else {
10565 ""
10566 };
10567
10568 this.buffer.update(cx, |buffer, cx| {
10569 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10570 });
10571 }
10572 }
10573
10574 this.change_selections(Default::default(), window, cx, |s| {
10575 s.select_anchor_ranges(cursor_positions)
10576 });
10577 });
10578 }
10579
10580 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10581 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10582 self.join_lines_impl(true, window, cx);
10583 }
10584
10585 pub fn sort_lines_case_sensitive(
10586 &mut self,
10587 _: &SortLinesCaseSensitive,
10588 window: &mut Window,
10589 cx: &mut Context<Self>,
10590 ) {
10591 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10592 }
10593
10594 pub fn sort_lines_by_length(
10595 &mut self,
10596 _: &SortLinesByLength,
10597 window: &mut Window,
10598 cx: &mut Context<Self>,
10599 ) {
10600 self.manipulate_immutable_lines(window, cx, |lines| {
10601 lines.sort_by_key(|&line| line.chars().count())
10602 })
10603 }
10604
10605 pub fn sort_lines_case_insensitive(
10606 &mut self,
10607 _: &SortLinesCaseInsensitive,
10608 window: &mut Window,
10609 cx: &mut Context<Self>,
10610 ) {
10611 self.manipulate_immutable_lines(window, cx, |lines| {
10612 lines.sort_by_key(|line| line.to_lowercase())
10613 })
10614 }
10615
10616 pub fn unique_lines_case_insensitive(
10617 &mut self,
10618 _: &UniqueLinesCaseInsensitive,
10619 window: &mut Window,
10620 cx: &mut Context<Self>,
10621 ) {
10622 self.manipulate_immutable_lines(window, cx, |lines| {
10623 let mut seen = HashSet::default();
10624 lines.retain(|line| seen.insert(line.to_lowercase()));
10625 })
10626 }
10627
10628 pub fn unique_lines_case_sensitive(
10629 &mut self,
10630 _: &UniqueLinesCaseSensitive,
10631 window: &mut Window,
10632 cx: &mut Context<Self>,
10633 ) {
10634 self.manipulate_immutable_lines(window, cx, |lines| {
10635 let mut seen = HashSet::default();
10636 lines.retain(|line| seen.insert(*line));
10637 })
10638 }
10639
10640 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10641 let snapshot = self.buffer.read(cx).snapshot(cx);
10642 for selection in self.selections.disjoint_anchors_arc().iter() {
10643 if snapshot
10644 .language_at(selection.start)
10645 .and_then(|lang| lang.config().wrap_characters.as_ref())
10646 .is_some()
10647 {
10648 return true;
10649 }
10650 }
10651 false
10652 }
10653
10654 fn wrap_selections_in_tag(
10655 &mut self,
10656 _: &WrapSelectionsInTag,
10657 window: &mut Window,
10658 cx: &mut Context<Self>,
10659 ) {
10660 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10661
10662 let snapshot = self.buffer.read(cx).snapshot(cx);
10663
10664 let mut edits = Vec::new();
10665 let mut boundaries = Vec::new();
10666
10667 for selection in self
10668 .selections
10669 .all_adjusted(&self.display_snapshot(cx))
10670 .iter()
10671 {
10672 let Some(wrap_config) = snapshot
10673 .language_at(selection.start)
10674 .and_then(|lang| lang.config().wrap_characters.clone())
10675 else {
10676 continue;
10677 };
10678
10679 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10680 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10681
10682 let start_before = snapshot.anchor_before(selection.start);
10683 let end_after = snapshot.anchor_after(selection.end);
10684
10685 edits.push((start_before..start_before, open_tag));
10686 edits.push((end_after..end_after, close_tag));
10687
10688 boundaries.push((
10689 start_before,
10690 end_after,
10691 wrap_config.start_prefix.len(),
10692 wrap_config.end_suffix.len(),
10693 ));
10694 }
10695
10696 if edits.is_empty() {
10697 return;
10698 }
10699
10700 self.transact(window, cx, |this, window, cx| {
10701 let buffer = this.buffer.update(cx, |buffer, cx| {
10702 buffer.edit(edits, None, cx);
10703 buffer.snapshot(cx)
10704 });
10705
10706 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10707 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10708 boundaries.into_iter()
10709 {
10710 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10711 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10712 new_selections.push(open_offset..open_offset);
10713 new_selections.push(close_offset..close_offset);
10714 }
10715
10716 this.change_selections(Default::default(), window, cx, |s| {
10717 s.select_ranges(new_selections);
10718 });
10719
10720 this.request_autoscroll(Autoscroll::fit(), cx);
10721 });
10722 }
10723
10724 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10725 let Some(project) = self.project.clone() else {
10726 return;
10727 };
10728 self.reload(project, window, cx)
10729 .detach_and_notify_err(window, cx);
10730 }
10731
10732 pub fn restore_file(
10733 &mut self,
10734 _: &::git::RestoreFile,
10735 window: &mut Window,
10736 cx: &mut Context<Self>,
10737 ) {
10738 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10739 let mut buffer_ids = HashSet::default();
10740 let snapshot = self.buffer().read(cx).snapshot(cx);
10741 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10742 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10743 }
10744
10745 let buffer = self.buffer().read(cx);
10746 let ranges = buffer_ids
10747 .into_iter()
10748 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10749 .collect::<Vec<_>>();
10750
10751 self.restore_hunks_in_ranges(ranges, window, cx);
10752 }
10753
10754 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10755 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10756 let selections = self
10757 .selections
10758 .all(&self.display_snapshot(cx))
10759 .into_iter()
10760 .map(|s| s.range())
10761 .collect();
10762 self.restore_hunks_in_ranges(selections, window, cx);
10763 }
10764
10765 pub fn restore_hunks_in_ranges(
10766 &mut self,
10767 ranges: Vec<Range<Point>>,
10768 window: &mut Window,
10769 cx: &mut Context<Editor>,
10770 ) {
10771 let mut revert_changes = HashMap::default();
10772 let chunk_by = self
10773 .snapshot(window, cx)
10774 .hunks_for_ranges(ranges)
10775 .into_iter()
10776 .chunk_by(|hunk| hunk.buffer_id);
10777 for (buffer_id, hunks) in &chunk_by {
10778 let hunks = hunks.collect::<Vec<_>>();
10779 for hunk in &hunks {
10780 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10781 }
10782 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10783 }
10784 drop(chunk_by);
10785 if !revert_changes.is_empty() {
10786 self.transact(window, cx, |editor, window, cx| {
10787 editor.restore(revert_changes, window, cx);
10788 });
10789 }
10790 }
10791
10792 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10793 if let Some(status) = self
10794 .addons
10795 .iter()
10796 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10797 {
10798 return Some(status);
10799 }
10800 self.project
10801 .as_ref()?
10802 .read(cx)
10803 .status_for_buffer_id(buffer_id, cx)
10804 }
10805
10806 pub fn open_active_item_in_terminal(
10807 &mut self,
10808 _: &OpenInTerminal,
10809 window: &mut Window,
10810 cx: &mut Context<Self>,
10811 ) {
10812 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10813 let project_path = buffer.read(cx).project_path(cx)?;
10814 let project = self.project()?.read(cx);
10815 let entry = project.entry_for_path(&project_path, cx)?;
10816 let parent = match &entry.canonical_path {
10817 Some(canonical_path) => canonical_path.to_path_buf(),
10818 None => project.absolute_path(&project_path, cx)?,
10819 }
10820 .parent()?
10821 .to_path_buf();
10822 Some(parent)
10823 }) {
10824 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10825 }
10826 }
10827
10828 fn set_breakpoint_context_menu(
10829 &mut self,
10830 display_row: DisplayRow,
10831 position: Option<Anchor>,
10832 clicked_point: gpui::Point<Pixels>,
10833 window: &mut Window,
10834 cx: &mut Context<Self>,
10835 ) {
10836 let source = self
10837 .buffer
10838 .read(cx)
10839 .snapshot(cx)
10840 .anchor_before(Point::new(display_row.0, 0u32));
10841
10842 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10843
10844 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10845 self,
10846 source,
10847 clicked_point,
10848 context_menu,
10849 window,
10850 cx,
10851 );
10852 }
10853
10854 fn add_edit_breakpoint_block(
10855 &mut self,
10856 anchor: Anchor,
10857 breakpoint: &Breakpoint,
10858 edit_action: BreakpointPromptEditAction,
10859 window: &mut Window,
10860 cx: &mut Context<Self>,
10861 ) {
10862 let weak_editor = cx.weak_entity();
10863 let bp_prompt = cx.new(|cx| {
10864 BreakpointPromptEditor::new(
10865 weak_editor,
10866 anchor,
10867 breakpoint.clone(),
10868 edit_action,
10869 window,
10870 cx,
10871 )
10872 });
10873
10874 let height = bp_prompt.update(cx, |this, cx| {
10875 this.prompt
10876 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10877 });
10878 let cloned_prompt = bp_prompt.clone();
10879 let blocks = vec![BlockProperties {
10880 style: BlockStyle::Sticky,
10881 placement: BlockPlacement::Above(anchor),
10882 height: Some(height),
10883 render: Arc::new(move |cx| {
10884 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10885 cloned_prompt.clone().into_any_element()
10886 }),
10887 priority: 0,
10888 }];
10889
10890 let focus_handle = bp_prompt.focus_handle(cx);
10891 window.focus(&focus_handle);
10892
10893 let block_ids = self.insert_blocks(blocks, None, cx);
10894 bp_prompt.update(cx, |prompt, _| {
10895 prompt.add_block_ids(block_ids);
10896 });
10897 }
10898
10899 pub(crate) fn breakpoint_at_row(
10900 &self,
10901 row: u32,
10902 window: &mut Window,
10903 cx: &mut Context<Self>,
10904 ) -> Option<(Anchor, Breakpoint)> {
10905 let snapshot = self.snapshot(window, cx);
10906 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10907
10908 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10909 }
10910
10911 pub(crate) fn breakpoint_at_anchor(
10912 &self,
10913 breakpoint_position: Anchor,
10914 snapshot: &EditorSnapshot,
10915 cx: &mut Context<Self>,
10916 ) -> Option<(Anchor, Breakpoint)> {
10917 let buffer = self
10918 .buffer
10919 .read(cx)
10920 .buffer_for_anchor(breakpoint_position, cx)?;
10921
10922 let enclosing_excerpt = breakpoint_position.excerpt_id;
10923 let buffer_snapshot = buffer.read(cx).snapshot();
10924
10925 let row = buffer_snapshot
10926 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10927 .row;
10928
10929 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10930 let anchor_end = snapshot
10931 .buffer_snapshot()
10932 .anchor_after(Point::new(row, line_len));
10933
10934 self.breakpoint_store
10935 .as_ref()?
10936 .read_with(cx, |breakpoint_store, cx| {
10937 breakpoint_store
10938 .breakpoints(
10939 &buffer,
10940 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10941 &buffer_snapshot,
10942 cx,
10943 )
10944 .next()
10945 .and_then(|(bp, _)| {
10946 let breakpoint_row = buffer_snapshot
10947 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10948 .row;
10949
10950 if breakpoint_row == row {
10951 snapshot
10952 .buffer_snapshot()
10953 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10954 .map(|position| (position, bp.bp.clone()))
10955 } else {
10956 None
10957 }
10958 })
10959 })
10960 }
10961
10962 pub fn edit_log_breakpoint(
10963 &mut self,
10964 _: &EditLogBreakpoint,
10965 window: &mut Window,
10966 cx: &mut Context<Self>,
10967 ) {
10968 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10969 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10970 message: None,
10971 state: BreakpointState::Enabled,
10972 condition: None,
10973 hit_condition: None,
10974 });
10975
10976 self.add_edit_breakpoint_block(
10977 anchor,
10978 &breakpoint,
10979 BreakpointPromptEditAction::Log,
10980 window,
10981 cx,
10982 );
10983 }
10984 }
10985
10986 fn breakpoints_at_cursors(
10987 &self,
10988 window: &mut Window,
10989 cx: &mut Context<Self>,
10990 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10991 let snapshot = self.snapshot(window, cx);
10992 let cursors = self
10993 .selections
10994 .disjoint_anchors_arc()
10995 .iter()
10996 .map(|selection| {
10997 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
10998
10999 let breakpoint_position = self
11000 .breakpoint_at_row(cursor_position.row, window, cx)
11001 .map(|bp| bp.0)
11002 .unwrap_or_else(|| {
11003 snapshot
11004 .display_snapshot
11005 .buffer_snapshot()
11006 .anchor_after(Point::new(cursor_position.row, 0))
11007 });
11008
11009 let breakpoint = self
11010 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11011 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11012
11013 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11014 })
11015 // 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.
11016 .collect::<HashMap<Anchor, _>>();
11017
11018 cursors.into_iter().collect()
11019 }
11020
11021 pub fn enable_breakpoint(
11022 &mut self,
11023 _: &crate::actions::EnableBreakpoint,
11024 window: &mut Window,
11025 cx: &mut Context<Self>,
11026 ) {
11027 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11028 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11029 continue;
11030 };
11031 self.edit_breakpoint_at_anchor(
11032 anchor,
11033 breakpoint,
11034 BreakpointEditAction::InvertState,
11035 cx,
11036 );
11037 }
11038 }
11039
11040 pub fn disable_breakpoint(
11041 &mut self,
11042 _: &crate::actions::DisableBreakpoint,
11043 window: &mut Window,
11044 cx: &mut Context<Self>,
11045 ) {
11046 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11047 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11048 continue;
11049 };
11050 self.edit_breakpoint_at_anchor(
11051 anchor,
11052 breakpoint,
11053 BreakpointEditAction::InvertState,
11054 cx,
11055 );
11056 }
11057 }
11058
11059 pub fn toggle_breakpoint(
11060 &mut self,
11061 _: &crate::actions::ToggleBreakpoint,
11062 window: &mut Window,
11063 cx: &mut Context<Self>,
11064 ) {
11065 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11066 if let Some(breakpoint) = breakpoint {
11067 self.edit_breakpoint_at_anchor(
11068 anchor,
11069 breakpoint,
11070 BreakpointEditAction::Toggle,
11071 cx,
11072 );
11073 } else {
11074 self.edit_breakpoint_at_anchor(
11075 anchor,
11076 Breakpoint::new_standard(),
11077 BreakpointEditAction::Toggle,
11078 cx,
11079 );
11080 }
11081 }
11082 }
11083
11084 pub fn edit_breakpoint_at_anchor(
11085 &mut self,
11086 breakpoint_position: Anchor,
11087 breakpoint: Breakpoint,
11088 edit_action: BreakpointEditAction,
11089 cx: &mut Context<Self>,
11090 ) {
11091 let Some(breakpoint_store) = &self.breakpoint_store else {
11092 return;
11093 };
11094
11095 let Some(buffer) = self
11096 .buffer
11097 .read(cx)
11098 .buffer_for_anchor(breakpoint_position, cx)
11099 else {
11100 return;
11101 };
11102
11103 breakpoint_store.update(cx, |breakpoint_store, cx| {
11104 breakpoint_store.toggle_breakpoint(
11105 buffer,
11106 BreakpointWithPosition {
11107 position: breakpoint_position.text_anchor,
11108 bp: breakpoint,
11109 },
11110 edit_action,
11111 cx,
11112 );
11113 });
11114
11115 cx.notify();
11116 }
11117
11118 #[cfg(any(test, feature = "test-support"))]
11119 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11120 self.breakpoint_store.clone()
11121 }
11122
11123 pub fn prepare_restore_change(
11124 &self,
11125 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11126 hunk: &MultiBufferDiffHunk,
11127 cx: &mut App,
11128 ) -> Option<()> {
11129 if hunk.is_created_file() {
11130 return None;
11131 }
11132 let buffer = self.buffer.read(cx);
11133 let diff = buffer.diff_for(hunk.buffer_id)?;
11134 let buffer = buffer.buffer(hunk.buffer_id)?;
11135 let buffer = buffer.read(cx);
11136 let original_text = diff
11137 .read(cx)
11138 .base_text()
11139 .as_rope()
11140 .slice(hunk.diff_base_byte_range.clone());
11141 let buffer_snapshot = buffer.snapshot();
11142 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11143 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11144 probe
11145 .0
11146 .start
11147 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11148 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11149 }) {
11150 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11151 Some(())
11152 } else {
11153 None
11154 }
11155 }
11156
11157 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11158 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11159 }
11160
11161 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11162 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11163 }
11164
11165 fn manipulate_lines<M>(
11166 &mut self,
11167 window: &mut Window,
11168 cx: &mut Context<Self>,
11169 mut manipulate: M,
11170 ) where
11171 M: FnMut(&str) -> LineManipulationResult,
11172 {
11173 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11174
11175 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11176 let buffer = self.buffer.read(cx).snapshot(cx);
11177
11178 let mut edits = Vec::new();
11179
11180 let selections = self.selections.all::<Point>(&display_map);
11181 let mut selections = selections.iter().peekable();
11182 let mut contiguous_row_selections = Vec::new();
11183 let mut new_selections = Vec::new();
11184 let mut added_lines = 0;
11185 let mut removed_lines = 0;
11186
11187 while let Some(selection) = selections.next() {
11188 let (start_row, end_row) = consume_contiguous_rows(
11189 &mut contiguous_row_selections,
11190 selection,
11191 &display_map,
11192 &mut selections,
11193 );
11194
11195 let start_point = Point::new(start_row.0, 0);
11196 let end_point = Point::new(
11197 end_row.previous_row().0,
11198 buffer.line_len(end_row.previous_row()),
11199 );
11200 let text = buffer
11201 .text_for_range(start_point..end_point)
11202 .collect::<String>();
11203
11204 let LineManipulationResult {
11205 new_text,
11206 line_count_before,
11207 line_count_after,
11208 } = manipulate(&text);
11209
11210 edits.push((start_point..end_point, new_text));
11211
11212 // Selections must change based on added and removed line count
11213 let start_row =
11214 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11215 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11216 new_selections.push(Selection {
11217 id: selection.id,
11218 start: start_row,
11219 end: end_row,
11220 goal: SelectionGoal::None,
11221 reversed: selection.reversed,
11222 });
11223
11224 if line_count_after > line_count_before {
11225 added_lines += line_count_after - line_count_before;
11226 } else if line_count_before > line_count_after {
11227 removed_lines += line_count_before - line_count_after;
11228 }
11229 }
11230
11231 self.transact(window, cx, |this, window, cx| {
11232 let buffer = this.buffer.update(cx, |buffer, cx| {
11233 buffer.edit(edits, None, cx);
11234 buffer.snapshot(cx)
11235 });
11236
11237 // Recalculate offsets on newly edited buffer
11238 let new_selections = new_selections
11239 .iter()
11240 .map(|s| {
11241 let start_point = Point::new(s.start.0, 0);
11242 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11243 Selection {
11244 id: s.id,
11245 start: buffer.point_to_offset(start_point),
11246 end: buffer.point_to_offset(end_point),
11247 goal: s.goal,
11248 reversed: s.reversed,
11249 }
11250 })
11251 .collect();
11252
11253 this.change_selections(Default::default(), window, cx, |s| {
11254 s.select(new_selections);
11255 });
11256
11257 this.request_autoscroll(Autoscroll::fit(), cx);
11258 });
11259 }
11260
11261 fn manipulate_immutable_lines<Fn>(
11262 &mut self,
11263 window: &mut Window,
11264 cx: &mut Context<Self>,
11265 mut callback: Fn,
11266 ) where
11267 Fn: FnMut(&mut Vec<&str>),
11268 {
11269 self.manipulate_lines(window, cx, |text| {
11270 let mut lines: Vec<&str> = text.split('\n').collect();
11271 let line_count_before = lines.len();
11272
11273 callback(&mut lines);
11274
11275 LineManipulationResult {
11276 new_text: lines.join("\n"),
11277 line_count_before,
11278 line_count_after: lines.len(),
11279 }
11280 });
11281 }
11282
11283 fn manipulate_mutable_lines<Fn>(
11284 &mut self,
11285 window: &mut Window,
11286 cx: &mut Context<Self>,
11287 mut callback: Fn,
11288 ) where
11289 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11290 {
11291 self.manipulate_lines(window, cx, |text| {
11292 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11293 let line_count_before = lines.len();
11294
11295 callback(&mut lines);
11296
11297 LineManipulationResult {
11298 new_text: lines.join("\n"),
11299 line_count_before,
11300 line_count_after: lines.len(),
11301 }
11302 });
11303 }
11304
11305 pub fn convert_indentation_to_spaces(
11306 &mut self,
11307 _: &ConvertIndentationToSpaces,
11308 window: &mut Window,
11309 cx: &mut Context<Self>,
11310 ) {
11311 let settings = self.buffer.read(cx).language_settings(cx);
11312 let tab_size = settings.tab_size.get() as usize;
11313
11314 self.manipulate_mutable_lines(window, cx, |lines| {
11315 // Allocates a reasonably sized scratch buffer once for the whole loop
11316 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11317 // Avoids recomputing spaces that could be inserted many times
11318 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11319 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11320 .collect();
11321
11322 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11323 let mut chars = line.as_ref().chars();
11324 let mut col = 0;
11325 let mut changed = false;
11326
11327 for ch in chars.by_ref() {
11328 match ch {
11329 ' ' => {
11330 reindented_line.push(' ');
11331 col += 1;
11332 }
11333 '\t' => {
11334 // \t are converted to spaces depending on the current column
11335 let spaces_len = tab_size - (col % tab_size);
11336 reindented_line.extend(&space_cache[spaces_len - 1]);
11337 col += spaces_len;
11338 changed = true;
11339 }
11340 _ => {
11341 // If we dont append before break, the character is consumed
11342 reindented_line.push(ch);
11343 break;
11344 }
11345 }
11346 }
11347
11348 if !changed {
11349 reindented_line.clear();
11350 continue;
11351 }
11352 // Append the rest of the line and replace old reference with new one
11353 reindented_line.extend(chars);
11354 *line = Cow::Owned(reindented_line.clone());
11355 reindented_line.clear();
11356 }
11357 });
11358 }
11359
11360 pub fn convert_indentation_to_tabs(
11361 &mut self,
11362 _: &ConvertIndentationToTabs,
11363 window: &mut Window,
11364 cx: &mut Context<Self>,
11365 ) {
11366 let settings = self.buffer.read(cx).language_settings(cx);
11367 let tab_size = settings.tab_size.get() as usize;
11368
11369 self.manipulate_mutable_lines(window, cx, |lines| {
11370 // Allocates a reasonably sized buffer once for the whole loop
11371 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11372 // Avoids recomputing spaces that could be inserted many times
11373 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11374 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11375 .collect();
11376
11377 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11378 let mut chars = line.chars();
11379 let mut spaces_count = 0;
11380 let mut first_non_indent_char = None;
11381 let mut changed = false;
11382
11383 for ch in chars.by_ref() {
11384 match ch {
11385 ' ' => {
11386 // Keep track of spaces. Append \t when we reach tab_size
11387 spaces_count += 1;
11388 changed = true;
11389 if spaces_count == tab_size {
11390 reindented_line.push('\t');
11391 spaces_count = 0;
11392 }
11393 }
11394 '\t' => {
11395 reindented_line.push('\t');
11396 spaces_count = 0;
11397 }
11398 _ => {
11399 // Dont append it yet, we might have remaining spaces
11400 first_non_indent_char = Some(ch);
11401 break;
11402 }
11403 }
11404 }
11405
11406 if !changed {
11407 reindented_line.clear();
11408 continue;
11409 }
11410 // Remaining spaces that didn't make a full tab stop
11411 if spaces_count > 0 {
11412 reindented_line.extend(&space_cache[spaces_count - 1]);
11413 }
11414 // If we consume an extra character that was not indentation, add it back
11415 if let Some(extra_char) = first_non_indent_char {
11416 reindented_line.push(extra_char);
11417 }
11418 // Append the rest of the line and replace old reference with new one
11419 reindented_line.extend(chars);
11420 *line = Cow::Owned(reindented_line.clone());
11421 reindented_line.clear();
11422 }
11423 });
11424 }
11425
11426 pub fn convert_to_upper_case(
11427 &mut self,
11428 _: &ConvertToUpperCase,
11429 window: &mut Window,
11430 cx: &mut Context<Self>,
11431 ) {
11432 self.manipulate_text(window, cx, |text| text.to_uppercase())
11433 }
11434
11435 pub fn convert_to_lower_case(
11436 &mut self,
11437 _: &ConvertToLowerCase,
11438 window: &mut Window,
11439 cx: &mut Context<Self>,
11440 ) {
11441 self.manipulate_text(window, cx, |text| text.to_lowercase())
11442 }
11443
11444 pub fn convert_to_title_case(
11445 &mut self,
11446 _: &ConvertToTitleCase,
11447 window: &mut Window,
11448 cx: &mut Context<Self>,
11449 ) {
11450 self.manipulate_text(window, cx, |text| {
11451 text.split('\n')
11452 .map(|line| line.to_case(Case::Title))
11453 .join("\n")
11454 })
11455 }
11456
11457 pub fn convert_to_snake_case(
11458 &mut self,
11459 _: &ConvertToSnakeCase,
11460 window: &mut Window,
11461 cx: &mut Context<Self>,
11462 ) {
11463 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11464 }
11465
11466 pub fn convert_to_kebab_case(
11467 &mut self,
11468 _: &ConvertToKebabCase,
11469 window: &mut Window,
11470 cx: &mut Context<Self>,
11471 ) {
11472 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11473 }
11474
11475 pub fn convert_to_upper_camel_case(
11476 &mut self,
11477 _: &ConvertToUpperCamelCase,
11478 window: &mut Window,
11479 cx: &mut Context<Self>,
11480 ) {
11481 self.manipulate_text(window, cx, |text| {
11482 text.split('\n')
11483 .map(|line| line.to_case(Case::UpperCamel))
11484 .join("\n")
11485 })
11486 }
11487
11488 pub fn convert_to_lower_camel_case(
11489 &mut self,
11490 _: &ConvertToLowerCamelCase,
11491 window: &mut Window,
11492 cx: &mut Context<Self>,
11493 ) {
11494 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11495 }
11496
11497 pub fn convert_to_opposite_case(
11498 &mut self,
11499 _: &ConvertToOppositeCase,
11500 window: &mut Window,
11501 cx: &mut Context<Self>,
11502 ) {
11503 self.manipulate_text(window, cx, |text| {
11504 text.chars()
11505 .fold(String::with_capacity(text.len()), |mut t, c| {
11506 if c.is_uppercase() {
11507 t.extend(c.to_lowercase());
11508 } else {
11509 t.extend(c.to_uppercase());
11510 }
11511 t
11512 })
11513 })
11514 }
11515
11516 pub fn convert_to_sentence_case(
11517 &mut self,
11518 _: &ConvertToSentenceCase,
11519 window: &mut Window,
11520 cx: &mut Context<Self>,
11521 ) {
11522 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11523 }
11524
11525 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11526 self.manipulate_text(window, cx, |text| {
11527 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11528 if has_upper_case_characters {
11529 text.to_lowercase()
11530 } else {
11531 text.to_uppercase()
11532 }
11533 })
11534 }
11535
11536 pub fn convert_to_rot13(
11537 &mut self,
11538 _: &ConvertToRot13,
11539 window: &mut Window,
11540 cx: &mut Context<Self>,
11541 ) {
11542 self.manipulate_text(window, cx, |text| {
11543 text.chars()
11544 .map(|c| match c {
11545 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11546 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11547 _ => c,
11548 })
11549 .collect()
11550 })
11551 }
11552
11553 pub fn convert_to_rot47(
11554 &mut self,
11555 _: &ConvertToRot47,
11556 window: &mut Window,
11557 cx: &mut Context<Self>,
11558 ) {
11559 self.manipulate_text(window, cx, |text| {
11560 text.chars()
11561 .map(|c| {
11562 let code_point = c as u32;
11563 if code_point >= 33 && code_point <= 126 {
11564 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11565 }
11566 c
11567 })
11568 .collect()
11569 })
11570 }
11571
11572 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11573 where
11574 Fn: FnMut(&str) -> String,
11575 {
11576 let buffer = self.buffer.read(cx).snapshot(cx);
11577
11578 let mut new_selections = Vec::new();
11579 let mut edits = Vec::new();
11580 let mut selection_adjustment = 0i32;
11581
11582 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11583 let selection_is_empty = selection.is_empty();
11584
11585 let (start, end) = if selection_is_empty {
11586 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11587 (word_range.start, word_range.end)
11588 } else {
11589 (
11590 buffer.point_to_offset(selection.start),
11591 buffer.point_to_offset(selection.end),
11592 )
11593 };
11594
11595 let text = buffer.text_for_range(start..end).collect::<String>();
11596 let old_length = text.len() as i32;
11597 let text = callback(&text);
11598
11599 new_selections.push(Selection {
11600 start: (start as i32 - selection_adjustment) as usize,
11601 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11602 goal: SelectionGoal::None,
11603 id: selection.id,
11604 reversed: selection.reversed,
11605 });
11606
11607 selection_adjustment += old_length - text.len() as i32;
11608
11609 edits.push((start..end, text));
11610 }
11611
11612 self.transact(window, cx, |this, window, cx| {
11613 this.buffer.update(cx, |buffer, cx| {
11614 buffer.edit(edits, None, cx);
11615 });
11616
11617 this.change_selections(Default::default(), window, cx, |s| {
11618 s.select(new_selections);
11619 });
11620
11621 this.request_autoscroll(Autoscroll::fit(), cx);
11622 });
11623 }
11624
11625 pub fn move_selection_on_drop(
11626 &mut self,
11627 selection: &Selection<Anchor>,
11628 target: DisplayPoint,
11629 is_cut: bool,
11630 window: &mut Window,
11631 cx: &mut Context<Self>,
11632 ) {
11633 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11634 let buffer = display_map.buffer_snapshot();
11635 let mut edits = Vec::new();
11636 let insert_point = display_map
11637 .clip_point(target, Bias::Left)
11638 .to_point(&display_map);
11639 let text = buffer
11640 .text_for_range(selection.start..selection.end)
11641 .collect::<String>();
11642 if is_cut {
11643 edits.push(((selection.start..selection.end), String::new()));
11644 }
11645 let insert_anchor = buffer.anchor_before(insert_point);
11646 edits.push(((insert_anchor..insert_anchor), text));
11647 let last_edit_start = insert_anchor.bias_left(buffer);
11648 let last_edit_end = insert_anchor.bias_right(buffer);
11649 self.transact(window, cx, |this, window, cx| {
11650 this.buffer.update(cx, |buffer, cx| {
11651 buffer.edit(edits, None, cx);
11652 });
11653 this.change_selections(Default::default(), window, cx, |s| {
11654 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11655 });
11656 });
11657 }
11658
11659 pub fn clear_selection_drag_state(&mut self) {
11660 self.selection_drag_state = SelectionDragState::None;
11661 }
11662
11663 pub fn duplicate(
11664 &mut self,
11665 upwards: bool,
11666 whole_lines: bool,
11667 window: &mut Window,
11668 cx: &mut Context<Self>,
11669 ) {
11670 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11671
11672 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11673 let buffer = display_map.buffer_snapshot();
11674 let selections = self.selections.all::<Point>(&display_map);
11675
11676 let mut edits = Vec::new();
11677 let mut selections_iter = selections.iter().peekable();
11678 while let Some(selection) = selections_iter.next() {
11679 let mut rows = selection.spanned_rows(false, &display_map);
11680 // duplicate line-wise
11681 if whole_lines || selection.start == selection.end {
11682 // Avoid duplicating the same lines twice.
11683 while let Some(next_selection) = selections_iter.peek() {
11684 let next_rows = next_selection.spanned_rows(false, &display_map);
11685 if next_rows.start < rows.end {
11686 rows.end = next_rows.end;
11687 selections_iter.next().unwrap();
11688 } else {
11689 break;
11690 }
11691 }
11692
11693 // Copy the text from the selected row region and splice it either at the start
11694 // or end of the region.
11695 let start = Point::new(rows.start.0, 0);
11696 let end = Point::new(
11697 rows.end.previous_row().0,
11698 buffer.line_len(rows.end.previous_row()),
11699 );
11700
11701 let mut text = buffer.text_for_range(start..end).collect::<String>();
11702
11703 let insert_location = if upwards {
11704 // When duplicating upward, we need to insert before the current line.
11705 // If we're on the last line and it doesn't end with a newline,
11706 // we need to add a newline before the duplicated content.
11707 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11708 && buffer.max_point().column > 0
11709 && !text.ends_with('\n');
11710
11711 if needs_leading_newline {
11712 text.insert(0, '\n');
11713 end
11714 } else {
11715 text.push('\n');
11716 Point::new(rows.start.0, 0)
11717 }
11718 } else {
11719 text.push('\n');
11720 start
11721 };
11722 edits.push((insert_location..insert_location, text));
11723 } else {
11724 // duplicate character-wise
11725 let start = selection.start;
11726 let end = selection.end;
11727 let text = buffer.text_for_range(start..end).collect::<String>();
11728 edits.push((selection.end..selection.end, text));
11729 }
11730 }
11731
11732 self.transact(window, cx, |this, window, cx| {
11733 this.buffer.update(cx, |buffer, cx| {
11734 buffer.edit(edits, None, cx);
11735 });
11736
11737 // When duplicating upward with whole lines, move the cursor to the duplicated line
11738 if upwards && whole_lines {
11739 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11740
11741 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11742 let mut new_ranges = Vec::new();
11743 let selections = s.all::<Point>(&display_map);
11744 let mut selections_iter = selections.iter().peekable();
11745
11746 while let Some(first_selection) = selections_iter.next() {
11747 // Group contiguous selections together to find the total row span
11748 let mut group_selections = vec![first_selection];
11749 let mut rows = first_selection.spanned_rows(false, &display_map);
11750
11751 while let Some(next_selection) = selections_iter.peek() {
11752 let next_rows = next_selection.spanned_rows(false, &display_map);
11753 if next_rows.start < rows.end {
11754 rows.end = next_rows.end;
11755 group_selections.push(selections_iter.next().unwrap());
11756 } else {
11757 break;
11758 }
11759 }
11760
11761 let row_count = rows.end.0 - rows.start.0;
11762
11763 // Move all selections in this group up by the total number of duplicated rows
11764 for selection in group_selections {
11765 let new_start = Point::new(
11766 selection.start.row.saturating_sub(row_count),
11767 selection.start.column,
11768 );
11769
11770 let new_end = Point::new(
11771 selection.end.row.saturating_sub(row_count),
11772 selection.end.column,
11773 );
11774
11775 new_ranges.push(new_start..new_end);
11776 }
11777 }
11778
11779 s.select_ranges(new_ranges);
11780 });
11781 }
11782
11783 this.request_autoscroll(Autoscroll::fit(), cx);
11784 });
11785 }
11786
11787 pub fn duplicate_line_up(
11788 &mut self,
11789 _: &DuplicateLineUp,
11790 window: &mut Window,
11791 cx: &mut Context<Self>,
11792 ) {
11793 self.duplicate(true, true, window, cx);
11794 }
11795
11796 pub fn duplicate_line_down(
11797 &mut self,
11798 _: &DuplicateLineDown,
11799 window: &mut Window,
11800 cx: &mut Context<Self>,
11801 ) {
11802 self.duplicate(false, true, window, cx);
11803 }
11804
11805 pub fn duplicate_selection(
11806 &mut self,
11807 _: &DuplicateSelection,
11808 window: &mut Window,
11809 cx: &mut Context<Self>,
11810 ) {
11811 self.duplicate(false, false, window, cx);
11812 }
11813
11814 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11815 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11816 if self.mode.is_single_line() {
11817 cx.propagate();
11818 return;
11819 }
11820
11821 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11822 let buffer = self.buffer.read(cx).snapshot(cx);
11823
11824 let mut edits = Vec::new();
11825 let mut unfold_ranges = Vec::new();
11826 let mut refold_creases = Vec::new();
11827
11828 let selections = self.selections.all::<Point>(&display_map);
11829 let mut selections = selections.iter().peekable();
11830 let mut contiguous_row_selections = Vec::new();
11831 let mut new_selections = Vec::new();
11832
11833 while let Some(selection) = selections.next() {
11834 // Find all the selections that span a contiguous row range
11835 let (start_row, end_row) = consume_contiguous_rows(
11836 &mut contiguous_row_selections,
11837 selection,
11838 &display_map,
11839 &mut selections,
11840 );
11841
11842 // Move the text spanned by the row range to be before the line preceding the row range
11843 if start_row.0 > 0 {
11844 let range_to_move = Point::new(
11845 start_row.previous_row().0,
11846 buffer.line_len(start_row.previous_row()),
11847 )
11848 ..Point::new(
11849 end_row.previous_row().0,
11850 buffer.line_len(end_row.previous_row()),
11851 );
11852 let insertion_point = display_map
11853 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11854 .0;
11855
11856 // Don't move lines across excerpts
11857 if buffer
11858 .excerpt_containing(insertion_point..range_to_move.end)
11859 .is_some()
11860 {
11861 let text = buffer
11862 .text_for_range(range_to_move.clone())
11863 .flat_map(|s| s.chars())
11864 .skip(1)
11865 .chain(['\n'])
11866 .collect::<String>();
11867
11868 edits.push((
11869 buffer.anchor_after(range_to_move.start)
11870 ..buffer.anchor_before(range_to_move.end),
11871 String::new(),
11872 ));
11873 let insertion_anchor = buffer.anchor_after(insertion_point);
11874 edits.push((insertion_anchor..insertion_anchor, text));
11875
11876 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11877
11878 // Move selections up
11879 new_selections.extend(contiguous_row_selections.drain(..).map(
11880 |mut selection| {
11881 selection.start.row -= row_delta;
11882 selection.end.row -= row_delta;
11883 selection
11884 },
11885 ));
11886
11887 // Move folds up
11888 unfold_ranges.push(range_to_move.clone());
11889 for fold in display_map.folds_in_range(
11890 buffer.anchor_before(range_to_move.start)
11891 ..buffer.anchor_after(range_to_move.end),
11892 ) {
11893 let mut start = fold.range.start.to_point(&buffer);
11894 let mut end = fold.range.end.to_point(&buffer);
11895 start.row -= row_delta;
11896 end.row -= row_delta;
11897 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11898 }
11899 }
11900 }
11901
11902 // If we didn't move line(s), preserve the existing selections
11903 new_selections.append(&mut contiguous_row_selections);
11904 }
11905
11906 self.transact(window, cx, |this, window, cx| {
11907 this.unfold_ranges(&unfold_ranges, true, true, cx);
11908 this.buffer.update(cx, |buffer, cx| {
11909 for (range, text) in edits {
11910 buffer.edit([(range, text)], None, cx);
11911 }
11912 });
11913 this.fold_creases(refold_creases, true, window, cx);
11914 this.change_selections(Default::default(), window, cx, |s| {
11915 s.select(new_selections);
11916 })
11917 });
11918 }
11919
11920 pub fn move_line_down(
11921 &mut self,
11922 _: &MoveLineDown,
11923 window: &mut Window,
11924 cx: &mut Context<Self>,
11925 ) {
11926 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11927 if self.mode.is_single_line() {
11928 cx.propagate();
11929 return;
11930 }
11931
11932 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11933 let buffer = self.buffer.read(cx).snapshot(cx);
11934
11935 let mut edits = Vec::new();
11936 let mut unfold_ranges = Vec::new();
11937 let mut refold_creases = Vec::new();
11938
11939 let selections = self.selections.all::<Point>(&display_map);
11940 let mut selections = selections.iter().peekable();
11941 let mut contiguous_row_selections = Vec::new();
11942 let mut new_selections = Vec::new();
11943
11944 while let Some(selection) = selections.next() {
11945 // Find all the selections that span a contiguous row range
11946 let (start_row, end_row) = consume_contiguous_rows(
11947 &mut contiguous_row_selections,
11948 selection,
11949 &display_map,
11950 &mut selections,
11951 );
11952
11953 // Move the text spanned by the row range to be after the last line of the row range
11954 if end_row.0 <= buffer.max_point().row {
11955 let range_to_move =
11956 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11957 let insertion_point = display_map
11958 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11959 .0;
11960
11961 // Don't move lines across excerpt boundaries
11962 if buffer
11963 .excerpt_containing(range_to_move.start..insertion_point)
11964 .is_some()
11965 {
11966 let mut text = String::from("\n");
11967 text.extend(buffer.text_for_range(range_to_move.clone()));
11968 text.pop(); // Drop trailing newline
11969 edits.push((
11970 buffer.anchor_after(range_to_move.start)
11971 ..buffer.anchor_before(range_to_move.end),
11972 String::new(),
11973 ));
11974 let insertion_anchor = buffer.anchor_after(insertion_point);
11975 edits.push((insertion_anchor..insertion_anchor, text));
11976
11977 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11978
11979 // Move selections down
11980 new_selections.extend(contiguous_row_selections.drain(..).map(
11981 |mut selection| {
11982 selection.start.row += row_delta;
11983 selection.end.row += row_delta;
11984 selection
11985 },
11986 ));
11987
11988 // Move folds down
11989 unfold_ranges.push(range_to_move.clone());
11990 for fold in display_map.folds_in_range(
11991 buffer.anchor_before(range_to_move.start)
11992 ..buffer.anchor_after(range_to_move.end),
11993 ) {
11994 let mut start = fold.range.start.to_point(&buffer);
11995 let mut end = fold.range.end.to_point(&buffer);
11996 start.row += row_delta;
11997 end.row += row_delta;
11998 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11999 }
12000 }
12001 }
12002
12003 // If we didn't move line(s), preserve the existing selections
12004 new_selections.append(&mut contiguous_row_selections);
12005 }
12006
12007 self.transact(window, cx, |this, window, cx| {
12008 this.unfold_ranges(&unfold_ranges, true, true, cx);
12009 this.buffer.update(cx, |buffer, cx| {
12010 for (range, text) in edits {
12011 buffer.edit([(range, text)], None, cx);
12012 }
12013 });
12014 this.fold_creases(refold_creases, true, window, cx);
12015 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12016 });
12017 }
12018
12019 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12020 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12021 let text_layout_details = &self.text_layout_details(window);
12022 self.transact(window, cx, |this, window, cx| {
12023 let edits = this.change_selections(Default::default(), window, cx, |s| {
12024 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12025 s.move_with(|display_map, selection| {
12026 if !selection.is_empty() {
12027 return;
12028 }
12029
12030 let mut head = selection.head();
12031 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12032 if head.column() == display_map.line_len(head.row()) {
12033 transpose_offset = display_map
12034 .buffer_snapshot()
12035 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12036 }
12037
12038 if transpose_offset == 0 {
12039 return;
12040 }
12041
12042 *head.column_mut() += 1;
12043 head = display_map.clip_point(head, Bias::Right);
12044 let goal = SelectionGoal::HorizontalPosition(
12045 display_map
12046 .x_for_display_point(head, text_layout_details)
12047 .into(),
12048 );
12049 selection.collapse_to(head, goal);
12050
12051 let transpose_start = display_map
12052 .buffer_snapshot()
12053 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12054 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12055 let transpose_end = display_map
12056 .buffer_snapshot()
12057 .clip_offset(transpose_offset + 1, Bias::Right);
12058 if let Some(ch) = display_map
12059 .buffer_snapshot()
12060 .chars_at(transpose_start)
12061 .next()
12062 {
12063 edits.push((transpose_start..transpose_offset, String::new()));
12064 edits.push((transpose_end..transpose_end, ch.to_string()));
12065 }
12066 }
12067 });
12068 edits
12069 });
12070 this.buffer
12071 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12072 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12073 this.change_selections(Default::default(), window, cx, |s| {
12074 s.select(selections);
12075 });
12076 });
12077 }
12078
12079 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12080 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12081 if self.mode.is_single_line() {
12082 cx.propagate();
12083 return;
12084 }
12085
12086 self.rewrap_impl(RewrapOptions::default(), cx)
12087 }
12088
12089 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12090 let buffer = self.buffer.read(cx).snapshot(cx);
12091 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12092
12093 #[derive(Clone, Debug, PartialEq)]
12094 enum CommentFormat {
12095 /// single line comment, with prefix for line
12096 Line(String),
12097 /// single line within a block comment, with prefix for line
12098 BlockLine(String),
12099 /// a single line of a block comment that includes the initial delimiter
12100 BlockCommentWithStart(BlockCommentConfig),
12101 /// a single line of a block comment that includes the ending delimiter
12102 BlockCommentWithEnd(BlockCommentConfig),
12103 }
12104
12105 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12106 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12107 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12108 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12109 .peekable();
12110
12111 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12112 row
12113 } else {
12114 return Vec::new();
12115 };
12116
12117 let language_settings = buffer.language_settings_at(selection.head(), cx);
12118 let language_scope = buffer.language_scope_at(selection.head());
12119
12120 let indent_and_prefix_for_row =
12121 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12122 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12123 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12124 &language_scope
12125 {
12126 let indent_end = Point::new(row, indent.len);
12127 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12128 let line_text_after_indent = buffer
12129 .text_for_range(indent_end..line_end)
12130 .collect::<String>();
12131
12132 let is_within_comment_override = buffer
12133 .language_scope_at(indent_end)
12134 .is_some_and(|scope| scope.override_name() == Some("comment"));
12135 let comment_delimiters = if is_within_comment_override {
12136 // we are within a comment syntax node, but we don't
12137 // yet know what kind of comment: block, doc or line
12138 match (
12139 language_scope.documentation_comment(),
12140 language_scope.block_comment(),
12141 ) {
12142 (Some(config), _) | (_, Some(config))
12143 if buffer.contains_str_at(indent_end, &config.start) =>
12144 {
12145 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12146 }
12147 (Some(config), _) | (_, Some(config))
12148 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12149 {
12150 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12151 }
12152 (Some(config), _) | (_, Some(config))
12153 if buffer.contains_str_at(indent_end, &config.prefix) =>
12154 {
12155 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12156 }
12157 (_, _) => language_scope
12158 .line_comment_prefixes()
12159 .iter()
12160 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12161 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12162 }
12163 } else {
12164 // we not in an overridden comment node, but we may
12165 // be within a non-overridden line comment node
12166 language_scope
12167 .line_comment_prefixes()
12168 .iter()
12169 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12170 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12171 };
12172
12173 let rewrap_prefix = language_scope
12174 .rewrap_prefixes()
12175 .iter()
12176 .find_map(|prefix_regex| {
12177 prefix_regex.find(&line_text_after_indent).map(|mat| {
12178 if mat.start() == 0 {
12179 Some(mat.as_str().to_string())
12180 } else {
12181 None
12182 }
12183 })
12184 })
12185 .flatten();
12186 (comment_delimiters, rewrap_prefix)
12187 } else {
12188 (None, None)
12189 };
12190 (indent, comment_prefix, rewrap_prefix)
12191 };
12192
12193 let mut ranges = Vec::new();
12194 let from_empty_selection = selection.is_empty();
12195
12196 let mut current_range_start = first_row;
12197 let mut prev_row = first_row;
12198 let (
12199 mut current_range_indent,
12200 mut current_range_comment_delimiters,
12201 mut current_range_rewrap_prefix,
12202 ) = indent_and_prefix_for_row(first_row);
12203
12204 for row in non_blank_rows_iter.skip(1) {
12205 let has_paragraph_break = row > prev_row + 1;
12206
12207 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12208 indent_and_prefix_for_row(row);
12209
12210 let has_indent_change = row_indent != current_range_indent;
12211 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12212
12213 let has_boundary_change = has_comment_change
12214 || row_rewrap_prefix.is_some()
12215 || (has_indent_change && current_range_comment_delimiters.is_some());
12216
12217 if has_paragraph_break || has_boundary_change {
12218 ranges.push((
12219 language_settings.clone(),
12220 Point::new(current_range_start, 0)
12221 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12222 current_range_indent,
12223 current_range_comment_delimiters.clone(),
12224 current_range_rewrap_prefix.clone(),
12225 from_empty_selection,
12226 ));
12227 current_range_start = row;
12228 current_range_indent = row_indent;
12229 current_range_comment_delimiters = row_comment_delimiters;
12230 current_range_rewrap_prefix = row_rewrap_prefix;
12231 }
12232 prev_row = row;
12233 }
12234
12235 ranges.push((
12236 language_settings.clone(),
12237 Point::new(current_range_start, 0)
12238 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12239 current_range_indent,
12240 current_range_comment_delimiters,
12241 current_range_rewrap_prefix,
12242 from_empty_selection,
12243 ));
12244
12245 ranges
12246 });
12247
12248 let mut edits = Vec::new();
12249 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12250
12251 for (
12252 language_settings,
12253 wrap_range,
12254 mut indent_size,
12255 comment_prefix,
12256 rewrap_prefix,
12257 from_empty_selection,
12258 ) in wrap_ranges
12259 {
12260 let mut start_row = wrap_range.start.row;
12261 let mut end_row = wrap_range.end.row;
12262
12263 // Skip selections that overlap with a range that has already been rewrapped.
12264 let selection_range = start_row..end_row;
12265 if rewrapped_row_ranges
12266 .iter()
12267 .any(|range| range.overlaps(&selection_range))
12268 {
12269 continue;
12270 }
12271
12272 let tab_size = language_settings.tab_size;
12273
12274 let (line_prefix, inside_comment) = match &comment_prefix {
12275 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12276 (Some(prefix.as_str()), true)
12277 }
12278 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12279 (Some(prefix.as_ref()), true)
12280 }
12281 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12282 start: _,
12283 end: _,
12284 prefix,
12285 tab_size,
12286 })) => {
12287 indent_size.len += tab_size;
12288 (Some(prefix.as_ref()), true)
12289 }
12290 None => (None, false),
12291 };
12292 let indent_prefix = indent_size.chars().collect::<String>();
12293 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12294
12295 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12296 RewrapBehavior::InComments => inside_comment,
12297 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12298 RewrapBehavior::Anywhere => true,
12299 };
12300
12301 let should_rewrap = options.override_language_settings
12302 || allow_rewrap_based_on_language
12303 || self.hard_wrap.is_some();
12304 if !should_rewrap {
12305 continue;
12306 }
12307
12308 if from_empty_selection {
12309 'expand_upwards: while start_row > 0 {
12310 let prev_row = start_row - 1;
12311 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12312 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12313 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12314 {
12315 start_row = prev_row;
12316 } else {
12317 break 'expand_upwards;
12318 }
12319 }
12320
12321 'expand_downwards: while end_row < buffer.max_point().row {
12322 let next_row = end_row + 1;
12323 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12324 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12325 && !buffer.is_line_blank(MultiBufferRow(next_row))
12326 {
12327 end_row = next_row;
12328 } else {
12329 break 'expand_downwards;
12330 }
12331 }
12332 }
12333
12334 let start = Point::new(start_row, 0);
12335 let start_offset = ToOffset::to_offset(&start, &buffer);
12336 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12337 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12338 let mut first_line_delimiter = None;
12339 let mut last_line_delimiter = None;
12340 let Some(lines_without_prefixes) = selection_text
12341 .lines()
12342 .enumerate()
12343 .map(|(ix, line)| {
12344 let line_trimmed = line.trim_start();
12345 if rewrap_prefix.is_some() && ix > 0 {
12346 Ok(line_trimmed)
12347 } else if let Some(
12348 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12349 start,
12350 prefix,
12351 end,
12352 tab_size,
12353 })
12354 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12355 start,
12356 prefix,
12357 end,
12358 tab_size,
12359 }),
12360 ) = &comment_prefix
12361 {
12362 let line_trimmed = line_trimmed
12363 .strip_prefix(start.as_ref())
12364 .map(|s| {
12365 let mut indent_size = indent_size;
12366 indent_size.len -= tab_size;
12367 let indent_prefix: String = indent_size.chars().collect();
12368 first_line_delimiter = Some((indent_prefix, start));
12369 s.trim_start()
12370 })
12371 .unwrap_or(line_trimmed);
12372 let line_trimmed = line_trimmed
12373 .strip_suffix(end.as_ref())
12374 .map(|s| {
12375 last_line_delimiter = Some(end);
12376 s.trim_end()
12377 })
12378 .unwrap_or(line_trimmed);
12379 let line_trimmed = line_trimmed
12380 .strip_prefix(prefix.as_ref())
12381 .unwrap_or(line_trimmed);
12382 Ok(line_trimmed)
12383 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12384 line_trimmed.strip_prefix(prefix).with_context(|| {
12385 format!("line did not start with prefix {prefix:?}: {line:?}")
12386 })
12387 } else {
12388 line_trimmed
12389 .strip_prefix(&line_prefix.trim_start())
12390 .with_context(|| {
12391 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12392 })
12393 }
12394 })
12395 .collect::<Result<Vec<_>, _>>()
12396 .log_err()
12397 else {
12398 continue;
12399 };
12400
12401 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12402 buffer
12403 .language_settings_at(Point::new(start_row, 0), cx)
12404 .preferred_line_length as usize
12405 });
12406
12407 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12408 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12409 } else {
12410 line_prefix.clone()
12411 };
12412
12413 let wrapped_text = {
12414 let mut wrapped_text = wrap_with_prefix(
12415 line_prefix,
12416 subsequent_lines_prefix,
12417 lines_without_prefixes.join("\n"),
12418 wrap_column,
12419 tab_size,
12420 options.preserve_existing_whitespace,
12421 );
12422
12423 if let Some((indent, delimiter)) = first_line_delimiter {
12424 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12425 }
12426 if let Some(last_line) = last_line_delimiter {
12427 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12428 }
12429
12430 wrapped_text
12431 };
12432
12433 // TODO: should always use char-based diff while still supporting cursor behavior that
12434 // matches vim.
12435 let mut diff_options = DiffOptions::default();
12436 if options.override_language_settings {
12437 diff_options.max_word_diff_len = 0;
12438 diff_options.max_word_diff_line_count = 0;
12439 } else {
12440 diff_options.max_word_diff_len = usize::MAX;
12441 diff_options.max_word_diff_line_count = usize::MAX;
12442 }
12443
12444 for (old_range, new_text) in
12445 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12446 {
12447 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12448 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12449 edits.push((edit_start..edit_end, new_text));
12450 }
12451
12452 rewrapped_row_ranges.push(start_row..=end_row);
12453 }
12454
12455 self.buffer
12456 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12457 }
12458
12459 pub fn cut_common(
12460 &mut self,
12461 cut_no_selection_line: bool,
12462 window: &mut Window,
12463 cx: &mut Context<Self>,
12464 ) -> ClipboardItem {
12465 let mut text = String::new();
12466 let buffer = self.buffer.read(cx).snapshot(cx);
12467 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12468 let mut clipboard_selections = Vec::with_capacity(selections.len());
12469 {
12470 let max_point = buffer.max_point();
12471 let mut is_first = true;
12472 for selection in &mut selections {
12473 let is_entire_line =
12474 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12475 if is_entire_line {
12476 selection.start = Point::new(selection.start.row, 0);
12477 if !selection.is_empty() && selection.end.column == 0 {
12478 selection.end = cmp::min(max_point, selection.end);
12479 } else {
12480 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12481 }
12482 selection.goal = SelectionGoal::None;
12483 }
12484 if is_first {
12485 is_first = false;
12486 } else {
12487 text += "\n";
12488 }
12489 let mut len = 0;
12490 for chunk in buffer.text_for_range(selection.start..selection.end) {
12491 text.push_str(chunk);
12492 len += chunk.len();
12493 }
12494 clipboard_selections.push(ClipboardSelection {
12495 len,
12496 is_entire_line,
12497 first_line_indent: buffer
12498 .indent_size_for_line(MultiBufferRow(selection.start.row))
12499 .len,
12500 });
12501 }
12502 }
12503
12504 self.transact(window, cx, |this, window, cx| {
12505 this.change_selections(Default::default(), window, cx, |s| {
12506 s.select(selections);
12507 });
12508 this.insert("", window, cx);
12509 });
12510 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12511 }
12512
12513 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12514 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12515 let item = self.cut_common(true, window, cx);
12516 cx.write_to_clipboard(item);
12517 }
12518
12519 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12520 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12521 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12522 s.move_with(|snapshot, sel| {
12523 if sel.is_empty() {
12524 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12525 }
12526 if sel.is_empty() {
12527 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12528 }
12529 });
12530 });
12531 let item = self.cut_common(false, window, cx);
12532 cx.set_global(KillRing(item))
12533 }
12534
12535 pub fn kill_ring_yank(
12536 &mut self,
12537 _: &KillRingYank,
12538 window: &mut Window,
12539 cx: &mut Context<Self>,
12540 ) {
12541 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12542 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12543 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12544 (kill_ring.text().to_string(), kill_ring.metadata_json())
12545 } else {
12546 return;
12547 }
12548 } else {
12549 return;
12550 };
12551 self.do_paste(&text, metadata, false, window, cx);
12552 }
12553
12554 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12555 self.do_copy(true, cx);
12556 }
12557
12558 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12559 self.do_copy(false, cx);
12560 }
12561
12562 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12563 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12564 let buffer = self.buffer.read(cx).read(cx);
12565 let mut text = String::new();
12566
12567 let mut clipboard_selections = Vec::with_capacity(selections.len());
12568 {
12569 let max_point = buffer.max_point();
12570 let mut is_first = true;
12571 for selection in &selections {
12572 let mut start = selection.start;
12573 let mut end = selection.end;
12574 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12575 let mut add_trailing_newline = false;
12576 if is_entire_line {
12577 start = Point::new(start.row, 0);
12578 let next_line_start = Point::new(end.row + 1, 0);
12579 if next_line_start <= max_point {
12580 end = next_line_start;
12581 } else {
12582 // We're on the last line without a trailing newline.
12583 // Copy to the end of the line and add a newline afterwards.
12584 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12585 add_trailing_newline = true;
12586 }
12587 }
12588
12589 let mut trimmed_selections = Vec::new();
12590 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12591 let row = MultiBufferRow(start.row);
12592 let first_indent = buffer.indent_size_for_line(row);
12593 if first_indent.len == 0 || start.column > first_indent.len {
12594 trimmed_selections.push(start..end);
12595 } else {
12596 trimmed_selections.push(
12597 Point::new(row.0, first_indent.len)
12598 ..Point::new(row.0, buffer.line_len(row)),
12599 );
12600 for row in start.row + 1..=end.row {
12601 let mut line_len = buffer.line_len(MultiBufferRow(row));
12602 if row == end.row {
12603 line_len = end.column;
12604 }
12605 if line_len == 0 {
12606 trimmed_selections
12607 .push(Point::new(row, 0)..Point::new(row, line_len));
12608 continue;
12609 }
12610 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12611 if row_indent_size.len >= first_indent.len {
12612 trimmed_selections.push(
12613 Point::new(row, first_indent.len)..Point::new(row, line_len),
12614 );
12615 } else {
12616 trimmed_selections.clear();
12617 trimmed_selections.push(start..end);
12618 break;
12619 }
12620 }
12621 }
12622 } else {
12623 trimmed_selections.push(start..end);
12624 }
12625
12626 for trimmed_range in trimmed_selections {
12627 if is_first {
12628 is_first = false;
12629 } else {
12630 text += "\n";
12631 }
12632 let mut len = 0;
12633 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12634 text.push_str(chunk);
12635 len += chunk.len();
12636 }
12637 if add_trailing_newline {
12638 text.push('\n');
12639 len += 1;
12640 }
12641 clipboard_selections.push(ClipboardSelection {
12642 len,
12643 is_entire_line,
12644 first_line_indent: buffer
12645 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12646 .len,
12647 });
12648 }
12649 }
12650 }
12651
12652 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12653 text,
12654 clipboard_selections,
12655 ));
12656 }
12657
12658 pub fn do_paste(
12659 &mut self,
12660 text: &String,
12661 clipboard_selections: Option<Vec<ClipboardSelection>>,
12662 handle_entire_lines: bool,
12663 window: &mut Window,
12664 cx: &mut Context<Self>,
12665 ) {
12666 if self.read_only(cx) {
12667 return;
12668 }
12669
12670 let clipboard_text = Cow::Borrowed(text.as_str());
12671
12672 self.transact(window, cx, |this, window, cx| {
12673 let had_active_edit_prediction = this.has_active_edit_prediction();
12674 let display_map = this.display_snapshot(cx);
12675 let old_selections = this.selections.all::<usize>(&display_map);
12676 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12677
12678 if let Some(mut clipboard_selections) = clipboard_selections {
12679 let all_selections_were_entire_line =
12680 clipboard_selections.iter().all(|s| s.is_entire_line);
12681 let first_selection_indent_column =
12682 clipboard_selections.first().map(|s| s.first_line_indent);
12683 if clipboard_selections.len() != old_selections.len() {
12684 clipboard_selections.drain(..);
12685 }
12686 let mut auto_indent_on_paste = true;
12687
12688 this.buffer.update(cx, |buffer, cx| {
12689 let snapshot = buffer.read(cx);
12690 auto_indent_on_paste = snapshot
12691 .language_settings_at(cursor_offset, cx)
12692 .auto_indent_on_paste;
12693
12694 let mut start_offset = 0;
12695 let mut edits = Vec::new();
12696 let mut original_indent_columns = Vec::new();
12697 for (ix, selection) in old_selections.iter().enumerate() {
12698 let to_insert;
12699 let entire_line;
12700 let original_indent_column;
12701 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12702 let end_offset = start_offset + clipboard_selection.len;
12703 to_insert = &clipboard_text[start_offset..end_offset];
12704 entire_line = clipboard_selection.is_entire_line;
12705 start_offset = end_offset + 1;
12706 original_indent_column = Some(clipboard_selection.first_line_indent);
12707 } else {
12708 to_insert = &*clipboard_text;
12709 entire_line = all_selections_were_entire_line;
12710 original_indent_column = first_selection_indent_column
12711 }
12712
12713 let (range, to_insert) =
12714 if selection.is_empty() && handle_entire_lines && entire_line {
12715 // If the corresponding selection was empty when this slice of the
12716 // clipboard text was written, then the entire line containing the
12717 // selection was copied. If this selection is also currently empty,
12718 // then paste the line before the current line of the buffer.
12719 let column = selection.start.to_point(&snapshot).column as usize;
12720 let line_start = selection.start - column;
12721 (line_start..line_start, Cow::Borrowed(to_insert))
12722 } else {
12723 let language = snapshot.language_at(selection.head());
12724 let range = selection.range();
12725 if let Some(language) = language
12726 && language.name() == "Markdown".into()
12727 {
12728 edit_for_markdown_paste(
12729 &snapshot,
12730 range,
12731 to_insert,
12732 url::Url::parse(to_insert).ok(),
12733 )
12734 } else {
12735 (range, Cow::Borrowed(to_insert))
12736 }
12737 };
12738
12739 edits.push((range, to_insert));
12740 original_indent_columns.push(original_indent_column);
12741 }
12742 drop(snapshot);
12743
12744 buffer.edit(
12745 edits,
12746 if auto_indent_on_paste {
12747 Some(AutoindentMode::Block {
12748 original_indent_columns,
12749 })
12750 } else {
12751 None
12752 },
12753 cx,
12754 );
12755 });
12756
12757 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12758 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12759 } else {
12760 let url = url::Url::parse(&clipboard_text).ok();
12761
12762 let auto_indent_mode = if !clipboard_text.is_empty() {
12763 Some(AutoindentMode::Block {
12764 original_indent_columns: Vec::new(),
12765 })
12766 } else {
12767 None
12768 };
12769
12770 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12771 let snapshot = buffer.snapshot(cx);
12772
12773 let anchors = old_selections
12774 .iter()
12775 .map(|s| {
12776 let anchor = snapshot.anchor_after(s.head());
12777 s.map(|_| anchor)
12778 })
12779 .collect::<Vec<_>>();
12780
12781 let mut edits = Vec::new();
12782
12783 for selection in old_selections.iter() {
12784 let language = snapshot.language_at(selection.head());
12785 let range = selection.range();
12786
12787 let (edit_range, edit_text) = if let Some(language) = language
12788 && language.name() == "Markdown".into()
12789 {
12790 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12791 } else {
12792 (range, clipboard_text.clone())
12793 };
12794
12795 edits.push((edit_range, edit_text));
12796 }
12797
12798 drop(snapshot);
12799 buffer.edit(edits, auto_indent_mode, cx);
12800
12801 anchors
12802 });
12803
12804 this.change_selections(Default::default(), window, cx, |s| {
12805 s.select_anchors(selection_anchors);
12806 });
12807 }
12808
12809 let trigger_in_words =
12810 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12811
12812 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12813 });
12814 }
12815
12816 pub fn diff_clipboard_with_selection(
12817 &mut self,
12818 _: &DiffClipboardWithSelection,
12819 window: &mut Window,
12820 cx: &mut Context<Self>,
12821 ) {
12822 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12823
12824 if selections.is_empty() {
12825 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12826 return;
12827 };
12828
12829 let clipboard_text = match cx.read_from_clipboard() {
12830 Some(item) => match item.entries().first() {
12831 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12832 _ => None,
12833 },
12834 None => None,
12835 };
12836
12837 let Some(clipboard_text) = clipboard_text else {
12838 log::warn!("Clipboard doesn't contain text.");
12839 return;
12840 };
12841
12842 window.dispatch_action(
12843 Box::new(DiffClipboardWithSelectionData {
12844 clipboard_text,
12845 editor: cx.entity(),
12846 }),
12847 cx,
12848 );
12849 }
12850
12851 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12852 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12853 if let Some(item) = cx.read_from_clipboard() {
12854 let entries = item.entries();
12855
12856 match entries.first() {
12857 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12858 // of all the pasted entries.
12859 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12860 .do_paste(
12861 clipboard_string.text(),
12862 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12863 true,
12864 window,
12865 cx,
12866 ),
12867 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12868 }
12869 }
12870 }
12871
12872 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12873 if self.read_only(cx) {
12874 return;
12875 }
12876
12877 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12878
12879 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12880 if let Some((selections, _)) =
12881 self.selection_history.transaction(transaction_id).cloned()
12882 {
12883 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12884 s.select_anchors(selections.to_vec());
12885 });
12886 } else {
12887 log::error!(
12888 "No entry in selection_history found for undo. \
12889 This may correspond to a bug where undo does not update the selection. \
12890 If this is occurring, please add details to \
12891 https://github.com/zed-industries/zed/issues/22692"
12892 );
12893 }
12894 self.request_autoscroll(Autoscroll::fit(), cx);
12895 self.unmark_text(window, cx);
12896 self.refresh_edit_prediction(true, false, window, cx);
12897 cx.emit(EditorEvent::Edited { transaction_id });
12898 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12899 }
12900 }
12901
12902 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12903 if self.read_only(cx) {
12904 return;
12905 }
12906
12907 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12908
12909 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12910 if let Some((_, Some(selections))) =
12911 self.selection_history.transaction(transaction_id).cloned()
12912 {
12913 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12914 s.select_anchors(selections.to_vec());
12915 });
12916 } else {
12917 log::error!(
12918 "No entry in selection_history found for redo. \
12919 This may correspond to a bug where undo does not update the selection. \
12920 If this is occurring, please add details to \
12921 https://github.com/zed-industries/zed/issues/22692"
12922 );
12923 }
12924 self.request_autoscroll(Autoscroll::fit(), cx);
12925 self.unmark_text(window, cx);
12926 self.refresh_edit_prediction(true, false, window, cx);
12927 cx.emit(EditorEvent::Edited { transaction_id });
12928 }
12929 }
12930
12931 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12932 self.buffer
12933 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12934 }
12935
12936 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12937 self.buffer
12938 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12939 }
12940
12941 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12942 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12943 self.change_selections(Default::default(), window, cx, |s| {
12944 s.move_with(|map, selection| {
12945 let cursor = if selection.is_empty() {
12946 movement::left(map, selection.start)
12947 } else {
12948 selection.start
12949 };
12950 selection.collapse_to(cursor, SelectionGoal::None);
12951 });
12952 })
12953 }
12954
12955 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12956 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12957 self.change_selections(Default::default(), window, cx, |s| {
12958 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12959 })
12960 }
12961
12962 pub fn move_right(&mut self, _: &MoveRight, 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_with(|map, selection| {
12966 let cursor = if selection.is_empty() {
12967 movement::right(map, selection.end)
12968 } else {
12969 selection.end
12970 };
12971 selection.collapse_to(cursor, SelectionGoal::None)
12972 });
12973 })
12974 }
12975
12976 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12977 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12978 self.change_selections(Default::default(), window, cx, |s| {
12979 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12980 });
12981 }
12982
12983 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12984 if self.take_rename(true, window, cx).is_some() {
12985 return;
12986 }
12987
12988 if self.mode.is_single_line() {
12989 cx.propagate();
12990 return;
12991 }
12992
12993 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12994
12995 let text_layout_details = &self.text_layout_details(window);
12996 let selection_count = self.selections.count();
12997 let first_selection = self.selections.first_anchor();
12998
12999 self.change_selections(Default::default(), window, cx, |s| {
13000 s.move_with(|map, selection| {
13001 if !selection.is_empty() {
13002 selection.goal = SelectionGoal::None;
13003 }
13004 let (cursor, goal) = movement::up(
13005 map,
13006 selection.start,
13007 selection.goal,
13008 false,
13009 text_layout_details,
13010 );
13011 selection.collapse_to(cursor, goal);
13012 });
13013 });
13014
13015 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13016 {
13017 cx.propagate();
13018 }
13019 }
13020
13021 pub fn move_up_by_lines(
13022 &mut self,
13023 action: &MoveUpByLines,
13024 window: &mut Window,
13025 cx: &mut Context<Self>,
13026 ) {
13027 if self.take_rename(true, window, cx).is_some() {
13028 return;
13029 }
13030
13031 if self.mode.is_single_line() {
13032 cx.propagate();
13033 return;
13034 }
13035
13036 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13037
13038 let text_layout_details = &self.text_layout_details(window);
13039
13040 self.change_selections(Default::default(), window, cx, |s| {
13041 s.move_with(|map, selection| {
13042 if !selection.is_empty() {
13043 selection.goal = SelectionGoal::None;
13044 }
13045 let (cursor, goal) = movement::up_by_rows(
13046 map,
13047 selection.start,
13048 action.lines,
13049 selection.goal,
13050 false,
13051 text_layout_details,
13052 );
13053 selection.collapse_to(cursor, goal);
13054 });
13055 })
13056 }
13057
13058 pub fn move_down_by_lines(
13059 &mut self,
13060 action: &MoveDownByLines,
13061 window: &mut Window,
13062 cx: &mut Context<Self>,
13063 ) {
13064 if self.take_rename(true, window, cx).is_some() {
13065 return;
13066 }
13067
13068 if self.mode.is_single_line() {
13069 cx.propagate();
13070 return;
13071 }
13072
13073 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13074
13075 let text_layout_details = &self.text_layout_details(window);
13076
13077 self.change_selections(Default::default(), window, cx, |s| {
13078 s.move_with(|map, selection| {
13079 if !selection.is_empty() {
13080 selection.goal = SelectionGoal::None;
13081 }
13082 let (cursor, goal) = movement::down_by_rows(
13083 map,
13084 selection.start,
13085 action.lines,
13086 selection.goal,
13087 false,
13088 text_layout_details,
13089 );
13090 selection.collapse_to(cursor, goal);
13091 });
13092 })
13093 }
13094
13095 pub fn select_down_by_lines(
13096 &mut self,
13097 action: &SelectDownByLines,
13098 window: &mut Window,
13099 cx: &mut Context<Self>,
13100 ) {
13101 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13102 let text_layout_details = &self.text_layout_details(window);
13103 self.change_selections(Default::default(), window, cx, |s| {
13104 s.move_heads_with(|map, head, goal| {
13105 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13106 })
13107 })
13108 }
13109
13110 pub fn select_up_by_lines(
13111 &mut self,
13112 action: &SelectUpByLines,
13113 window: &mut Window,
13114 cx: &mut Context<Self>,
13115 ) {
13116 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13117 let text_layout_details = &self.text_layout_details(window);
13118 self.change_selections(Default::default(), window, cx, |s| {
13119 s.move_heads_with(|map, head, goal| {
13120 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13121 })
13122 })
13123 }
13124
13125 pub fn select_page_up(
13126 &mut self,
13127 _: &SelectPageUp,
13128 window: &mut Window,
13129 cx: &mut Context<Self>,
13130 ) {
13131 let Some(row_count) = self.visible_row_count() else {
13132 return;
13133 };
13134
13135 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13136
13137 let text_layout_details = &self.text_layout_details(window);
13138
13139 self.change_selections(Default::default(), window, cx, |s| {
13140 s.move_heads_with(|map, head, goal| {
13141 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13142 })
13143 })
13144 }
13145
13146 pub fn move_page_up(
13147 &mut self,
13148 action: &MovePageUp,
13149 window: &mut Window,
13150 cx: &mut Context<Self>,
13151 ) {
13152 if self.take_rename(true, window, cx).is_some() {
13153 return;
13154 }
13155
13156 if self
13157 .context_menu
13158 .borrow_mut()
13159 .as_mut()
13160 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13161 .unwrap_or(false)
13162 {
13163 return;
13164 }
13165
13166 if matches!(self.mode, EditorMode::SingleLine) {
13167 cx.propagate();
13168 return;
13169 }
13170
13171 let Some(row_count) = self.visible_row_count() else {
13172 return;
13173 };
13174
13175 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13176
13177 let effects = if action.center_cursor {
13178 SelectionEffects::scroll(Autoscroll::center())
13179 } else {
13180 SelectionEffects::default()
13181 };
13182
13183 let text_layout_details = &self.text_layout_details(window);
13184
13185 self.change_selections(effects, window, cx, |s| {
13186 s.move_with(|map, selection| {
13187 if !selection.is_empty() {
13188 selection.goal = SelectionGoal::None;
13189 }
13190 let (cursor, goal) = movement::up_by_rows(
13191 map,
13192 selection.end,
13193 row_count,
13194 selection.goal,
13195 false,
13196 text_layout_details,
13197 );
13198 selection.collapse_to(cursor, goal);
13199 });
13200 });
13201 }
13202
13203 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13204 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13205 let text_layout_details = &self.text_layout_details(window);
13206 self.change_selections(Default::default(), window, cx, |s| {
13207 s.move_heads_with(|map, head, goal| {
13208 movement::up(map, head, goal, false, text_layout_details)
13209 })
13210 })
13211 }
13212
13213 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13214 self.take_rename(true, window, cx);
13215
13216 if self.mode.is_single_line() {
13217 cx.propagate();
13218 return;
13219 }
13220
13221 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13222
13223 let text_layout_details = &self.text_layout_details(window);
13224 let selection_count = self.selections.count();
13225 let first_selection = self.selections.first_anchor();
13226
13227 self.change_selections(Default::default(), window, cx, |s| {
13228 s.move_with(|map, selection| {
13229 if !selection.is_empty() {
13230 selection.goal = SelectionGoal::None;
13231 }
13232 let (cursor, goal) = movement::down(
13233 map,
13234 selection.end,
13235 selection.goal,
13236 false,
13237 text_layout_details,
13238 );
13239 selection.collapse_to(cursor, goal);
13240 });
13241 });
13242
13243 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13244 {
13245 cx.propagate();
13246 }
13247 }
13248
13249 pub fn select_page_down(
13250 &mut self,
13251 _: &SelectPageDown,
13252 window: &mut Window,
13253 cx: &mut Context<Self>,
13254 ) {
13255 let Some(row_count) = self.visible_row_count() else {
13256 return;
13257 };
13258
13259 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13260
13261 let text_layout_details = &self.text_layout_details(window);
13262
13263 self.change_selections(Default::default(), window, cx, |s| {
13264 s.move_heads_with(|map, head, goal| {
13265 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13266 })
13267 })
13268 }
13269
13270 pub fn move_page_down(
13271 &mut self,
13272 action: &MovePageDown,
13273 window: &mut Window,
13274 cx: &mut Context<Self>,
13275 ) {
13276 if self.take_rename(true, window, cx).is_some() {
13277 return;
13278 }
13279
13280 if self
13281 .context_menu
13282 .borrow_mut()
13283 .as_mut()
13284 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13285 .unwrap_or(false)
13286 {
13287 return;
13288 }
13289
13290 if matches!(self.mode, EditorMode::SingleLine) {
13291 cx.propagate();
13292 return;
13293 }
13294
13295 let Some(row_count) = self.visible_row_count() else {
13296 return;
13297 };
13298
13299 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13300
13301 let effects = if action.center_cursor {
13302 SelectionEffects::scroll(Autoscroll::center())
13303 } else {
13304 SelectionEffects::default()
13305 };
13306
13307 let text_layout_details = &self.text_layout_details(window);
13308 self.change_selections(effects, window, cx, |s| {
13309 s.move_with(|map, selection| {
13310 if !selection.is_empty() {
13311 selection.goal = SelectionGoal::None;
13312 }
13313 let (cursor, goal) = movement::down_by_rows(
13314 map,
13315 selection.end,
13316 row_count,
13317 selection.goal,
13318 false,
13319 text_layout_details,
13320 );
13321 selection.collapse_to(cursor, goal);
13322 });
13323 });
13324 }
13325
13326 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13327 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13328 let text_layout_details = &self.text_layout_details(window);
13329 self.change_selections(Default::default(), window, cx, |s| {
13330 s.move_heads_with(|map, head, goal| {
13331 movement::down(map, head, goal, false, text_layout_details)
13332 })
13333 });
13334 }
13335
13336 pub fn context_menu_first(
13337 &mut self,
13338 _: &ContextMenuFirst,
13339 window: &mut Window,
13340 cx: &mut Context<Self>,
13341 ) {
13342 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13343 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13344 }
13345 }
13346
13347 pub fn context_menu_prev(
13348 &mut self,
13349 _: &ContextMenuPrevious,
13350 window: &mut Window,
13351 cx: &mut Context<Self>,
13352 ) {
13353 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13354 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13355 }
13356 }
13357
13358 pub fn context_menu_next(
13359 &mut self,
13360 _: &ContextMenuNext,
13361 window: &mut Window,
13362 cx: &mut Context<Self>,
13363 ) {
13364 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13365 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13366 }
13367 }
13368
13369 pub fn context_menu_last(
13370 &mut self,
13371 _: &ContextMenuLast,
13372 window: &mut Window,
13373 cx: &mut Context<Self>,
13374 ) {
13375 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13376 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13377 }
13378 }
13379
13380 pub fn signature_help_prev(
13381 &mut self,
13382 _: &SignatureHelpPrevious,
13383 _: &mut Window,
13384 cx: &mut Context<Self>,
13385 ) {
13386 if let Some(popover) = self.signature_help_state.popover_mut() {
13387 if popover.current_signature == 0 {
13388 popover.current_signature = popover.signatures.len() - 1;
13389 } else {
13390 popover.current_signature -= 1;
13391 }
13392 cx.notify();
13393 }
13394 }
13395
13396 pub fn signature_help_next(
13397 &mut self,
13398 _: &SignatureHelpNext,
13399 _: &mut Window,
13400 cx: &mut Context<Self>,
13401 ) {
13402 if let Some(popover) = self.signature_help_state.popover_mut() {
13403 if popover.current_signature + 1 == popover.signatures.len() {
13404 popover.current_signature = 0;
13405 } else {
13406 popover.current_signature += 1;
13407 }
13408 cx.notify();
13409 }
13410 }
13411
13412 pub fn move_to_previous_word_start(
13413 &mut self,
13414 _: &MoveToPreviousWordStart,
13415 window: &mut Window,
13416 cx: &mut Context<Self>,
13417 ) {
13418 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13419 self.change_selections(Default::default(), window, cx, |s| {
13420 s.move_cursors_with(|map, head, _| {
13421 (
13422 movement::previous_word_start(map, head),
13423 SelectionGoal::None,
13424 )
13425 });
13426 })
13427 }
13428
13429 pub fn move_to_previous_subword_start(
13430 &mut self,
13431 _: &MoveToPreviousSubwordStart,
13432 window: &mut Window,
13433 cx: &mut Context<Self>,
13434 ) {
13435 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13436 self.change_selections(Default::default(), window, cx, |s| {
13437 s.move_cursors_with(|map, head, _| {
13438 (
13439 movement::previous_subword_start(map, head),
13440 SelectionGoal::None,
13441 )
13442 });
13443 })
13444 }
13445
13446 pub fn select_to_previous_word_start(
13447 &mut self,
13448 _: &SelectToPreviousWordStart,
13449 window: &mut Window,
13450 cx: &mut Context<Self>,
13451 ) {
13452 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13453 self.change_selections(Default::default(), window, cx, |s| {
13454 s.move_heads_with(|map, head, _| {
13455 (
13456 movement::previous_word_start(map, head),
13457 SelectionGoal::None,
13458 )
13459 });
13460 })
13461 }
13462
13463 pub fn select_to_previous_subword_start(
13464 &mut self,
13465 _: &SelectToPreviousSubwordStart,
13466 window: &mut Window,
13467 cx: &mut Context<Self>,
13468 ) {
13469 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13470 self.change_selections(Default::default(), window, cx, |s| {
13471 s.move_heads_with(|map, head, _| {
13472 (
13473 movement::previous_subword_start(map, head),
13474 SelectionGoal::None,
13475 )
13476 });
13477 })
13478 }
13479
13480 pub fn delete_to_previous_word_start(
13481 &mut self,
13482 action: &DeleteToPreviousWordStart,
13483 window: &mut Window,
13484 cx: &mut Context<Self>,
13485 ) {
13486 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13487 self.transact(window, cx, |this, window, cx| {
13488 this.select_autoclose_pair(window, cx);
13489 this.change_selections(Default::default(), window, cx, |s| {
13490 s.move_with(|map, selection| {
13491 if selection.is_empty() {
13492 let mut cursor = if action.ignore_newlines {
13493 movement::previous_word_start(map, selection.head())
13494 } else {
13495 movement::previous_word_start_or_newline(map, selection.head())
13496 };
13497 cursor = movement::adjust_greedy_deletion(
13498 map,
13499 selection.head(),
13500 cursor,
13501 action.ignore_brackets,
13502 );
13503 selection.set_head(cursor, SelectionGoal::None);
13504 }
13505 });
13506 });
13507 this.insert("", window, cx);
13508 });
13509 }
13510
13511 pub fn delete_to_previous_subword_start(
13512 &mut self,
13513 _: &DeleteToPreviousSubwordStart,
13514 window: &mut Window,
13515 cx: &mut Context<Self>,
13516 ) {
13517 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13518 self.transact(window, cx, |this, window, cx| {
13519 this.select_autoclose_pair(window, cx);
13520 this.change_selections(Default::default(), window, cx, |s| {
13521 s.move_with(|map, selection| {
13522 if selection.is_empty() {
13523 let mut cursor = movement::previous_subword_start(map, selection.head());
13524 cursor =
13525 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13526 selection.set_head(cursor, SelectionGoal::None);
13527 }
13528 });
13529 });
13530 this.insert("", window, cx);
13531 });
13532 }
13533
13534 pub fn move_to_next_word_end(
13535 &mut self,
13536 _: &MoveToNextWordEnd,
13537 window: &mut Window,
13538 cx: &mut Context<Self>,
13539 ) {
13540 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13541 self.change_selections(Default::default(), window, cx, |s| {
13542 s.move_cursors_with(|map, head, _| {
13543 (movement::next_word_end(map, head), SelectionGoal::None)
13544 });
13545 })
13546 }
13547
13548 pub fn move_to_next_subword_end(
13549 &mut self,
13550 _: &MoveToNextSubwordEnd,
13551 window: &mut Window,
13552 cx: &mut Context<Self>,
13553 ) {
13554 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13555 self.change_selections(Default::default(), window, cx, |s| {
13556 s.move_cursors_with(|map, head, _| {
13557 (movement::next_subword_end(map, head), SelectionGoal::None)
13558 });
13559 })
13560 }
13561
13562 pub fn select_to_next_word_end(
13563 &mut self,
13564 _: &SelectToNextWordEnd,
13565 window: &mut Window,
13566 cx: &mut Context<Self>,
13567 ) {
13568 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13569 self.change_selections(Default::default(), window, cx, |s| {
13570 s.move_heads_with(|map, head, _| {
13571 (movement::next_word_end(map, head), SelectionGoal::None)
13572 });
13573 })
13574 }
13575
13576 pub fn select_to_next_subword_end(
13577 &mut self,
13578 _: &SelectToNextSubwordEnd,
13579 window: &mut Window,
13580 cx: &mut Context<Self>,
13581 ) {
13582 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13583 self.change_selections(Default::default(), window, cx, |s| {
13584 s.move_heads_with(|map, head, _| {
13585 (movement::next_subword_end(map, head), SelectionGoal::None)
13586 });
13587 })
13588 }
13589
13590 pub fn delete_to_next_word_end(
13591 &mut self,
13592 action: &DeleteToNextWordEnd,
13593 window: &mut Window,
13594 cx: &mut Context<Self>,
13595 ) {
13596 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13597 self.transact(window, cx, |this, window, cx| {
13598 this.change_selections(Default::default(), window, cx, |s| {
13599 s.move_with(|map, selection| {
13600 if selection.is_empty() {
13601 let mut cursor = if action.ignore_newlines {
13602 movement::next_word_end(map, selection.head())
13603 } else {
13604 movement::next_word_end_or_newline(map, selection.head())
13605 };
13606 cursor = movement::adjust_greedy_deletion(
13607 map,
13608 selection.head(),
13609 cursor,
13610 action.ignore_brackets,
13611 );
13612 selection.set_head(cursor, SelectionGoal::None);
13613 }
13614 });
13615 });
13616 this.insert("", window, cx);
13617 });
13618 }
13619
13620 pub fn delete_to_next_subword_end(
13621 &mut self,
13622 _: &DeleteToNextSubwordEnd,
13623 window: &mut Window,
13624 cx: &mut Context<Self>,
13625 ) {
13626 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13627 self.transact(window, cx, |this, window, cx| {
13628 this.change_selections(Default::default(), window, cx, |s| {
13629 s.move_with(|map, selection| {
13630 if selection.is_empty() {
13631 let mut cursor = movement::next_subword_end(map, selection.head());
13632 cursor =
13633 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13634 selection.set_head(cursor, SelectionGoal::None);
13635 }
13636 });
13637 });
13638 this.insert("", window, cx);
13639 });
13640 }
13641
13642 pub fn move_to_beginning_of_line(
13643 &mut self,
13644 action: &MoveToBeginningOfLine,
13645 window: &mut Window,
13646 cx: &mut Context<Self>,
13647 ) {
13648 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13649 self.change_selections(Default::default(), window, cx, |s| {
13650 s.move_cursors_with(|map, head, _| {
13651 (
13652 movement::indented_line_beginning(
13653 map,
13654 head,
13655 action.stop_at_soft_wraps,
13656 action.stop_at_indent,
13657 ),
13658 SelectionGoal::None,
13659 )
13660 });
13661 })
13662 }
13663
13664 pub fn select_to_beginning_of_line(
13665 &mut self,
13666 action: &SelectToBeginningOfLine,
13667 window: &mut Window,
13668 cx: &mut Context<Self>,
13669 ) {
13670 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13671 self.change_selections(Default::default(), window, cx, |s| {
13672 s.move_heads_with(|map, head, _| {
13673 (
13674 movement::indented_line_beginning(
13675 map,
13676 head,
13677 action.stop_at_soft_wraps,
13678 action.stop_at_indent,
13679 ),
13680 SelectionGoal::None,
13681 )
13682 });
13683 });
13684 }
13685
13686 pub fn delete_to_beginning_of_line(
13687 &mut self,
13688 action: &DeleteToBeginningOfLine,
13689 window: &mut Window,
13690 cx: &mut Context<Self>,
13691 ) {
13692 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13693 self.transact(window, cx, |this, window, cx| {
13694 this.change_selections(Default::default(), window, cx, |s| {
13695 s.move_with(|_, selection| {
13696 selection.reversed = true;
13697 });
13698 });
13699
13700 this.select_to_beginning_of_line(
13701 &SelectToBeginningOfLine {
13702 stop_at_soft_wraps: false,
13703 stop_at_indent: action.stop_at_indent,
13704 },
13705 window,
13706 cx,
13707 );
13708 this.backspace(&Backspace, window, cx);
13709 });
13710 }
13711
13712 pub fn move_to_end_of_line(
13713 &mut self,
13714 action: &MoveToEndOfLine,
13715 window: &mut Window,
13716 cx: &mut Context<Self>,
13717 ) {
13718 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13719 self.change_selections(Default::default(), window, cx, |s| {
13720 s.move_cursors_with(|map, head, _| {
13721 (
13722 movement::line_end(map, head, action.stop_at_soft_wraps),
13723 SelectionGoal::None,
13724 )
13725 });
13726 })
13727 }
13728
13729 pub fn select_to_end_of_line(
13730 &mut self,
13731 action: &SelectToEndOfLine,
13732 window: &mut Window,
13733 cx: &mut Context<Self>,
13734 ) {
13735 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13736 self.change_selections(Default::default(), window, cx, |s| {
13737 s.move_heads_with(|map, head, _| {
13738 (
13739 movement::line_end(map, head, action.stop_at_soft_wraps),
13740 SelectionGoal::None,
13741 )
13742 });
13743 })
13744 }
13745
13746 pub fn delete_to_end_of_line(
13747 &mut self,
13748 _: &DeleteToEndOfLine,
13749 window: &mut Window,
13750 cx: &mut Context<Self>,
13751 ) {
13752 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13753 self.transact(window, cx, |this, window, cx| {
13754 this.select_to_end_of_line(
13755 &SelectToEndOfLine {
13756 stop_at_soft_wraps: false,
13757 },
13758 window,
13759 cx,
13760 );
13761 this.delete(&Delete, window, cx);
13762 });
13763 }
13764
13765 pub fn cut_to_end_of_line(
13766 &mut self,
13767 action: &CutToEndOfLine,
13768 window: &mut Window,
13769 cx: &mut Context<Self>,
13770 ) {
13771 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13772 self.transact(window, cx, |this, window, cx| {
13773 this.select_to_end_of_line(
13774 &SelectToEndOfLine {
13775 stop_at_soft_wraps: false,
13776 },
13777 window,
13778 cx,
13779 );
13780 if !action.stop_at_newlines {
13781 this.change_selections(Default::default(), window, cx, |s| {
13782 s.move_with(|_, sel| {
13783 if sel.is_empty() {
13784 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13785 }
13786 });
13787 });
13788 }
13789 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13790 let item = this.cut_common(false, window, cx);
13791 cx.write_to_clipboard(item);
13792 });
13793 }
13794
13795 pub fn move_to_start_of_paragraph(
13796 &mut self,
13797 _: &MoveToStartOfParagraph,
13798 window: &mut Window,
13799 cx: &mut Context<Self>,
13800 ) {
13801 if matches!(self.mode, EditorMode::SingleLine) {
13802 cx.propagate();
13803 return;
13804 }
13805 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13806 self.change_selections(Default::default(), window, cx, |s| {
13807 s.move_with(|map, selection| {
13808 selection.collapse_to(
13809 movement::start_of_paragraph(map, selection.head(), 1),
13810 SelectionGoal::None,
13811 )
13812 });
13813 })
13814 }
13815
13816 pub fn move_to_end_of_paragraph(
13817 &mut self,
13818 _: &MoveToEndOfParagraph,
13819 window: &mut Window,
13820 cx: &mut Context<Self>,
13821 ) {
13822 if matches!(self.mode, EditorMode::SingleLine) {
13823 cx.propagate();
13824 return;
13825 }
13826 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13827 self.change_selections(Default::default(), window, cx, |s| {
13828 s.move_with(|map, selection| {
13829 selection.collapse_to(
13830 movement::end_of_paragraph(map, selection.head(), 1),
13831 SelectionGoal::None,
13832 )
13833 });
13834 })
13835 }
13836
13837 pub fn select_to_start_of_paragraph(
13838 &mut self,
13839 _: &SelectToStartOfParagraph,
13840 window: &mut Window,
13841 cx: &mut Context<Self>,
13842 ) {
13843 if matches!(self.mode, EditorMode::SingleLine) {
13844 cx.propagate();
13845 return;
13846 }
13847 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13848 self.change_selections(Default::default(), window, cx, |s| {
13849 s.move_heads_with(|map, head, _| {
13850 (
13851 movement::start_of_paragraph(map, head, 1),
13852 SelectionGoal::None,
13853 )
13854 });
13855 })
13856 }
13857
13858 pub fn select_to_end_of_paragraph(
13859 &mut self,
13860 _: &SelectToEndOfParagraph,
13861 window: &mut Window,
13862 cx: &mut Context<Self>,
13863 ) {
13864 if matches!(self.mode, EditorMode::SingleLine) {
13865 cx.propagate();
13866 return;
13867 }
13868 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13869 self.change_selections(Default::default(), window, cx, |s| {
13870 s.move_heads_with(|map, head, _| {
13871 (
13872 movement::end_of_paragraph(map, head, 1),
13873 SelectionGoal::None,
13874 )
13875 });
13876 })
13877 }
13878
13879 pub fn move_to_start_of_excerpt(
13880 &mut self,
13881 _: &MoveToStartOfExcerpt,
13882 window: &mut Window,
13883 cx: &mut Context<Self>,
13884 ) {
13885 if matches!(self.mode, EditorMode::SingleLine) {
13886 cx.propagate();
13887 return;
13888 }
13889 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13890 self.change_selections(Default::default(), window, cx, |s| {
13891 s.move_with(|map, selection| {
13892 selection.collapse_to(
13893 movement::start_of_excerpt(
13894 map,
13895 selection.head(),
13896 workspace::searchable::Direction::Prev,
13897 ),
13898 SelectionGoal::None,
13899 )
13900 });
13901 })
13902 }
13903
13904 pub fn move_to_start_of_next_excerpt(
13905 &mut self,
13906 _: &MoveToStartOfNextExcerpt,
13907 window: &mut Window,
13908 cx: &mut Context<Self>,
13909 ) {
13910 if matches!(self.mode, EditorMode::SingleLine) {
13911 cx.propagate();
13912 return;
13913 }
13914
13915 self.change_selections(Default::default(), window, cx, |s| {
13916 s.move_with(|map, selection| {
13917 selection.collapse_to(
13918 movement::start_of_excerpt(
13919 map,
13920 selection.head(),
13921 workspace::searchable::Direction::Next,
13922 ),
13923 SelectionGoal::None,
13924 )
13925 });
13926 })
13927 }
13928
13929 pub fn move_to_end_of_excerpt(
13930 &mut self,
13931 _: &MoveToEndOfExcerpt,
13932 window: &mut Window,
13933 cx: &mut Context<Self>,
13934 ) {
13935 if matches!(self.mode, EditorMode::SingleLine) {
13936 cx.propagate();
13937 return;
13938 }
13939 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13940 self.change_selections(Default::default(), window, cx, |s| {
13941 s.move_with(|map, selection| {
13942 selection.collapse_to(
13943 movement::end_of_excerpt(
13944 map,
13945 selection.head(),
13946 workspace::searchable::Direction::Next,
13947 ),
13948 SelectionGoal::None,
13949 )
13950 });
13951 })
13952 }
13953
13954 pub fn move_to_end_of_previous_excerpt(
13955 &mut self,
13956 _: &MoveToEndOfPreviousExcerpt,
13957 window: &mut Window,
13958 cx: &mut Context<Self>,
13959 ) {
13960 if matches!(self.mode, EditorMode::SingleLine) {
13961 cx.propagate();
13962 return;
13963 }
13964 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13965 self.change_selections(Default::default(), window, cx, |s| {
13966 s.move_with(|map, selection| {
13967 selection.collapse_to(
13968 movement::end_of_excerpt(
13969 map,
13970 selection.head(),
13971 workspace::searchable::Direction::Prev,
13972 ),
13973 SelectionGoal::None,
13974 )
13975 });
13976 })
13977 }
13978
13979 pub fn select_to_start_of_excerpt(
13980 &mut self,
13981 _: &SelectToStartOfExcerpt,
13982 window: &mut Window,
13983 cx: &mut Context<Self>,
13984 ) {
13985 if matches!(self.mode, EditorMode::SingleLine) {
13986 cx.propagate();
13987 return;
13988 }
13989 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13990 self.change_selections(Default::default(), window, cx, |s| {
13991 s.move_heads_with(|map, head, _| {
13992 (
13993 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13994 SelectionGoal::None,
13995 )
13996 });
13997 })
13998 }
13999
14000 pub fn select_to_start_of_next_excerpt(
14001 &mut self,
14002 _: &SelectToStartOfNextExcerpt,
14003 window: &mut Window,
14004 cx: &mut Context<Self>,
14005 ) {
14006 if matches!(self.mode, EditorMode::SingleLine) {
14007 cx.propagate();
14008 return;
14009 }
14010 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14011 self.change_selections(Default::default(), window, cx, |s| {
14012 s.move_heads_with(|map, head, _| {
14013 (
14014 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14015 SelectionGoal::None,
14016 )
14017 });
14018 })
14019 }
14020
14021 pub fn select_to_end_of_excerpt(
14022 &mut self,
14023 _: &SelectToEndOfExcerpt,
14024 window: &mut Window,
14025 cx: &mut Context<Self>,
14026 ) {
14027 if matches!(self.mode, EditorMode::SingleLine) {
14028 cx.propagate();
14029 return;
14030 }
14031 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14032 self.change_selections(Default::default(), window, cx, |s| {
14033 s.move_heads_with(|map, head, _| {
14034 (
14035 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14036 SelectionGoal::None,
14037 )
14038 });
14039 })
14040 }
14041
14042 pub fn select_to_end_of_previous_excerpt(
14043 &mut self,
14044 _: &SelectToEndOfPreviousExcerpt,
14045 window: &mut Window,
14046 cx: &mut Context<Self>,
14047 ) {
14048 if matches!(self.mode, EditorMode::SingleLine) {
14049 cx.propagate();
14050 return;
14051 }
14052 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14053 self.change_selections(Default::default(), window, cx, |s| {
14054 s.move_heads_with(|map, head, _| {
14055 (
14056 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14057 SelectionGoal::None,
14058 )
14059 });
14060 })
14061 }
14062
14063 pub fn move_to_beginning(
14064 &mut self,
14065 _: &MoveToBeginning,
14066 window: &mut Window,
14067 cx: &mut Context<Self>,
14068 ) {
14069 if matches!(self.mode, EditorMode::SingleLine) {
14070 cx.propagate();
14071 return;
14072 }
14073 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14074 self.change_selections(Default::default(), window, cx, |s| {
14075 s.select_ranges(vec![0..0]);
14076 });
14077 }
14078
14079 pub fn select_to_beginning(
14080 &mut self,
14081 _: &SelectToBeginning,
14082 window: &mut Window,
14083 cx: &mut Context<Self>,
14084 ) {
14085 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14086 selection.set_head(Point::zero(), SelectionGoal::None);
14087 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14088 self.change_selections(Default::default(), window, cx, |s| {
14089 s.select(vec![selection]);
14090 });
14091 }
14092
14093 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14094 if matches!(self.mode, EditorMode::SingleLine) {
14095 cx.propagate();
14096 return;
14097 }
14098 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14099 let cursor = self.buffer.read(cx).read(cx).len();
14100 self.change_selections(Default::default(), window, cx, |s| {
14101 s.select_ranges(vec![cursor..cursor])
14102 });
14103 }
14104
14105 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14106 self.nav_history = nav_history;
14107 }
14108
14109 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14110 self.nav_history.as_ref()
14111 }
14112
14113 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14114 self.push_to_nav_history(
14115 self.selections.newest_anchor().head(),
14116 None,
14117 false,
14118 true,
14119 cx,
14120 );
14121 }
14122
14123 fn push_to_nav_history(
14124 &mut self,
14125 cursor_anchor: Anchor,
14126 new_position: Option<Point>,
14127 is_deactivate: bool,
14128 always: bool,
14129 cx: &mut Context<Self>,
14130 ) {
14131 if let Some(nav_history) = self.nav_history.as_mut() {
14132 let buffer = self.buffer.read(cx).read(cx);
14133 let cursor_position = cursor_anchor.to_point(&buffer);
14134 let scroll_state = self.scroll_manager.anchor();
14135 let scroll_top_row = scroll_state.top_row(&buffer);
14136 drop(buffer);
14137
14138 if let Some(new_position) = new_position {
14139 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14140 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14141 return;
14142 }
14143 }
14144
14145 nav_history.push(
14146 Some(NavigationData {
14147 cursor_anchor,
14148 cursor_position,
14149 scroll_anchor: scroll_state,
14150 scroll_top_row,
14151 }),
14152 cx,
14153 );
14154 cx.emit(EditorEvent::PushedToNavHistory {
14155 anchor: cursor_anchor,
14156 is_deactivate,
14157 })
14158 }
14159 }
14160
14161 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14162 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14163 let buffer = self.buffer.read(cx).snapshot(cx);
14164 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14165 selection.set_head(buffer.len(), SelectionGoal::None);
14166 self.change_selections(Default::default(), window, cx, |s| {
14167 s.select(vec![selection]);
14168 });
14169 }
14170
14171 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14172 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14173 let end = self.buffer.read(cx).read(cx).len();
14174 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14175 s.select_ranges(vec![0..end]);
14176 });
14177 }
14178
14179 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14180 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14181 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14182 let mut selections = self.selections.all::<Point>(&display_map);
14183 let max_point = display_map.buffer_snapshot().max_point();
14184 for selection in &mut selections {
14185 let rows = selection.spanned_rows(true, &display_map);
14186 selection.start = Point::new(rows.start.0, 0);
14187 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14188 selection.reversed = false;
14189 }
14190 self.change_selections(Default::default(), window, cx, |s| {
14191 s.select(selections);
14192 });
14193 }
14194
14195 pub fn split_selection_into_lines(
14196 &mut self,
14197 action: &SplitSelectionIntoLines,
14198 window: &mut Window,
14199 cx: &mut Context<Self>,
14200 ) {
14201 let selections = self
14202 .selections
14203 .all::<Point>(&self.display_snapshot(cx))
14204 .into_iter()
14205 .map(|selection| selection.start..selection.end)
14206 .collect::<Vec<_>>();
14207 self.unfold_ranges(&selections, true, true, cx);
14208
14209 let mut new_selection_ranges = Vec::new();
14210 {
14211 let buffer = self.buffer.read(cx).read(cx);
14212 for selection in selections {
14213 for row in selection.start.row..selection.end.row {
14214 let line_start = Point::new(row, 0);
14215 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14216
14217 if action.keep_selections {
14218 // Keep the selection range for each line
14219 let selection_start = if row == selection.start.row {
14220 selection.start
14221 } else {
14222 line_start
14223 };
14224 new_selection_ranges.push(selection_start..line_end);
14225 } else {
14226 // Collapse to cursor at end of line
14227 new_selection_ranges.push(line_end..line_end);
14228 }
14229 }
14230
14231 let is_multiline_selection = selection.start.row != selection.end.row;
14232 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14233 // so this action feels more ergonomic when paired with other selection operations
14234 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14235 if !should_skip_last {
14236 if action.keep_selections {
14237 if is_multiline_selection {
14238 let line_start = Point::new(selection.end.row, 0);
14239 new_selection_ranges.push(line_start..selection.end);
14240 } else {
14241 new_selection_ranges.push(selection.start..selection.end);
14242 }
14243 } else {
14244 new_selection_ranges.push(selection.end..selection.end);
14245 }
14246 }
14247 }
14248 }
14249 self.change_selections(Default::default(), window, cx, |s| {
14250 s.select_ranges(new_selection_ranges);
14251 });
14252 }
14253
14254 pub fn add_selection_above(
14255 &mut self,
14256 action: &AddSelectionAbove,
14257 window: &mut Window,
14258 cx: &mut Context<Self>,
14259 ) {
14260 self.add_selection(true, action.skip_soft_wrap, window, cx);
14261 }
14262
14263 pub fn add_selection_below(
14264 &mut self,
14265 action: &AddSelectionBelow,
14266 window: &mut Window,
14267 cx: &mut Context<Self>,
14268 ) {
14269 self.add_selection(false, action.skip_soft_wrap, window, cx);
14270 }
14271
14272 fn add_selection(
14273 &mut self,
14274 above: bool,
14275 skip_soft_wrap: bool,
14276 window: &mut Window,
14277 cx: &mut Context<Self>,
14278 ) {
14279 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14280
14281 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14282 let all_selections = self.selections.all::<Point>(&display_map);
14283 let text_layout_details = self.text_layout_details(window);
14284
14285 let (mut columnar_selections, new_selections_to_columnarize) = {
14286 if let Some(state) = self.add_selections_state.as_ref() {
14287 let columnar_selection_ids: HashSet<_> = state
14288 .groups
14289 .iter()
14290 .flat_map(|group| group.stack.iter())
14291 .copied()
14292 .collect();
14293
14294 all_selections
14295 .into_iter()
14296 .partition(|s| columnar_selection_ids.contains(&s.id))
14297 } else {
14298 (Vec::new(), all_selections)
14299 }
14300 };
14301
14302 let mut state = self
14303 .add_selections_state
14304 .take()
14305 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14306
14307 for selection in new_selections_to_columnarize {
14308 let range = selection.display_range(&display_map).sorted();
14309 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14310 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14311 let positions = start_x.min(end_x)..start_x.max(end_x);
14312 let mut stack = Vec::new();
14313 for row in range.start.row().0..=range.end.row().0 {
14314 if let Some(selection) = self.selections.build_columnar_selection(
14315 &display_map,
14316 DisplayRow(row),
14317 &positions,
14318 selection.reversed,
14319 &text_layout_details,
14320 ) {
14321 stack.push(selection.id);
14322 columnar_selections.push(selection);
14323 }
14324 }
14325 if !stack.is_empty() {
14326 if above {
14327 stack.reverse();
14328 }
14329 state.groups.push(AddSelectionsGroup { above, stack });
14330 }
14331 }
14332
14333 let mut final_selections = Vec::new();
14334 let end_row = if above {
14335 DisplayRow(0)
14336 } else {
14337 display_map.max_point().row()
14338 };
14339
14340 let mut last_added_item_per_group = HashMap::default();
14341 for group in state.groups.iter_mut() {
14342 if let Some(last_id) = group.stack.last() {
14343 last_added_item_per_group.insert(*last_id, group);
14344 }
14345 }
14346
14347 for selection in columnar_selections {
14348 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14349 if above == group.above {
14350 let range = selection.display_range(&display_map).sorted();
14351 debug_assert_eq!(range.start.row(), range.end.row());
14352 let mut row = range.start.row();
14353 let positions =
14354 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14355 Pixels::from(start)..Pixels::from(end)
14356 } else {
14357 let start_x =
14358 display_map.x_for_display_point(range.start, &text_layout_details);
14359 let end_x =
14360 display_map.x_for_display_point(range.end, &text_layout_details);
14361 start_x.min(end_x)..start_x.max(end_x)
14362 };
14363
14364 let mut maybe_new_selection = None;
14365 let direction = if above { -1 } else { 1 };
14366
14367 while row != end_row {
14368 if skip_soft_wrap {
14369 row = display_map
14370 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14371 .row();
14372 } else if above {
14373 row.0 -= 1;
14374 } else {
14375 row.0 += 1;
14376 }
14377
14378 if let Some(new_selection) = self.selections.build_columnar_selection(
14379 &display_map,
14380 row,
14381 &positions,
14382 selection.reversed,
14383 &text_layout_details,
14384 ) {
14385 maybe_new_selection = Some(new_selection);
14386 break;
14387 }
14388 }
14389
14390 if let Some(new_selection) = maybe_new_selection {
14391 group.stack.push(new_selection.id);
14392 if above {
14393 final_selections.push(new_selection);
14394 final_selections.push(selection);
14395 } else {
14396 final_selections.push(selection);
14397 final_selections.push(new_selection);
14398 }
14399 } else {
14400 final_selections.push(selection);
14401 }
14402 } else {
14403 group.stack.pop();
14404 }
14405 } else {
14406 final_selections.push(selection);
14407 }
14408 }
14409
14410 self.change_selections(Default::default(), window, cx, |s| {
14411 s.select(final_selections);
14412 });
14413
14414 let final_selection_ids: HashSet<_> = self
14415 .selections
14416 .all::<Point>(&display_map)
14417 .iter()
14418 .map(|s| s.id)
14419 .collect();
14420 state.groups.retain_mut(|group| {
14421 // selections might get merged above so we remove invalid items from stacks
14422 group.stack.retain(|id| final_selection_ids.contains(id));
14423
14424 // single selection in stack can be treated as initial state
14425 group.stack.len() > 1
14426 });
14427
14428 if !state.groups.is_empty() {
14429 self.add_selections_state = Some(state);
14430 }
14431 }
14432
14433 fn select_match_ranges(
14434 &mut self,
14435 range: Range<usize>,
14436 reversed: bool,
14437 replace_newest: bool,
14438 auto_scroll: Option<Autoscroll>,
14439 window: &mut Window,
14440 cx: &mut Context<Editor>,
14441 ) {
14442 self.unfold_ranges(
14443 std::slice::from_ref(&range),
14444 false,
14445 auto_scroll.is_some(),
14446 cx,
14447 );
14448 let effects = if let Some(scroll) = auto_scroll {
14449 SelectionEffects::scroll(scroll)
14450 } else {
14451 SelectionEffects::no_scroll()
14452 };
14453 self.change_selections(effects, window, cx, |s| {
14454 if replace_newest {
14455 s.delete(s.newest_anchor().id);
14456 }
14457 if reversed {
14458 s.insert_range(range.end..range.start);
14459 } else {
14460 s.insert_range(range);
14461 }
14462 });
14463 }
14464
14465 pub fn select_next_match_internal(
14466 &mut self,
14467 display_map: &DisplaySnapshot,
14468 replace_newest: bool,
14469 autoscroll: Option<Autoscroll>,
14470 window: &mut Window,
14471 cx: &mut Context<Self>,
14472 ) -> Result<()> {
14473 let buffer = display_map.buffer_snapshot();
14474 let mut selections = self.selections.all::<usize>(&display_map);
14475 if let Some(mut select_next_state) = self.select_next_state.take() {
14476 let query = &select_next_state.query;
14477 if !select_next_state.done {
14478 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14479 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14480 let mut next_selected_range = None;
14481
14482 let bytes_after_last_selection =
14483 buffer.bytes_in_range(last_selection.end..buffer.len());
14484 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14485 let query_matches = query
14486 .stream_find_iter(bytes_after_last_selection)
14487 .map(|result| (last_selection.end, result))
14488 .chain(
14489 query
14490 .stream_find_iter(bytes_before_first_selection)
14491 .map(|result| (0, result)),
14492 );
14493
14494 for (start_offset, query_match) in query_matches {
14495 let query_match = query_match.unwrap(); // can only fail due to I/O
14496 let offset_range =
14497 start_offset + query_match.start()..start_offset + query_match.end();
14498
14499 if !select_next_state.wordwise
14500 || (!buffer.is_inside_word(offset_range.start, None)
14501 && !buffer.is_inside_word(offset_range.end, None))
14502 {
14503 let idx = selections
14504 .partition_point(|selection| selection.end <= offset_range.start);
14505 let overlaps = selections
14506 .get(idx)
14507 .map_or(false, |selection| selection.start < offset_range.end);
14508
14509 if !overlaps {
14510 next_selected_range = Some(offset_range);
14511 break;
14512 }
14513 }
14514 }
14515
14516 if let Some(next_selected_range) = next_selected_range {
14517 self.select_match_ranges(
14518 next_selected_range,
14519 last_selection.reversed,
14520 replace_newest,
14521 autoscroll,
14522 window,
14523 cx,
14524 );
14525 } else {
14526 select_next_state.done = true;
14527 }
14528 }
14529
14530 self.select_next_state = Some(select_next_state);
14531 } else {
14532 let mut only_carets = true;
14533 let mut same_text_selected = true;
14534 let mut selected_text = None;
14535
14536 let mut selections_iter = selections.iter().peekable();
14537 while let Some(selection) = selections_iter.next() {
14538 if selection.start != selection.end {
14539 only_carets = false;
14540 }
14541
14542 if same_text_selected {
14543 if selected_text.is_none() {
14544 selected_text =
14545 Some(buffer.text_for_range(selection.range()).collect::<String>());
14546 }
14547
14548 if let Some(next_selection) = selections_iter.peek() {
14549 if next_selection.range().len() == selection.range().len() {
14550 let next_selected_text = buffer
14551 .text_for_range(next_selection.range())
14552 .collect::<String>();
14553 if Some(next_selected_text) != selected_text {
14554 same_text_selected = false;
14555 selected_text = None;
14556 }
14557 } else {
14558 same_text_selected = false;
14559 selected_text = None;
14560 }
14561 }
14562 }
14563 }
14564
14565 if only_carets {
14566 for selection in &mut selections {
14567 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14568 selection.start = word_range.start;
14569 selection.end = word_range.end;
14570 selection.goal = SelectionGoal::None;
14571 selection.reversed = false;
14572 self.select_match_ranges(
14573 selection.start..selection.end,
14574 selection.reversed,
14575 replace_newest,
14576 autoscroll,
14577 window,
14578 cx,
14579 );
14580 }
14581
14582 if selections.len() == 1 {
14583 let selection = selections
14584 .last()
14585 .expect("ensured that there's only one selection");
14586 let query = buffer
14587 .text_for_range(selection.start..selection.end)
14588 .collect::<String>();
14589 let is_empty = query.is_empty();
14590 let select_state = SelectNextState {
14591 query: AhoCorasick::new(&[query])?,
14592 wordwise: true,
14593 done: is_empty,
14594 };
14595 self.select_next_state = Some(select_state);
14596 } else {
14597 self.select_next_state = None;
14598 }
14599 } else if let Some(selected_text) = selected_text {
14600 self.select_next_state = Some(SelectNextState {
14601 query: AhoCorasick::new(&[selected_text])?,
14602 wordwise: false,
14603 done: false,
14604 });
14605 self.select_next_match_internal(
14606 display_map,
14607 replace_newest,
14608 autoscroll,
14609 window,
14610 cx,
14611 )?;
14612 }
14613 }
14614 Ok(())
14615 }
14616
14617 pub fn select_all_matches(
14618 &mut self,
14619 _action: &SelectAllMatches,
14620 window: &mut Window,
14621 cx: &mut Context<Self>,
14622 ) -> Result<()> {
14623 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14624
14625 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14626
14627 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14628 let Some(select_next_state) = self.select_next_state.as_mut() else {
14629 return Ok(());
14630 };
14631 if select_next_state.done {
14632 return Ok(());
14633 }
14634
14635 let mut new_selections = Vec::new();
14636
14637 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14638 let buffer = display_map.buffer_snapshot();
14639 let query_matches = select_next_state
14640 .query
14641 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14642
14643 for query_match in query_matches.into_iter() {
14644 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14645 let offset_range = if reversed {
14646 query_match.end()..query_match.start()
14647 } else {
14648 query_match.start()..query_match.end()
14649 };
14650
14651 if !select_next_state.wordwise
14652 || (!buffer.is_inside_word(offset_range.start, None)
14653 && !buffer.is_inside_word(offset_range.end, None))
14654 {
14655 new_selections.push(offset_range.start..offset_range.end);
14656 }
14657 }
14658
14659 select_next_state.done = true;
14660
14661 if new_selections.is_empty() {
14662 log::error!("bug: new_selections is empty in select_all_matches");
14663 return Ok(());
14664 }
14665
14666 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14667 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14668 selections.select_ranges(new_selections)
14669 });
14670
14671 Ok(())
14672 }
14673
14674 pub fn select_next(
14675 &mut self,
14676 action: &SelectNext,
14677 window: &mut Window,
14678 cx: &mut Context<Self>,
14679 ) -> Result<()> {
14680 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14681 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14682 self.select_next_match_internal(
14683 &display_map,
14684 action.replace_newest,
14685 Some(Autoscroll::newest()),
14686 window,
14687 cx,
14688 )?;
14689 Ok(())
14690 }
14691
14692 pub fn select_previous(
14693 &mut self,
14694 action: &SelectPrevious,
14695 window: &mut Window,
14696 cx: &mut Context<Self>,
14697 ) -> Result<()> {
14698 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14699 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14700 let buffer = display_map.buffer_snapshot();
14701 let mut selections = self.selections.all::<usize>(&display_map);
14702 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14703 let query = &select_prev_state.query;
14704 if !select_prev_state.done {
14705 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14706 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14707 let mut next_selected_range = None;
14708 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14709 let bytes_before_last_selection =
14710 buffer.reversed_bytes_in_range(0..last_selection.start);
14711 let bytes_after_first_selection =
14712 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14713 let query_matches = query
14714 .stream_find_iter(bytes_before_last_selection)
14715 .map(|result| (last_selection.start, result))
14716 .chain(
14717 query
14718 .stream_find_iter(bytes_after_first_selection)
14719 .map(|result| (buffer.len(), result)),
14720 );
14721 for (end_offset, query_match) in query_matches {
14722 let query_match = query_match.unwrap(); // can only fail due to I/O
14723 let offset_range =
14724 end_offset - query_match.end()..end_offset - query_match.start();
14725
14726 if !select_prev_state.wordwise
14727 || (!buffer.is_inside_word(offset_range.start, None)
14728 && !buffer.is_inside_word(offset_range.end, None))
14729 {
14730 next_selected_range = Some(offset_range);
14731 break;
14732 }
14733 }
14734
14735 if let Some(next_selected_range) = next_selected_range {
14736 self.select_match_ranges(
14737 next_selected_range,
14738 last_selection.reversed,
14739 action.replace_newest,
14740 Some(Autoscroll::newest()),
14741 window,
14742 cx,
14743 );
14744 } else {
14745 select_prev_state.done = true;
14746 }
14747 }
14748
14749 self.select_prev_state = Some(select_prev_state);
14750 } else {
14751 let mut only_carets = true;
14752 let mut same_text_selected = true;
14753 let mut selected_text = None;
14754
14755 let mut selections_iter = selections.iter().peekable();
14756 while let Some(selection) = selections_iter.next() {
14757 if selection.start != selection.end {
14758 only_carets = false;
14759 }
14760
14761 if same_text_selected {
14762 if selected_text.is_none() {
14763 selected_text =
14764 Some(buffer.text_for_range(selection.range()).collect::<String>());
14765 }
14766
14767 if let Some(next_selection) = selections_iter.peek() {
14768 if next_selection.range().len() == selection.range().len() {
14769 let next_selected_text = buffer
14770 .text_for_range(next_selection.range())
14771 .collect::<String>();
14772 if Some(next_selected_text) != selected_text {
14773 same_text_selected = false;
14774 selected_text = None;
14775 }
14776 } else {
14777 same_text_selected = false;
14778 selected_text = None;
14779 }
14780 }
14781 }
14782 }
14783
14784 if only_carets {
14785 for selection in &mut selections {
14786 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14787 selection.start = word_range.start;
14788 selection.end = word_range.end;
14789 selection.goal = SelectionGoal::None;
14790 selection.reversed = false;
14791 self.select_match_ranges(
14792 selection.start..selection.end,
14793 selection.reversed,
14794 action.replace_newest,
14795 Some(Autoscroll::newest()),
14796 window,
14797 cx,
14798 );
14799 }
14800 if selections.len() == 1 {
14801 let selection = selections
14802 .last()
14803 .expect("ensured that there's only one selection");
14804 let query = buffer
14805 .text_for_range(selection.start..selection.end)
14806 .collect::<String>();
14807 let is_empty = query.is_empty();
14808 let select_state = SelectNextState {
14809 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14810 wordwise: true,
14811 done: is_empty,
14812 };
14813 self.select_prev_state = Some(select_state);
14814 } else {
14815 self.select_prev_state = None;
14816 }
14817 } else if let Some(selected_text) = selected_text {
14818 self.select_prev_state = Some(SelectNextState {
14819 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14820 wordwise: false,
14821 done: false,
14822 });
14823 self.select_previous(action, window, cx)?;
14824 }
14825 }
14826 Ok(())
14827 }
14828
14829 pub fn find_next_match(
14830 &mut self,
14831 _: &FindNextMatch,
14832 window: &mut Window,
14833 cx: &mut Context<Self>,
14834 ) -> Result<()> {
14835 let selections = self.selections.disjoint_anchors_arc();
14836 match selections.first() {
14837 Some(first) if selections.len() >= 2 => {
14838 self.change_selections(Default::default(), window, cx, |s| {
14839 s.select_ranges([first.range()]);
14840 });
14841 }
14842 _ => self.select_next(
14843 &SelectNext {
14844 replace_newest: true,
14845 },
14846 window,
14847 cx,
14848 )?,
14849 }
14850 Ok(())
14851 }
14852
14853 pub fn find_previous_match(
14854 &mut self,
14855 _: &FindPreviousMatch,
14856 window: &mut Window,
14857 cx: &mut Context<Self>,
14858 ) -> Result<()> {
14859 let selections = self.selections.disjoint_anchors_arc();
14860 match selections.last() {
14861 Some(last) if selections.len() >= 2 => {
14862 self.change_selections(Default::default(), window, cx, |s| {
14863 s.select_ranges([last.range()]);
14864 });
14865 }
14866 _ => self.select_previous(
14867 &SelectPrevious {
14868 replace_newest: true,
14869 },
14870 window,
14871 cx,
14872 )?,
14873 }
14874 Ok(())
14875 }
14876
14877 pub fn toggle_comments(
14878 &mut self,
14879 action: &ToggleComments,
14880 window: &mut Window,
14881 cx: &mut Context<Self>,
14882 ) {
14883 if self.read_only(cx) {
14884 return;
14885 }
14886 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14887 let text_layout_details = &self.text_layout_details(window);
14888 self.transact(window, cx, |this, window, cx| {
14889 let mut selections = this
14890 .selections
14891 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14892 let mut edits = Vec::new();
14893 let mut selection_edit_ranges = Vec::new();
14894 let mut last_toggled_row = None;
14895 let snapshot = this.buffer.read(cx).read(cx);
14896 let empty_str: Arc<str> = Arc::default();
14897 let mut suffixes_inserted = Vec::new();
14898 let ignore_indent = action.ignore_indent;
14899
14900 fn comment_prefix_range(
14901 snapshot: &MultiBufferSnapshot,
14902 row: MultiBufferRow,
14903 comment_prefix: &str,
14904 comment_prefix_whitespace: &str,
14905 ignore_indent: bool,
14906 ) -> Range<Point> {
14907 let indent_size = if ignore_indent {
14908 0
14909 } else {
14910 snapshot.indent_size_for_line(row).len
14911 };
14912
14913 let start = Point::new(row.0, indent_size);
14914
14915 let mut line_bytes = snapshot
14916 .bytes_in_range(start..snapshot.max_point())
14917 .flatten()
14918 .copied();
14919
14920 // If this line currently begins with the line comment prefix, then record
14921 // the range containing the prefix.
14922 if line_bytes
14923 .by_ref()
14924 .take(comment_prefix.len())
14925 .eq(comment_prefix.bytes())
14926 {
14927 // Include any whitespace that matches the comment prefix.
14928 let matching_whitespace_len = line_bytes
14929 .zip(comment_prefix_whitespace.bytes())
14930 .take_while(|(a, b)| a == b)
14931 .count() as u32;
14932 let end = Point::new(
14933 start.row,
14934 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14935 );
14936 start..end
14937 } else {
14938 start..start
14939 }
14940 }
14941
14942 fn comment_suffix_range(
14943 snapshot: &MultiBufferSnapshot,
14944 row: MultiBufferRow,
14945 comment_suffix: &str,
14946 comment_suffix_has_leading_space: bool,
14947 ) -> Range<Point> {
14948 let end = Point::new(row.0, snapshot.line_len(row));
14949 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14950
14951 let mut line_end_bytes = snapshot
14952 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14953 .flatten()
14954 .copied();
14955
14956 let leading_space_len = if suffix_start_column > 0
14957 && line_end_bytes.next() == Some(b' ')
14958 && comment_suffix_has_leading_space
14959 {
14960 1
14961 } else {
14962 0
14963 };
14964
14965 // If this line currently begins with the line comment prefix, then record
14966 // the range containing the prefix.
14967 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14968 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14969 start..end
14970 } else {
14971 end..end
14972 }
14973 }
14974
14975 // TODO: Handle selections that cross excerpts
14976 for selection in &mut selections {
14977 let start_column = snapshot
14978 .indent_size_for_line(MultiBufferRow(selection.start.row))
14979 .len;
14980 let language = if let Some(language) =
14981 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14982 {
14983 language
14984 } else {
14985 continue;
14986 };
14987
14988 selection_edit_ranges.clear();
14989
14990 // If multiple selections contain a given row, avoid processing that
14991 // row more than once.
14992 let mut start_row = MultiBufferRow(selection.start.row);
14993 if last_toggled_row == Some(start_row) {
14994 start_row = start_row.next_row();
14995 }
14996 let end_row =
14997 if selection.end.row > selection.start.row && selection.end.column == 0 {
14998 MultiBufferRow(selection.end.row - 1)
14999 } else {
15000 MultiBufferRow(selection.end.row)
15001 };
15002 last_toggled_row = Some(end_row);
15003
15004 if start_row > end_row {
15005 continue;
15006 }
15007
15008 // If the language has line comments, toggle those.
15009 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15010
15011 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15012 if ignore_indent {
15013 full_comment_prefixes = full_comment_prefixes
15014 .into_iter()
15015 .map(|s| Arc::from(s.trim_end()))
15016 .collect();
15017 }
15018
15019 if !full_comment_prefixes.is_empty() {
15020 let first_prefix = full_comment_prefixes
15021 .first()
15022 .expect("prefixes is non-empty");
15023 let prefix_trimmed_lengths = full_comment_prefixes
15024 .iter()
15025 .map(|p| p.trim_end_matches(' ').len())
15026 .collect::<SmallVec<[usize; 4]>>();
15027
15028 let mut all_selection_lines_are_comments = true;
15029
15030 for row in start_row.0..=end_row.0 {
15031 let row = MultiBufferRow(row);
15032 if start_row < end_row && snapshot.is_line_blank(row) {
15033 continue;
15034 }
15035
15036 let prefix_range = full_comment_prefixes
15037 .iter()
15038 .zip(prefix_trimmed_lengths.iter().copied())
15039 .map(|(prefix, trimmed_prefix_len)| {
15040 comment_prefix_range(
15041 snapshot.deref(),
15042 row,
15043 &prefix[..trimmed_prefix_len],
15044 &prefix[trimmed_prefix_len..],
15045 ignore_indent,
15046 )
15047 })
15048 .max_by_key(|range| range.end.column - range.start.column)
15049 .expect("prefixes is non-empty");
15050
15051 if prefix_range.is_empty() {
15052 all_selection_lines_are_comments = false;
15053 }
15054
15055 selection_edit_ranges.push(prefix_range);
15056 }
15057
15058 if all_selection_lines_are_comments {
15059 edits.extend(
15060 selection_edit_ranges
15061 .iter()
15062 .cloned()
15063 .map(|range| (range, empty_str.clone())),
15064 );
15065 } else {
15066 let min_column = selection_edit_ranges
15067 .iter()
15068 .map(|range| range.start.column)
15069 .min()
15070 .unwrap_or(0);
15071 edits.extend(selection_edit_ranges.iter().map(|range| {
15072 let position = Point::new(range.start.row, min_column);
15073 (position..position, first_prefix.clone())
15074 }));
15075 }
15076 } else if let Some(BlockCommentConfig {
15077 start: full_comment_prefix,
15078 end: comment_suffix,
15079 ..
15080 }) = language.block_comment()
15081 {
15082 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15083 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15084 let prefix_range = comment_prefix_range(
15085 snapshot.deref(),
15086 start_row,
15087 comment_prefix,
15088 comment_prefix_whitespace,
15089 ignore_indent,
15090 );
15091 let suffix_range = comment_suffix_range(
15092 snapshot.deref(),
15093 end_row,
15094 comment_suffix.trim_start_matches(' '),
15095 comment_suffix.starts_with(' '),
15096 );
15097
15098 if prefix_range.is_empty() || suffix_range.is_empty() {
15099 edits.push((
15100 prefix_range.start..prefix_range.start,
15101 full_comment_prefix.clone(),
15102 ));
15103 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15104 suffixes_inserted.push((end_row, comment_suffix.len()));
15105 } else {
15106 edits.push((prefix_range, empty_str.clone()));
15107 edits.push((suffix_range, empty_str.clone()));
15108 }
15109 } else {
15110 continue;
15111 }
15112 }
15113
15114 drop(snapshot);
15115 this.buffer.update(cx, |buffer, cx| {
15116 buffer.edit(edits, None, cx);
15117 });
15118
15119 // Adjust selections so that they end before any comment suffixes that
15120 // were inserted.
15121 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15122 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15123 let snapshot = this.buffer.read(cx).read(cx);
15124 for selection in &mut selections {
15125 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15126 match row.cmp(&MultiBufferRow(selection.end.row)) {
15127 Ordering::Less => {
15128 suffixes_inserted.next();
15129 continue;
15130 }
15131 Ordering::Greater => break,
15132 Ordering::Equal => {
15133 if selection.end.column == snapshot.line_len(row) {
15134 if selection.is_empty() {
15135 selection.start.column -= suffix_len as u32;
15136 }
15137 selection.end.column -= suffix_len as u32;
15138 }
15139 break;
15140 }
15141 }
15142 }
15143 }
15144
15145 drop(snapshot);
15146 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15147
15148 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15149 let selections_on_single_row = selections.windows(2).all(|selections| {
15150 selections[0].start.row == selections[1].start.row
15151 && selections[0].end.row == selections[1].end.row
15152 && selections[0].start.row == selections[0].end.row
15153 });
15154 let selections_selecting = selections
15155 .iter()
15156 .any(|selection| selection.start != selection.end);
15157 let advance_downwards = action.advance_downwards
15158 && selections_on_single_row
15159 && !selections_selecting
15160 && !matches!(this.mode, EditorMode::SingleLine);
15161
15162 if advance_downwards {
15163 let snapshot = this.buffer.read(cx).snapshot(cx);
15164
15165 this.change_selections(Default::default(), window, cx, |s| {
15166 s.move_cursors_with(|display_snapshot, display_point, _| {
15167 let mut point = display_point.to_point(display_snapshot);
15168 point.row += 1;
15169 point = snapshot.clip_point(point, Bias::Left);
15170 let display_point = point.to_display_point(display_snapshot);
15171 let goal = SelectionGoal::HorizontalPosition(
15172 display_snapshot
15173 .x_for_display_point(display_point, text_layout_details)
15174 .into(),
15175 );
15176 (display_point, goal)
15177 })
15178 });
15179 }
15180 });
15181 }
15182
15183 pub fn select_enclosing_symbol(
15184 &mut self,
15185 _: &SelectEnclosingSymbol,
15186 window: &mut Window,
15187 cx: &mut Context<Self>,
15188 ) {
15189 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15190
15191 let buffer = self.buffer.read(cx).snapshot(cx);
15192 let old_selections = self
15193 .selections
15194 .all::<usize>(&self.display_snapshot(cx))
15195 .into_boxed_slice();
15196
15197 fn update_selection(
15198 selection: &Selection<usize>,
15199 buffer_snap: &MultiBufferSnapshot,
15200 ) -> Option<Selection<usize>> {
15201 let cursor = selection.head();
15202 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15203 for symbol in symbols.iter().rev() {
15204 let start = symbol.range.start.to_offset(buffer_snap);
15205 let end = symbol.range.end.to_offset(buffer_snap);
15206 let new_range = start..end;
15207 if start < selection.start || end > selection.end {
15208 return Some(Selection {
15209 id: selection.id,
15210 start: new_range.start,
15211 end: new_range.end,
15212 goal: SelectionGoal::None,
15213 reversed: selection.reversed,
15214 });
15215 }
15216 }
15217 None
15218 }
15219
15220 let mut selected_larger_symbol = false;
15221 let new_selections = old_selections
15222 .iter()
15223 .map(|selection| match update_selection(selection, &buffer) {
15224 Some(new_selection) => {
15225 if new_selection.range() != selection.range() {
15226 selected_larger_symbol = true;
15227 }
15228 new_selection
15229 }
15230 None => selection.clone(),
15231 })
15232 .collect::<Vec<_>>();
15233
15234 if selected_larger_symbol {
15235 self.change_selections(Default::default(), window, cx, |s| {
15236 s.select(new_selections);
15237 });
15238 }
15239 }
15240
15241 pub fn select_larger_syntax_node(
15242 &mut self,
15243 _: &SelectLargerSyntaxNode,
15244 window: &mut Window,
15245 cx: &mut Context<Self>,
15246 ) {
15247 let Some(visible_row_count) = self.visible_row_count() else {
15248 return;
15249 };
15250 let old_selections: Box<[_]> = self
15251 .selections
15252 .all::<usize>(&self.display_snapshot(cx))
15253 .into();
15254 if old_selections.is_empty() {
15255 return;
15256 }
15257
15258 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15259
15260 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15261 let buffer = self.buffer.read(cx).snapshot(cx);
15262
15263 let mut selected_larger_node = false;
15264 let mut new_selections = old_selections
15265 .iter()
15266 .map(|selection| {
15267 let old_range = selection.start..selection.end;
15268
15269 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15270 // manually select word at selection
15271 if ["string_content", "inline"].contains(&node.kind()) {
15272 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15273 // ignore if word is already selected
15274 if !word_range.is_empty() && old_range != word_range {
15275 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15276 // only select word if start and end point belongs to same word
15277 if word_range == last_word_range {
15278 selected_larger_node = true;
15279 return Selection {
15280 id: selection.id,
15281 start: word_range.start,
15282 end: word_range.end,
15283 goal: SelectionGoal::None,
15284 reversed: selection.reversed,
15285 };
15286 }
15287 }
15288 }
15289 }
15290
15291 let mut new_range = old_range.clone();
15292 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15293 new_range = range;
15294 if !node.is_named() {
15295 continue;
15296 }
15297 if !display_map.intersects_fold(new_range.start)
15298 && !display_map.intersects_fold(new_range.end)
15299 {
15300 break;
15301 }
15302 }
15303
15304 selected_larger_node |= new_range != old_range;
15305 Selection {
15306 id: selection.id,
15307 start: new_range.start,
15308 end: new_range.end,
15309 goal: SelectionGoal::None,
15310 reversed: selection.reversed,
15311 }
15312 })
15313 .collect::<Vec<_>>();
15314
15315 if !selected_larger_node {
15316 return; // don't put this call in the history
15317 }
15318
15319 // scroll based on transformation done to the last selection created by the user
15320 let (last_old, last_new) = old_selections
15321 .last()
15322 .zip(new_selections.last().cloned())
15323 .expect("old_selections isn't empty");
15324
15325 // revert selection
15326 let is_selection_reversed = {
15327 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15328 new_selections.last_mut().expect("checked above").reversed =
15329 should_newest_selection_be_reversed;
15330 should_newest_selection_be_reversed
15331 };
15332
15333 if selected_larger_node {
15334 self.select_syntax_node_history.disable_clearing = true;
15335 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15336 s.select(new_selections.clone());
15337 });
15338 self.select_syntax_node_history.disable_clearing = false;
15339 }
15340
15341 let start_row = last_new.start.to_display_point(&display_map).row().0;
15342 let end_row = last_new.end.to_display_point(&display_map).row().0;
15343 let selection_height = end_row - start_row + 1;
15344 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15345
15346 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15347 let scroll_behavior = if fits_on_the_screen {
15348 self.request_autoscroll(Autoscroll::fit(), cx);
15349 SelectSyntaxNodeScrollBehavior::FitSelection
15350 } else if is_selection_reversed {
15351 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15352 SelectSyntaxNodeScrollBehavior::CursorTop
15353 } else {
15354 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15355 SelectSyntaxNodeScrollBehavior::CursorBottom
15356 };
15357
15358 self.select_syntax_node_history.push((
15359 old_selections,
15360 scroll_behavior,
15361 is_selection_reversed,
15362 ));
15363 }
15364
15365 pub fn select_smaller_syntax_node(
15366 &mut self,
15367 _: &SelectSmallerSyntaxNode,
15368 window: &mut Window,
15369 cx: &mut Context<Self>,
15370 ) {
15371 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15372
15373 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15374 self.select_syntax_node_history.pop()
15375 {
15376 if let Some(selection) = selections.last_mut() {
15377 selection.reversed = is_selection_reversed;
15378 }
15379
15380 self.select_syntax_node_history.disable_clearing = true;
15381 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15382 s.select(selections.to_vec());
15383 });
15384 self.select_syntax_node_history.disable_clearing = false;
15385
15386 match scroll_behavior {
15387 SelectSyntaxNodeScrollBehavior::CursorTop => {
15388 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15389 }
15390 SelectSyntaxNodeScrollBehavior::FitSelection => {
15391 self.request_autoscroll(Autoscroll::fit(), cx);
15392 }
15393 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15394 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15395 }
15396 }
15397 }
15398 }
15399
15400 pub fn unwrap_syntax_node(
15401 &mut self,
15402 _: &UnwrapSyntaxNode,
15403 window: &mut Window,
15404 cx: &mut Context<Self>,
15405 ) {
15406 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15407
15408 let buffer = self.buffer.read(cx).snapshot(cx);
15409 let selections = self
15410 .selections
15411 .all::<usize>(&self.display_snapshot(cx))
15412 .into_iter()
15413 // subtracting the offset requires sorting
15414 .sorted_by_key(|i| i.start);
15415
15416 let full_edits = selections
15417 .into_iter()
15418 .filter_map(|selection| {
15419 let child = if selection.is_empty()
15420 && let Some((_, ancestor_range)) =
15421 buffer.syntax_ancestor(selection.start..selection.end)
15422 {
15423 ancestor_range
15424 } else {
15425 selection.range()
15426 };
15427
15428 let mut parent = child.clone();
15429 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15430 parent = ancestor_range;
15431 if parent.start < child.start || parent.end > child.end {
15432 break;
15433 }
15434 }
15435
15436 if parent == child {
15437 return None;
15438 }
15439 let text = buffer.text_for_range(child).collect::<String>();
15440 Some((selection.id, parent, text))
15441 })
15442 .collect::<Vec<_>>();
15443 if full_edits.is_empty() {
15444 return;
15445 }
15446
15447 self.transact(window, cx, |this, window, cx| {
15448 this.buffer.update(cx, |buffer, cx| {
15449 buffer.edit(
15450 full_edits
15451 .iter()
15452 .map(|(_, p, t)| (p.clone(), t.clone()))
15453 .collect::<Vec<_>>(),
15454 None,
15455 cx,
15456 );
15457 });
15458 this.change_selections(Default::default(), window, cx, |s| {
15459 let mut offset = 0;
15460 let mut selections = vec![];
15461 for (id, parent, text) in full_edits {
15462 let start = parent.start - offset;
15463 offset += parent.len() - text.len();
15464 selections.push(Selection {
15465 id,
15466 start,
15467 end: start + text.len(),
15468 reversed: false,
15469 goal: Default::default(),
15470 });
15471 }
15472 s.select(selections);
15473 });
15474 });
15475 }
15476
15477 pub fn select_next_syntax_node(
15478 &mut self,
15479 _: &SelectNextSyntaxNode,
15480 window: &mut Window,
15481 cx: &mut Context<Self>,
15482 ) {
15483 let old_selections: Box<[_]> = self
15484 .selections
15485 .all::<usize>(&self.display_snapshot(cx))
15486 .into();
15487 if old_selections.is_empty() {
15488 return;
15489 }
15490
15491 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15492
15493 let buffer = self.buffer.read(cx).snapshot(cx);
15494 let mut selected_sibling = false;
15495
15496 let new_selections = old_selections
15497 .iter()
15498 .map(|selection| {
15499 let old_range = selection.start..selection.end;
15500
15501 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15502 let new_range = node.byte_range();
15503 selected_sibling = true;
15504 Selection {
15505 id: selection.id,
15506 start: new_range.start,
15507 end: new_range.end,
15508 goal: SelectionGoal::None,
15509 reversed: selection.reversed,
15510 }
15511 } else {
15512 selection.clone()
15513 }
15514 })
15515 .collect::<Vec<_>>();
15516
15517 if selected_sibling {
15518 self.change_selections(
15519 SelectionEffects::scroll(Autoscroll::fit()),
15520 window,
15521 cx,
15522 |s| {
15523 s.select(new_selections);
15524 },
15525 );
15526 }
15527 }
15528
15529 pub fn select_prev_syntax_node(
15530 &mut self,
15531 _: &SelectPreviousSyntaxNode,
15532 window: &mut Window,
15533 cx: &mut Context<Self>,
15534 ) {
15535 let old_selections: Box<[_]> = self
15536 .selections
15537 .all::<usize>(&self.display_snapshot(cx))
15538 .into();
15539 if old_selections.is_empty() {
15540 return;
15541 }
15542
15543 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15544
15545 let buffer = self.buffer.read(cx).snapshot(cx);
15546 let mut selected_sibling = false;
15547
15548 let new_selections = old_selections
15549 .iter()
15550 .map(|selection| {
15551 let old_range = selection.start..selection.end;
15552
15553 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15554 let new_range = node.byte_range();
15555 selected_sibling = true;
15556 Selection {
15557 id: selection.id,
15558 start: new_range.start,
15559 end: new_range.end,
15560 goal: SelectionGoal::None,
15561 reversed: selection.reversed,
15562 }
15563 } else {
15564 selection.clone()
15565 }
15566 })
15567 .collect::<Vec<_>>();
15568
15569 if selected_sibling {
15570 self.change_selections(
15571 SelectionEffects::scroll(Autoscroll::fit()),
15572 window,
15573 cx,
15574 |s| {
15575 s.select(new_selections);
15576 },
15577 );
15578 }
15579 }
15580
15581 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15582 if !EditorSettings::get_global(cx).gutter.runnables {
15583 self.clear_tasks();
15584 return Task::ready(());
15585 }
15586 let project = self.project().map(Entity::downgrade);
15587 let task_sources = self.lsp_task_sources(cx);
15588 let multi_buffer = self.buffer.downgrade();
15589 cx.spawn_in(window, async move |editor, cx| {
15590 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15591 let Some(project) = project.and_then(|p| p.upgrade()) else {
15592 return;
15593 };
15594 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15595 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15596 }) else {
15597 return;
15598 };
15599
15600 let hide_runnables = project
15601 .update(cx, |project, _| project.is_via_collab())
15602 .unwrap_or(true);
15603 if hide_runnables {
15604 return;
15605 }
15606 let new_rows =
15607 cx.background_spawn({
15608 let snapshot = display_snapshot.clone();
15609 async move {
15610 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15611 }
15612 })
15613 .await;
15614 let Ok(lsp_tasks) =
15615 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15616 else {
15617 return;
15618 };
15619 let lsp_tasks = lsp_tasks.await;
15620
15621 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15622 lsp_tasks
15623 .into_iter()
15624 .flat_map(|(kind, tasks)| {
15625 tasks.into_iter().filter_map(move |(location, task)| {
15626 Some((kind.clone(), location?, task))
15627 })
15628 })
15629 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15630 let buffer = location.target.buffer;
15631 let buffer_snapshot = buffer.read(cx).snapshot();
15632 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15633 |(excerpt_id, snapshot, _)| {
15634 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15635 display_snapshot
15636 .buffer_snapshot()
15637 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15638 } else {
15639 None
15640 }
15641 },
15642 );
15643 if let Some(offset) = offset {
15644 let task_buffer_range =
15645 location.target.range.to_point(&buffer_snapshot);
15646 let context_buffer_range =
15647 task_buffer_range.to_offset(&buffer_snapshot);
15648 let context_range = BufferOffset(context_buffer_range.start)
15649 ..BufferOffset(context_buffer_range.end);
15650
15651 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15652 .or_insert_with(|| RunnableTasks {
15653 templates: Vec::new(),
15654 offset,
15655 column: task_buffer_range.start.column,
15656 extra_variables: HashMap::default(),
15657 context_range,
15658 })
15659 .templates
15660 .push((kind, task.original_task().clone()));
15661 }
15662
15663 acc
15664 })
15665 }) else {
15666 return;
15667 };
15668
15669 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15670 buffer.language_settings(cx).tasks.prefer_lsp
15671 }) else {
15672 return;
15673 };
15674
15675 let rows = Self::runnable_rows(
15676 project,
15677 display_snapshot,
15678 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15679 new_rows,
15680 cx.clone(),
15681 )
15682 .await;
15683 editor
15684 .update(cx, |editor, _| {
15685 editor.clear_tasks();
15686 for (key, mut value) in rows {
15687 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15688 value.templates.extend(lsp_tasks.templates);
15689 }
15690
15691 editor.insert_tasks(key, value);
15692 }
15693 for (key, value) in lsp_tasks_by_rows {
15694 editor.insert_tasks(key, value);
15695 }
15696 })
15697 .ok();
15698 })
15699 }
15700 fn fetch_runnable_ranges(
15701 snapshot: &DisplaySnapshot,
15702 range: Range<Anchor>,
15703 ) -> Vec<language::RunnableRange> {
15704 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15705 }
15706
15707 fn runnable_rows(
15708 project: Entity<Project>,
15709 snapshot: DisplaySnapshot,
15710 prefer_lsp: bool,
15711 runnable_ranges: Vec<RunnableRange>,
15712 cx: AsyncWindowContext,
15713 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15714 cx.spawn(async move |cx| {
15715 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15716 for mut runnable in runnable_ranges {
15717 let Some(tasks) = cx
15718 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15719 .ok()
15720 else {
15721 continue;
15722 };
15723 let mut tasks = tasks.await;
15724
15725 if prefer_lsp {
15726 tasks.retain(|(task_kind, _)| {
15727 !matches!(task_kind, TaskSourceKind::Language { .. })
15728 });
15729 }
15730 if tasks.is_empty() {
15731 continue;
15732 }
15733
15734 let point = runnable
15735 .run_range
15736 .start
15737 .to_point(&snapshot.buffer_snapshot());
15738 let Some(row) = snapshot
15739 .buffer_snapshot()
15740 .buffer_line_for_row(MultiBufferRow(point.row))
15741 .map(|(_, range)| range.start.row)
15742 else {
15743 continue;
15744 };
15745
15746 let context_range =
15747 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15748 runnable_rows.push((
15749 (runnable.buffer_id, row),
15750 RunnableTasks {
15751 templates: tasks,
15752 offset: snapshot
15753 .buffer_snapshot()
15754 .anchor_before(runnable.run_range.start),
15755 context_range,
15756 column: point.column,
15757 extra_variables: runnable.extra_captures,
15758 },
15759 ));
15760 }
15761 runnable_rows
15762 })
15763 }
15764
15765 fn templates_with_tags(
15766 project: &Entity<Project>,
15767 runnable: &mut Runnable,
15768 cx: &mut App,
15769 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15770 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15771 let (worktree_id, file) = project
15772 .buffer_for_id(runnable.buffer, cx)
15773 .and_then(|buffer| buffer.read(cx).file())
15774 .map(|file| (file.worktree_id(cx), file.clone()))
15775 .unzip();
15776
15777 (
15778 project.task_store().read(cx).task_inventory().cloned(),
15779 worktree_id,
15780 file,
15781 )
15782 });
15783
15784 let tags = mem::take(&mut runnable.tags);
15785 let language = runnable.language.clone();
15786 cx.spawn(async move |cx| {
15787 let mut templates_with_tags = Vec::new();
15788 if let Some(inventory) = inventory {
15789 for RunnableTag(tag) in tags {
15790 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15791 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15792 }) else {
15793 return templates_with_tags;
15794 };
15795 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15796 move |(_, template)| {
15797 template.tags.iter().any(|source_tag| source_tag == &tag)
15798 },
15799 ));
15800 }
15801 }
15802 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15803
15804 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15805 // Strongest source wins; if we have worktree tag binding, prefer that to
15806 // global and language bindings;
15807 // if we have a global binding, prefer that to language binding.
15808 let first_mismatch = templates_with_tags
15809 .iter()
15810 .position(|(tag_source, _)| tag_source != leading_tag_source);
15811 if let Some(index) = first_mismatch {
15812 templates_with_tags.truncate(index);
15813 }
15814 }
15815
15816 templates_with_tags
15817 })
15818 }
15819
15820 pub fn move_to_enclosing_bracket(
15821 &mut self,
15822 _: &MoveToEnclosingBracket,
15823 window: &mut Window,
15824 cx: &mut Context<Self>,
15825 ) {
15826 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15827 self.change_selections(Default::default(), window, cx, |s| {
15828 s.move_offsets_with(|snapshot, selection| {
15829 let Some(enclosing_bracket_ranges) =
15830 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15831 else {
15832 return;
15833 };
15834
15835 let mut best_length = usize::MAX;
15836 let mut best_inside = false;
15837 let mut best_in_bracket_range = false;
15838 let mut best_destination = None;
15839 for (open, close) in enclosing_bracket_ranges {
15840 let close = close.to_inclusive();
15841 let length = close.end() - open.start;
15842 let inside = selection.start >= open.end && selection.end <= *close.start();
15843 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15844 || close.contains(&selection.head());
15845
15846 // If best is next to a bracket and current isn't, skip
15847 if !in_bracket_range && best_in_bracket_range {
15848 continue;
15849 }
15850
15851 // Prefer smaller lengths unless best is inside and current isn't
15852 if length > best_length && (best_inside || !inside) {
15853 continue;
15854 }
15855
15856 best_length = length;
15857 best_inside = inside;
15858 best_in_bracket_range = in_bracket_range;
15859 best_destination = Some(
15860 if close.contains(&selection.start) && close.contains(&selection.end) {
15861 if inside { open.end } else { open.start }
15862 } else if inside {
15863 *close.start()
15864 } else {
15865 *close.end()
15866 },
15867 );
15868 }
15869
15870 if let Some(destination) = best_destination {
15871 selection.collapse_to(destination, SelectionGoal::None);
15872 }
15873 })
15874 });
15875 }
15876
15877 pub fn undo_selection(
15878 &mut self,
15879 _: &UndoSelection,
15880 window: &mut Window,
15881 cx: &mut Context<Self>,
15882 ) {
15883 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15884 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15885 self.selection_history.mode = SelectionHistoryMode::Undoing;
15886 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15887 this.end_selection(window, cx);
15888 this.change_selections(
15889 SelectionEffects::scroll(Autoscroll::newest()),
15890 window,
15891 cx,
15892 |s| s.select_anchors(entry.selections.to_vec()),
15893 );
15894 });
15895 self.selection_history.mode = SelectionHistoryMode::Normal;
15896
15897 self.select_next_state = entry.select_next_state;
15898 self.select_prev_state = entry.select_prev_state;
15899 self.add_selections_state = entry.add_selections_state;
15900 }
15901 }
15902
15903 pub fn redo_selection(
15904 &mut self,
15905 _: &RedoSelection,
15906 window: &mut Window,
15907 cx: &mut Context<Self>,
15908 ) {
15909 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15910 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15911 self.selection_history.mode = SelectionHistoryMode::Redoing;
15912 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15913 this.end_selection(window, cx);
15914 this.change_selections(
15915 SelectionEffects::scroll(Autoscroll::newest()),
15916 window,
15917 cx,
15918 |s| s.select_anchors(entry.selections.to_vec()),
15919 );
15920 });
15921 self.selection_history.mode = SelectionHistoryMode::Normal;
15922
15923 self.select_next_state = entry.select_next_state;
15924 self.select_prev_state = entry.select_prev_state;
15925 self.add_selections_state = entry.add_selections_state;
15926 }
15927 }
15928
15929 pub fn expand_excerpts(
15930 &mut self,
15931 action: &ExpandExcerpts,
15932 _: &mut Window,
15933 cx: &mut Context<Self>,
15934 ) {
15935 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15936 }
15937
15938 pub fn expand_excerpts_down(
15939 &mut self,
15940 action: &ExpandExcerptsDown,
15941 _: &mut Window,
15942 cx: &mut Context<Self>,
15943 ) {
15944 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15945 }
15946
15947 pub fn expand_excerpts_up(
15948 &mut self,
15949 action: &ExpandExcerptsUp,
15950 _: &mut Window,
15951 cx: &mut Context<Self>,
15952 ) {
15953 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15954 }
15955
15956 pub fn expand_excerpts_for_direction(
15957 &mut self,
15958 lines: u32,
15959 direction: ExpandExcerptDirection,
15960
15961 cx: &mut Context<Self>,
15962 ) {
15963 let selections = self.selections.disjoint_anchors_arc();
15964
15965 let lines = if lines == 0 {
15966 EditorSettings::get_global(cx).expand_excerpt_lines
15967 } else {
15968 lines
15969 };
15970
15971 self.buffer.update(cx, |buffer, cx| {
15972 let snapshot = buffer.snapshot(cx);
15973 let mut excerpt_ids = selections
15974 .iter()
15975 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15976 .collect::<Vec<_>>();
15977 excerpt_ids.sort();
15978 excerpt_ids.dedup();
15979 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15980 })
15981 }
15982
15983 pub fn expand_excerpt(
15984 &mut self,
15985 excerpt: ExcerptId,
15986 direction: ExpandExcerptDirection,
15987 window: &mut Window,
15988 cx: &mut Context<Self>,
15989 ) {
15990 let current_scroll_position = self.scroll_position(cx);
15991 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15992 let mut scroll = None;
15993
15994 if direction == ExpandExcerptDirection::Down {
15995 let multi_buffer = self.buffer.read(cx);
15996 let snapshot = multi_buffer.snapshot(cx);
15997 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15998 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15999 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16000 {
16001 let buffer_snapshot = buffer.read(cx).snapshot();
16002 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16003 let last_row = buffer_snapshot.max_point().row;
16004 let lines_below = last_row.saturating_sub(excerpt_end_row);
16005 if lines_below >= lines_to_expand {
16006 scroll = Some(
16007 current_scroll_position
16008 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16009 );
16010 }
16011 }
16012 }
16013 if direction == ExpandExcerptDirection::Up
16014 && self
16015 .buffer
16016 .read(cx)
16017 .snapshot(cx)
16018 .excerpt_before(excerpt)
16019 .is_none()
16020 {
16021 scroll = Some(current_scroll_position);
16022 }
16023
16024 self.buffer.update(cx, |buffer, cx| {
16025 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16026 });
16027
16028 if let Some(new_scroll_position) = scroll {
16029 self.set_scroll_position(new_scroll_position, window, cx);
16030 }
16031 }
16032
16033 pub fn go_to_singleton_buffer_point(
16034 &mut self,
16035 point: Point,
16036 window: &mut Window,
16037 cx: &mut Context<Self>,
16038 ) {
16039 self.go_to_singleton_buffer_range(point..point, window, cx);
16040 }
16041
16042 pub fn go_to_singleton_buffer_range(
16043 &mut self,
16044 range: Range<Point>,
16045 window: &mut Window,
16046 cx: &mut Context<Self>,
16047 ) {
16048 let multibuffer = self.buffer().read(cx);
16049 let Some(buffer) = multibuffer.as_singleton() else {
16050 return;
16051 };
16052 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16053 return;
16054 };
16055 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16056 return;
16057 };
16058 self.change_selections(
16059 SelectionEffects::default().nav_history(true),
16060 window,
16061 cx,
16062 |s| s.select_anchor_ranges([start..end]),
16063 );
16064 }
16065
16066 pub fn go_to_diagnostic(
16067 &mut self,
16068 action: &GoToDiagnostic,
16069 window: &mut Window,
16070 cx: &mut Context<Self>,
16071 ) {
16072 if !self.diagnostics_enabled() {
16073 return;
16074 }
16075 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16076 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16077 }
16078
16079 pub fn go_to_prev_diagnostic(
16080 &mut self,
16081 action: &GoToPreviousDiagnostic,
16082 window: &mut Window,
16083 cx: &mut Context<Self>,
16084 ) {
16085 if !self.diagnostics_enabled() {
16086 return;
16087 }
16088 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16089 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16090 }
16091
16092 pub fn go_to_diagnostic_impl(
16093 &mut self,
16094 direction: Direction,
16095 severity: GoToDiagnosticSeverityFilter,
16096 window: &mut Window,
16097 cx: &mut Context<Self>,
16098 ) {
16099 let buffer = self.buffer.read(cx).snapshot(cx);
16100 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16101
16102 let mut active_group_id = None;
16103 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16104 && active_group.active_range.start.to_offset(&buffer) == selection.start
16105 {
16106 active_group_id = Some(active_group.group_id);
16107 }
16108
16109 fn filtered<'a>(
16110 severity: GoToDiagnosticSeverityFilter,
16111 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16112 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16113 diagnostics
16114 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16115 .filter(|entry| entry.range.start != entry.range.end)
16116 .filter(|entry| !entry.diagnostic.is_unnecessary)
16117 }
16118
16119 let before = filtered(
16120 severity,
16121 buffer
16122 .diagnostics_in_range(0..selection.start)
16123 .filter(|entry| entry.range.start <= selection.start),
16124 );
16125 let after = filtered(
16126 severity,
16127 buffer
16128 .diagnostics_in_range(selection.start..buffer.len())
16129 .filter(|entry| entry.range.start >= selection.start),
16130 );
16131
16132 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16133 if direction == Direction::Prev {
16134 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16135 {
16136 for diagnostic in prev_diagnostics.into_iter().rev() {
16137 if diagnostic.range.start != selection.start
16138 || active_group_id
16139 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16140 {
16141 found = Some(diagnostic);
16142 break 'outer;
16143 }
16144 }
16145 }
16146 } else {
16147 for diagnostic in after.chain(before) {
16148 if diagnostic.range.start != selection.start
16149 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16150 {
16151 found = Some(diagnostic);
16152 break;
16153 }
16154 }
16155 }
16156 let Some(next_diagnostic) = found else {
16157 return;
16158 };
16159
16160 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16161 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16162 return;
16163 };
16164 let snapshot = self.snapshot(window, cx);
16165 if snapshot.intersects_fold(next_diagnostic.range.start) {
16166 self.unfold_ranges(
16167 std::slice::from_ref(&next_diagnostic.range),
16168 true,
16169 false,
16170 cx,
16171 );
16172 }
16173 self.change_selections(Default::default(), window, cx, |s| {
16174 s.select_ranges(vec![
16175 next_diagnostic.range.start..next_diagnostic.range.start,
16176 ])
16177 });
16178 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16179 self.refresh_edit_prediction(false, true, window, cx);
16180 }
16181
16182 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16183 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16184 let snapshot = self.snapshot(window, cx);
16185 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16186 self.go_to_hunk_before_or_after_position(
16187 &snapshot,
16188 selection.head(),
16189 Direction::Next,
16190 window,
16191 cx,
16192 );
16193 }
16194
16195 pub fn go_to_hunk_before_or_after_position(
16196 &mut self,
16197 snapshot: &EditorSnapshot,
16198 position: Point,
16199 direction: Direction,
16200 window: &mut Window,
16201 cx: &mut Context<Editor>,
16202 ) {
16203 let row = if direction == Direction::Next {
16204 self.hunk_after_position(snapshot, position)
16205 .map(|hunk| hunk.row_range.start)
16206 } else {
16207 self.hunk_before_position(snapshot, position)
16208 };
16209
16210 if let Some(row) = row {
16211 let destination = Point::new(row.0, 0);
16212 let autoscroll = Autoscroll::center();
16213
16214 self.unfold_ranges(&[destination..destination], false, false, cx);
16215 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16216 s.select_ranges([destination..destination]);
16217 });
16218 }
16219 }
16220
16221 fn hunk_after_position(
16222 &mut self,
16223 snapshot: &EditorSnapshot,
16224 position: Point,
16225 ) -> Option<MultiBufferDiffHunk> {
16226 snapshot
16227 .buffer_snapshot()
16228 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16229 .find(|hunk| hunk.row_range.start.0 > position.row)
16230 .or_else(|| {
16231 snapshot
16232 .buffer_snapshot()
16233 .diff_hunks_in_range(Point::zero()..position)
16234 .find(|hunk| hunk.row_range.end.0 < position.row)
16235 })
16236 }
16237
16238 fn go_to_prev_hunk(
16239 &mut self,
16240 _: &GoToPreviousHunk,
16241 window: &mut Window,
16242 cx: &mut Context<Self>,
16243 ) {
16244 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16245 let snapshot = self.snapshot(window, cx);
16246 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16247 self.go_to_hunk_before_or_after_position(
16248 &snapshot,
16249 selection.head(),
16250 Direction::Prev,
16251 window,
16252 cx,
16253 );
16254 }
16255
16256 fn hunk_before_position(
16257 &mut self,
16258 snapshot: &EditorSnapshot,
16259 position: Point,
16260 ) -> Option<MultiBufferRow> {
16261 snapshot
16262 .buffer_snapshot()
16263 .diff_hunk_before(position)
16264 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16265 }
16266
16267 fn go_to_next_change(
16268 &mut self,
16269 _: &GoToNextChange,
16270 window: &mut Window,
16271 cx: &mut Context<Self>,
16272 ) {
16273 if let Some(selections) = self
16274 .change_list
16275 .next_change(1, Direction::Next)
16276 .map(|s| s.to_vec())
16277 {
16278 self.change_selections(Default::default(), window, cx, |s| {
16279 let map = s.display_map();
16280 s.select_display_ranges(selections.iter().map(|a| {
16281 let point = a.to_display_point(&map);
16282 point..point
16283 }))
16284 })
16285 }
16286 }
16287
16288 fn go_to_previous_change(
16289 &mut self,
16290 _: &GoToPreviousChange,
16291 window: &mut Window,
16292 cx: &mut Context<Self>,
16293 ) {
16294 if let Some(selections) = self
16295 .change_list
16296 .next_change(1, Direction::Prev)
16297 .map(|s| s.to_vec())
16298 {
16299 self.change_selections(Default::default(), window, cx, |s| {
16300 let map = s.display_map();
16301 s.select_display_ranges(selections.iter().map(|a| {
16302 let point = a.to_display_point(&map);
16303 point..point
16304 }))
16305 })
16306 }
16307 }
16308
16309 pub fn go_to_next_document_highlight(
16310 &mut self,
16311 _: &GoToNextDocumentHighlight,
16312 window: &mut Window,
16313 cx: &mut Context<Self>,
16314 ) {
16315 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16316 }
16317
16318 pub fn go_to_prev_document_highlight(
16319 &mut self,
16320 _: &GoToPreviousDocumentHighlight,
16321 window: &mut Window,
16322 cx: &mut Context<Self>,
16323 ) {
16324 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16325 }
16326
16327 pub fn go_to_document_highlight_before_or_after_position(
16328 &mut self,
16329 direction: Direction,
16330 window: &mut Window,
16331 cx: &mut Context<Editor>,
16332 ) {
16333 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16334 let snapshot = self.snapshot(window, cx);
16335 let buffer = &snapshot.buffer_snapshot();
16336 let position = self
16337 .selections
16338 .newest::<Point>(&snapshot.display_snapshot)
16339 .head();
16340 let anchor_position = buffer.anchor_after(position);
16341
16342 // Get all document highlights (both read and write)
16343 let mut all_highlights = Vec::new();
16344
16345 if let Some((_, read_highlights)) = self
16346 .background_highlights
16347 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16348 {
16349 all_highlights.extend(read_highlights.iter());
16350 }
16351
16352 if let Some((_, write_highlights)) = self
16353 .background_highlights
16354 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16355 {
16356 all_highlights.extend(write_highlights.iter());
16357 }
16358
16359 if all_highlights.is_empty() {
16360 return;
16361 }
16362
16363 // Sort highlights by position
16364 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16365
16366 let target_highlight = match direction {
16367 Direction::Next => {
16368 // Find the first highlight after the current position
16369 all_highlights
16370 .iter()
16371 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16372 }
16373 Direction::Prev => {
16374 // Find the last highlight before the current position
16375 all_highlights
16376 .iter()
16377 .rev()
16378 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16379 }
16380 };
16381
16382 if let Some(highlight) = target_highlight {
16383 let destination = highlight.start.to_point(buffer);
16384 let autoscroll = Autoscroll::center();
16385
16386 self.unfold_ranges(&[destination..destination], false, false, cx);
16387 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16388 s.select_ranges([destination..destination]);
16389 });
16390 }
16391 }
16392
16393 fn go_to_line<T: 'static>(
16394 &mut self,
16395 position: Anchor,
16396 highlight_color: Option<Hsla>,
16397 window: &mut Window,
16398 cx: &mut Context<Self>,
16399 ) {
16400 let snapshot = self.snapshot(window, cx).display_snapshot;
16401 let position = position.to_point(&snapshot.buffer_snapshot());
16402 let start = snapshot
16403 .buffer_snapshot()
16404 .clip_point(Point::new(position.row, 0), Bias::Left);
16405 let end = start + Point::new(1, 0);
16406 let start = snapshot.buffer_snapshot().anchor_before(start);
16407 let end = snapshot.buffer_snapshot().anchor_before(end);
16408
16409 self.highlight_rows::<T>(
16410 start..end,
16411 highlight_color
16412 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16413 Default::default(),
16414 cx,
16415 );
16416
16417 if self.buffer.read(cx).is_singleton() {
16418 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16419 }
16420 }
16421
16422 pub fn go_to_definition(
16423 &mut self,
16424 _: &GoToDefinition,
16425 window: &mut Window,
16426 cx: &mut Context<Self>,
16427 ) -> Task<Result<Navigated>> {
16428 let definition =
16429 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16430 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16431 cx.spawn_in(window, async move |editor, cx| {
16432 if definition.await? == Navigated::Yes {
16433 return Ok(Navigated::Yes);
16434 }
16435 match fallback_strategy {
16436 GoToDefinitionFallback::None => Ok(Navigated::No),
16437 GoToDefinitionFallback::FindAllReferences => {
16438 match editor.update_in(cx, |editor, window, cx| {
16439 editor.find_all_references(&FindAllReferences, window, cx)
16440 })? {
16441 Some(references) => references.await,
16442 None => Ok(Navigated::No),
16443 }
16444 }
16445 }
16446 })
16447 }
16448
16449 pub fn go_to_declaration(
16450 &mut self,
16451 _: &GoToDeclaration,
16452 window: &mut Window,
16453 cx: &mut Context<Self>,
16454 ) -> Task<Result<Navigated>> {
16455 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16456 }
16457
16458 pub fn go_to_declaration_split(
16459 &mut self,
16460 _: &GoToDeclaration,
16461 window: &mut Window,
16462 cx: &mut Context<Self>,
16463 ) -> Task<Result<Navigated>> {
16464 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16465 }
16466
16467 pub fn go_to_implementation(
16468 &mut self,
16469 _: &GoToImplementation,
16470 window: &mut Window,
16471 cx: &mut Context<Self>,
16472 ) -> Task<Result<Navigated>> {
16473 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16474 }
16475
16476 pub fn go_to_implementation_split(
16477 &mut self,
16478 _: &GoToImplementationSplit,
16479 window: &mut Window,
16480 cx: &mut Context<Self>,
16481 ) -> Task<Result<Navigated>> {
16482 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16483 }
16484
16485 pub fn go_to_type_definition(
16486 &mut self,
16487 _: &GoToTypeDefinition,
16488 window: &mut Window,
16489 cx: &mut Context<Self>,
16490 ) -> Task<Result<Navigated>> {
16491 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16492 }
16493
16494 pub fn go_to_definition_split(
16495 &mut self,
16496 _: &GoToDefinitionSplit,
16497 window: &mut Window,
16498 cx: &mut Context<Self>,
16499 ) -> Task<Result<Navigated>> {
16500 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16501 }
16502
16503 pub fn go_to_type_definition_split(
16504 &mut self,
16505 _: &GoToTypeDefinitionSplit,
16506 window: &mut Window,
16507 cx: &mut Context<Self>,
16508 ) -> Task<Result<Navigated>> {
16509 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16510 }
16511
16512 fn go_to_definition_of_kind(
16513 &mut self,
16514 kind: GotoDefinitionKind,
16515 split: bool,
16516 window: &mut Window,
16517 cx: &mut Context<Self>,
16518 ) -> Task<Result<Navigated>> {
16519 let Some(provider) = self.semantics_provider.clone() else {
16520 return Task::ready(Ok(Navigated::No));
16521 };
16522 let head = self
16523 .selections
16524 .newest::<usize>(&self.display_snapshot(cx))
16525 .head();
16526 let buffer = self.buffer.read(cx);
16527 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16528 return Task::ready(Ok(Navigated::No));
16529 };
16530 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16531 return Task::ready(Ok(Navigated::No));
16532 };
16533
16534 cx.spawn_in(window, async move |editor, cx| {
16535 let Some(definitions) = definitions.await? else {
16536 return Ok(Navigated::No);
16537 };
16538 let navigated = editor
16539 .update_in(cx, |editor, window, cx| {
16540 editor.navigate_to_hover_links(
16541 Some(kind),
16542 definitions
16543 .into_iter()
16544 .filter(|location| {
16545 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16546 })
16547 .map(HoverLink::Text)
16548 .collect::<Vec<_>>(),
16549 split,
16550 window,
16551 cx,
16552 )
16553 })?
16554 .await?;
16555 anyhow::Ok(navigated)
16556 })
16557 }
16558
16559 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16560 let selection = self.selections.newest_anchor();
16561 let head = selection.head();
16562 let tail = selection.tail();
16563
16564 let Some((buffer, start_position)) =
16565 self.buffer.read(cx).text_anchor_for_position(head, cx)
16566 else {
16567 return;
16568 };
16569
16570 let end_position = if head != tail {
16571 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16572 return;
16573 };
16574 Some(pos)
16575 } else {
16576 None
16577 };
16578
16579 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16580 let url = if let Some(end_pos) = end_position {
16581 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16582 } else {
16583 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16584 };
16585
16586 if let Some(url) = url {
16587 cx.update(|window, cx| {
16588 if parse_zed_link(&url, cx).is_some() {
16589 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16590 } else {
16591 cx.open_url(&url);
16592 }
16593 })?;
16594 }
16595
16596 anyhow::Ok(())
16597 });
16598
16599 url_finder.detach();
16600 }
16601
16602 pub fn open_selected_filename(
16603 &mut self,
16604 _: &OpenSelectedFilename,
16605 window: &mut Window,
16606 cx: &mut Context<Self>,
16607 ) {
16608 let Some(workspace) = self.workspace() else {
16609 return;
16610 };
16611
16612 let position = self.selections.newest_anchor().head();
16613
16614 let Some((buffer, buffer_position)) =
16615 self.buffer.read(cx).text_anchor_for_position(position, cx)
16616 else {
16617 return;
16618 };
16619
16620 let project = self.project.clone();
16621
16622 cx.spawn_in(window, async move |_, cx| {
16623 let result = find_file(&buffer, project, buffer_position, cx).await;
16624
16625 if let Some((_, path)) = result {
16626 workspace
16627 .update_in(cx, |workspace, window, cx| {
16628 workspace.open_resolved_path(path, window, cx)
16629 })?
16630 .await?;
16631 }
16632 anyhow::Ok(())
16633 })
16634 .detach();
16635 }
16636
16637 pub(crate) fn navigate_to_hover_links(
16638 &mut self,
16639 kind: Option<GotoDefinitionKind>,
16640 definitions: Vec<HoverLink>,
16641 split: bool,
16642 window: &mut Window,
16643 cx: &mut Context<Editor>,
16644 ) -> Task<Result<Navigated>> {
16645 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16646 let mut first_url_or_file = None;
16647 let definitions: Vec<_> = definitions
16648 .into_iter()
16649 .filter_map(|def| match def {
16650 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16651 HoverLink::InlayHint(lsp_location, server_id) => {
16652 let computation =
16653 self.compute_target_location(lsp_location, server_id, window, cx);
16654 Some(cx.background_spawn(computation))
16655 }
16656 HoverLink::Url(url) => {
16657 first_url_or_file = Some(Either::Left(url));
16658 None
16659 }
16660 HoverLink::File(path) => {
16661 first_url_or_file = Some(Either::Right(path));
16662 None
16663 }
16664 })
16665 .collect();
16666
16667 let workspace = self.workspace();
16668
16669 cx.spawn_in(window, async move |editor, cx| {
16670 let locations: Vec<Location> = future::join_all(definitions)
16671 .await
16672 .into_iter()
16673 .filter_map(|location| location.transpose())
16674 .collect::<Result<_>>()
16675 .context("location tasks")?;
16676 let mut locations = cx.update(|_, cx| {
16677 locations
16678 .into_iter()
16679 .map(|location| {
16680 let buffer = location.buffer.read(cx);
16681 (location.buffer, location.range.to_point(buffer))
16682 })
16683 .into_group_map()
16684 })?;
16685 let mut num_locations = 0;
16686 for ranges in locations.values_mut() {
16687 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16688 ranges.dedup();
16689 num_locations += ranges.len();
16690 }
16691
16692 if num_locations > 1 {
16693 let Some(workspace) = workspace else {
16694 return Ok(Navigated::No);
16695 };
16696
16697 let tab_kind = match kind {
16698 Some(GotoDefinitionKind::Implementation) => "Implementations",
16699 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16700 Some(GotoDefinitionKind::Declaration) => "Declarations",
16701 Some(GotoDefinitionKind::Type) => "Types",
16702 };
16703 let title = editor
16704 .update_in(cx, |_, _, cx| {
16705 let target = locations
16706 .iter()
16707 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16708 .map(|(buffer, location)| {
16709 buffer
16710 .read(cx)
16711 .text_for_range(location.clone())
16712 .collect::<String>()
16713 })
16714 .filter(|text| !text.contains('\n'))
16715 .unique()
16716 .take(3)
16717 .join(", ");
16718 if target.is_empty() {
16719 tab_kind.to_owned()
16720 } else {
16721 format!("{tab_kind} for {target}")
16722 }
16723 })
16724 .context("buffer title")?;
16725
16726 let opened = workspace
16727 .update_in(cx, |workspace, window, cx| {
16728 Self::open_locations_in_multibuffer(
16729 workspace,
16730 locations,
16731 title,
16732 split,
16733 MultibufferSelectionMode::First,
16734 window,
16735 cx,
16736 )
16737 })
16738 .is_ok();
16739
16740 anyhow::Ok(Navigated::from_bool(opened))
16741 } else if num_locations == 0 {
16742 // If there is one url or file, open it directly
16743 match first_url_or_file {
16744 Some(Either::Left(url)) => {
16745 cx.update(|_, cx| cx.open_url(&url))?;
16746 Ok(Navigated::Yes)
16747 }
16748 Some(Either::Right(path)) => {
16749 let Some(workspace) = workspace else {
16750 return Ok(Navigated::No);
16751 };
16752
16753 workspace
16754 .update_in(cx, |workspace, window, cx| {
16755 workspace.open_resolved_path(path, window, cx)
16756 })?
16757 .await?;
16758 Ok(Navigated::Yes)
16759 }
16760 None => Ok(Navigated::No),
16761 }
16762 } else {
16763 let Some(workspace) = workspace else {
16764 return Ok(Navigated::No);
16765 };
16766
16767 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16768 let target_range = target_ranges.first().unwrap().clone();
16769
16770 editor.update_in(cx, |editor, window, cx| {
16771 let range = target_range.to_point(target_buffer.read(cx));
16772 let range = editor.range_for_match(&range, false);
16773 let range = collapse_multiline_range(range);
16774
16775 if !split
16776 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16777 {
16778 editor.go_to_singleton_buffer_range(range, window, cx);
16779 } else {
16780 let pane = workspace.read(cx).active_pane().clone();
16781 window.defer(cx, move |window, cx| {
16782 let target_editor: Entity<Self> =
16783 workspace.update(cx, |workspace, cx| {
16784 let pane = if split {
16785 workspace.adjacent_pane(window, cx)
16786 } else {
16787 workspace.active_pane().clone()
16788 };
16789
16790 workspace.open_project_item(
16791 pane,
16792 target_buffer.clone(),
16793 true,
16794 true,
16795 window,
16796 cx,
16797 )
16798 });
16799 target_editor.update(cx, |target_editor, cx| {
16800 // When selecting a definition in a different buffer, disable the nav history
16801 // to avoid creating a history entry at the previous cursor location.
16802 pane.update(cx, |pane, _| pane.disable_history());
16803 target_editor.go_to_singleton_buffer_range(range, window, cx);
16804 pane.update(cx, |pane, _| pane.enable_history());
16805 });
16806 });
16807 }
16808 Navigated::Yes
16809 })
16810 }
16811 })
16812 }
16813
16814 fn compute_target_location(
16815 &self,
16816 lsp_location: lsp::Location,
16817 server_id: LanguageServerId,
16818 window: &mut Window,
16819 cx: &mut Context<Self>,
16820 ) -> Task<anyhow::Result<Option<Location>>> {
16821 let Some(project) = self.project.clone() else {
16822 return Task::ready(Ok(None));
16823 };
16824
16825 cx.spawn_in(window, async move |editor, cx| {
16826 let location_task = editor.update(cx, |_, cx| {
16827 project.update(cx, |project, cx| {
16828 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16829 })
16830 })?;
16831 let location = Some({
16832 let target_buffer_handle = location_task.await.context("open local buffer")?;
16833 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16834 let target_start = target_buffer
16835 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16836 let target_end = target_buffer
16837 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16838 target_buffer.anchor_after(target_start)
16839 ..target_buffer.anchor_before(target_end)
16840 })?;
16841 Location {
16842 buffer: target_buffer_handle,
16843 range,
16844 }
16845 });
16846 Ok(location)
16847 })
16848 }
16849
16850 fn go_to_next_reference(
16851 &mut self,
16852 _: &GoToNextReference,
16853 window: &mut Window,
16854 cx: &mut Context<Self>,
16855 ) {
16856 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
16857 if let Some(task) = task {
16858 task.detach();
16859 };
16860 }
16861
16862 fn go_to_prev_reference(
16863 &mut self,
16864 _: &GoToPreviousReference,
16865 window: &mut Window,
16866 cx: &mut Context<Self>,
16867 ) {
16868 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
16869 if let Some(task) = task {
16870 task.detach();
16871 };
16872 }
16873
16874 pub fn go_to_reference_before_or_after_position(
16875 &mut self,
16876 direction: Direction,
16877 count: usize,
16878 window: &mut Window,
16879 cx: &mut Context<Self>,
16880 ) -> Option<Task<Result<()>>> {
16881 let selection = self.selections.newest_anchor();
16882 let head = selection.head();
16883
16884 let multi_buffer = self.buffer.read(cx);
16885
16886 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
16887 let workspace = self.workspace()?;
16888 let project = workspace.read(cx).project().clone();
16889 let references =
16890 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
16891 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
16892 let Some(locations) = references.await? else {
16893 return Ok(());
16894 };
16895
16896 if locations.is_empty() {
16897 // totally normal - the cursor may be on something which is not
16898 // a symbol (e.g. a keyword)
16899 log::info!("no references found under cursor");
16900 return Ok(());
16901 }
16902
16903 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
16904
16905 let multi_buffer_snapshot =
16906 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
16907
16908 let (locations, current_location_index) =
16909 multi_buffer.update(cx, |multi_buffer, cx| {
16910 let mut locations = locations
16911 .into_iter()
16912 .filter_map(|loc| {
16913 let start = multi_buffer.buffer_anchor_to_anchor(
16914 &loc.buffer,
16915 loc.range.start,
16916 cx,
16917 )?;
16918 let end = multi_buffer.buffer_anchor_to_anchor(
16919 &loc.buffer,
16920 loc.range.end,
16921 cx,
16922 )?;
16923 Some(start..end)
16924 })
16925 .collect::<Vec<_>>();
16926
16927 // There is an O(n) implementation, but given this list will be
16928 // small (usually <100 items), the extra O(log(n)) factor isn't
16929 // worth the (surprisingly large amount of) extra complexity.
16930 locations
16931 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
16932
16933 let head_offset = head.to_offset(&multi_buffer_snapshot);
16934
16935 let current_location_index = locations.iter().position(|loc| {
16936 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
16937 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
16938 });
16939
16940 (locations, current_location_index)
16941 })?;
16942
16943 let Some(current_location_index) = current_location_index else {
16944 // This indicates something has gone wrong, because we already
16945 // handle the "no references" case above
16946 log::error!(
16947 "failed to find current reference under cursor. Total references: {}",
16948 locations.len()
16949 );
16950 return Ok(());
16951 };
16952
16953 let destination_location_index = match direction {
16954 Direction::Next => (current_location_index + count) % locations.len(),
16955 Direction::Prev => {
16956 (current_location_index + locations.len() - count % locations.len())
16957 % locations.len()
16958 }
16959 };
16960
16961 // TODO(cameron): is this needed?
16962 // the thinking is to avoid "jumping to the current location" (avoid
16963 // polluting "jumplist" in vim terms)
16964 if current_location_index == destination_location_index {
16965 return Ok(());
16966 }
16967
16968 let Range { start, end } = locations[destination_location_index];
16969
16970 editor.update_in(cx, |editor, window, cx| {
16971 let effects = SelectionEffects::default();
16972
16973 editor.unfold_ranges(&[start..end], false, false, cx);
16974 editor.change_selections(effects, window, cx, |s| {
16975 s.select_ranges([start..start]);
16976 });
16977 })?;
16978
16979 Ok(())
16980 }))
16981 }
16982
16983 pub fn find_all_references(
16984 &mut self,
16985 _: &FindAllReferences,
16986 window: &mut Window,
16987 cx: &mut Context<Self>,
16988 ) -> Option<Task<Result<Navigated>>> {
16989 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16990 let multi_buffer = self.buffer.read(cx);
16991 let head = selection.head();
16992
16993 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16994 let head_anchor = multi_buffer_snapshot.anchor_at(
16995 head,
16996 if head < selection.tail() {
16997 Bias::Right
16998 } else {
16999 Bias::Left
17000 },
17001 );
17002
17003 match self
17004 .find_all_references_task_sources
17005 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17006 {
17007 Ok(_) => {
17008 log::info!(
17009 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17010 );
17011 return None;
17012 }
17013 Err(i) => {
17014 self.find_all_references_task_sources.insert(i, head_anchor);
17015 }
17016 }
17017
17018 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17019 let workspace = self.workspace()?;
17020 let project = workspace.read(cx).project().clone();
17021 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17022 Some(cx.spawn_in(window, async move |editor, cx| {
17023 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17024 if let Ok(i) = editor
17025 .find_all_references_task_sources
17026 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17027 {
17028 editor.find_all_references_task_sources.remove(i);
17029 }
17030 });
17031
17032 let Some(locations) = references.await? else {
17033 return anyhow::Ok(Navigated::No);
17034 };
17035 let mut locations = cx.update(|_, cx| {
17036 locations
17037 .into_iter()
17038 .map(|location| {
17039 let buffer = location.buffer.read(cx);
17040 (location.buffer, location.range.to_point(buffer))
17041 })
17042 .into_group_map()
17043 })?;
17044 if locations.is_empty() {
17045 return anyhow::Ok(Navigated::No);
17046 }
17047 for ranges in locations.values_mut() {
17048 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17049 ranges.dedup();
17050 }
17051
17052 workspace.update_in(cx, |workspace, window, cx| {
17053 let target = locations
17054 .iter()
17055 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17056 .map(|(buffer, location)| {
17057 buffer
17058 .read(cx)
17059 .text_for_range(location.clone())
17060 .collect::<String>()
17061 })
17062 .filter(|text| !text.contains('\n'))
17063 .unique()
17064 .take(3)
17065 .join(", ");
17066 let title = if target.is_empty() {
17067 "References".to_owned()
17068 } else {
17069 format!("References to {target}")
17070 };
17071 Self::open_locations_in_multibuffer(
17072 workspace,
17073 locations,
17074 title,
17075 false,
17076 MultibufferSelectionMode::First,
17077 window,
17078 cx,
17079 );
17080 Navigated::Yes
17081 })
17082 }))
17083 }
17084
17085 /// Opens a multibuffer with the given project locations in it
17086 pub fn open_locations_in_multibuffer(
17087 workspace: &mut Workspace,
17088 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17089 title: String,
17090 split: bool,
17091 multibuffer_selection_mode: MultibufferSelectionMode,
17092 window: &mut Window,
17093 cx: &mut Context<Workspace>,
17094 ) {
17095 if locations.is_empty() {
17096 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17097 return;
17098 }
17099
17100 let capability = workspace.project().read(cx).capability();
17101 let mut ranges = <Vec<Range<Anchor>>>::new();
17102
17103 // a key to find existing multibuffer editors with the same set of locations
17104 // to prevent us from opening more and more multibuffer tabs for searches and the like
17105 let mut key = (title.clone(), vec![]);
17106 let excerpt_buffer = cx.new(|cx| {
17107 let key = &mut key.1;
17108 let mut multibuffer = MultiBuffer::new(capability);
17109 for (buffer, mut ranges_for_buffer) in locations {
17110 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17111 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17112 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17113 PathKey::for_buffer(&buffer, cx),
17114 buffer.clone(),
17115 ranges_for_buffer,
17116 multibuffer_context_lines(cx),
17117 cx,
17118 );
17119 ranges.extend(new_ranges)
17120 }
17121
17122 multibuffer.with_title(title)
17123 });
17124 let existing = workspace.active_pane().update(cx, |pane, cx| {
17125 pane.items()
17126 .filter_map(|item| item.downcast::<Editor>())
17127 .find(|editor| {
17128 editor
17129 .read(cx)
17130 .lookup_key
17131 .as_ref()
17132 .and_then(|it| {
17133 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17134 })
17135 .is_some_and(|it| *it == key)
17136 })
17137 });
17138 let editor = existing.unwrap_or_else(|| {
17139 cx.new(|cx| {
17140 let mut editor = Editor::for_multibuffer(
17141 excerpt_buffer,
17142 Some(workspace.project().clone()),
17143 window,
17144 cx,
17145 );
17146 editor.lookup_key = Some(Box::new(key));
17147 editor
17148 })
17149 });
17150 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17151 MultibufferSelectionMode::First => {
17152 if let Some(first_range) = ranges.first() {
17153 editor.change_selections(
17154 SelectionEffects::no_scroll(),
17155 window,
17156 cx,
17157 |selections| {
17158 selections.clear_disjoint();
17159 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17160 },
17161 );
17162 }
17163 editor.highlight_background::<Self>(
17164 &ranges,
17165 |theme| theme.colors().editor_highlighted_line_background,
17166 cx,
17167 );
17168 }
17169 MultibufferSelectionMode::All => {
17170 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17171 selections.clear_disjoint();
17172 selections.select_anchor_ranges(ranges);
17173 });
17174 }
17175 });
17176
17177 let item = Box::new(editor);
17178 let item_id = item.item_id();
17179
17180 if split {
17181 let pane = workspace.adjacent_pane(window, cx);
17182 workspace.add_item(pane, item, None, true, true, window, cx);
17183 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17184 let (preview_item_id, preview_item_idx) =
17185 workspace.active_pane().read_with(cx, |pane, _| {
17186 (pane.preview_item_id(), pane.preview_item_idx())
17187 });
17188
17189 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17190
17191 if let Some(preview_item_id) = preview_item_id {
17192 workspace.active_pane().update(cx, |pane, cx| {
17193 pane.remove_item(preview_item_id, false, false, window, cx);
17194 });
17195 }
17196 } else {
17197 workspace.add_item_to_active_pane(item, None, true, window, cx);
17198 }
17199 workspace.active_pane().update(cx, |pane, cx| {
17200 pane.set_preview_item_id(Some(item_id), cx);
17201 });
17202 }
17203
17204 pub fn rename(
17205 &mut self,
17206 _: &Rename,
17207 window: &mut Window,
17208 cx: &mut Context<Self>,
17209 ) -> Option<Task<Result<()>>> {
17210 use language::ToOffset as _;
17211
17212 let provider = self.semantics_provider.clone()?;
17213 let selection = self.selections.newest_anchor().clone();
17214 let (cursor_buffer, cursor_buffer_position) = self
17215 .buffer
17216 .read(cx)
17217 .text_anchor_for_position(selection.head(), cx)?;
17218 let (tail_buffer, cursor_buffer_position_end) = self
17219 .buffer
17220 .read(cx)
17221 .text_anchor_for_position(selection.tail(), cx)?;
17222 if tail_buffer != cursor_buffer {
17223 return None;
17224 }
17225
17226 let snapshot = cursor_buffer.read(cx).snapshot();
17227 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17228 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17229 let prepare_rename = provider
17230 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17231 .unwrap_or_else(|| Task::ready(Ok(None)));
17232 drop(snapshot);
17233
17234 Some(cx.spawn_in(window, async move |this, cx| {
17235 let rename_range = if let Some(range) = prepare_rename.await? {
17236 Some(range)
17237 } else {
17238 this.update(cx, |this, cx| {
17239 let buffer = this.buffer.read(cx).snapshot(cx);
17240 let mut buffer_highlights = this
17241 .document_highlights_for_position(selection.head(), &buffer)
17242 .filter(|highlight| {
17243 highlight.start.excerpt_id == selection.head().excerpt_id
17244 && highlight.end.excerpt_id == selection.head().excerpt_id
17245 });
17246 buffer_highlights
17247 .next()
17248 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17249 })?
17250 };
17251 if let Some(rename_range) = rename_range {
17252 this.update_in(cx, |this, window, cx| {
17253 let snapshot = cursor_buffer.read(cx).snapshot();
17254 let rename_buffer_range = rename_range.to_offset(&snapshot);
17255 let cursor_offset_in_rename_range =
17256 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17257 let cursor_offset_in_rename_range_end =
17258 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17259
17260 this.take_rename(false, window, cx);
17261 let buffer = this.buffer.read(cx).read(cx);
17262 let cursor_offset = selection.head().to_offset(&buffer);
17263 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17264 let rename_end = rename_start + rename_buffer_range.len();
17265 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17266 let mut old_highlight_id = None;
17267 let old_name: Arc<str> = buffer
17268 .chunks(rename_start..rename_end, true)
17269 .map(|chunk| {
17270 if old_highlight_id.is_none() {
17271 old_highlight_id = chunk.syntax_highlight_id;
17272 }
17273 chunk.text
17274 })
17275 .collect::<String>()
17276 .into();
17277
17278 drop(buffer);
17279
17280 // Position the selection in the rename editor so that it matches the current selection.
17281 this.show_local_selections = false;
17282 let rename_editor = cx.new(|cx| {
17283 let mut editor = Editor::single_line(window, cx);
17284 editor.buffer.update(cx, |buffer, cx| {
17285 buffer.edit([(0..0, old_name.clone())], None, cx)
17286 });
17287 let rename_selection_range = match cursor_offset_in_rename_range
17288 .cmp(&cursor_offset_in_rename_range_end)
17289 {
17290 Ordering::Equal => {
17291 editor.select_all(&SelectAll, window, cx);
17292 return editor;
17293 }
17294 Ordering::Less => {
17295 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17296 }
17297 Ordering::Greater => {
17298 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17299 }
17300 };
17301 if rename_selection_range.end > old_name.len() {
17302 editor.select_all(&SelectAll, window, cx);
17303 } else {
17304 editor.change_selections(Default::default(), window, cx, |s| {
17305 s.select_ranges([rename_selection_range]);
17306 });
17307 }
17308 editor
17309 });
17310 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17311 if e == &EditorEvent::Focused {
17312 cx.emit(EditorEvent::FocusedIn)
17313 }
17314 })
17315 .detach();
17316
17317 let write_highlights =
17318 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17319 let read_highlights =
17320 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17321 let ranges = write_highlights
17322 .iter()
17323 .flat_map(|(_, ranges)| ranges.iter())
17324 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17325 .cloned()
17326 .collect();
17327
17328 this.highlight_text::<Rename>(
17329 ranges,
17330 HighlightStyle {
17331 fade_out: Some(0.6),
17332 ..Default::default()
17333 },
17334 cx,
17335 );
17336 let rename_focus_handle = rename_editor.focus_handle(cx);
17337 window.focus(&rename_focus_handle);
17338 let block_id = this.insert_blocks(
17339 [BlockProperties {
17340 style: BlockStyle::Flex,
17341 placement: BlockPlacement::Below(range.start),
17342 height: Some(1),
17343 render: Arc::new({
17344 let rename_editor = rename_editor.clone();
17345 move |cx: &mut BlockContext| {
17346 let mut text_style = cx.editor_style.text.clone();
17347 if let Some(highlight_style) = old_highlight_id
17348 .and_then(|h| h.style(&cx.editor_style.syntax))
17349 {
17350 text_style = text_style.highlight(highlight_style);
17351 }
17352 div()
17353 .block_mouse_except_scroll()
17354 .pl(cx.anchor_x)
17355 .child(EditorElement::new(
17356 &rename_editor,
17357 EditorStyle {
17358 background: cx.theme().system().transparent,
17359 local_player: cx.editor_style.local_player,
17360 text: text_style,
17361 scrollbar_width: cx.editor_style.scrollbar_width,
17362 syntax: cx.editor_style.syntax.clone(),
17363 status: cx.editor_style.status.clone(),
17364 inlay_hints_style: HighlightStyle {
17365 font_weight: Some(FontWeight::BOLD),
17366 ..make_inlay_hints_style(cx.app)
17367 },
17368 edit_prediction_styles: make_suggestion_styles(
17369 cx.app,
17370 ),
17371 ..EditorStyle::default()
17372 },
17373 ))
17374 .into_any_element()
17375 }
17376 }),
17377 priority: 0,
17378 }],
17379 Some(Autoscroll::fit()),
17380 cx,
17381 )[0];
17382 this.pending_rename = Some(RenameState {
17383 range,
17384 old_name,
17385 editor: rename_editor,
17386 block_id,
17387 });
17388 })?;
17389 }
17390
17391 Ok(())
17392 }))
17393 }
17394
17395 pub fn confirm_rename(
17396 &mut self,
17397 _: &ConfirmRename,
17398 window: &mut Window,
17399 cx: &mut Context<Self>,
17400 ) -> Option<Task<Result<()>>> {
17401 let rename = self.take_rename(false, window, cx)?;
17402 let workspace = self.workspace()?.downgrade();
17403 let (buffer, start) = self
17404 .buffer
17405 .read(cx)
17406 .text_anchor_for_position(rename.range.start, cx)?;
17407 let (end_buffer, _) = self
17408 .buffer
17409 .read(cx)
17410 .text_anchor_for_position(rename.range.end, cx)?;
17411 if buffer != end_buffer {
17412 return None;
17413 }
17414
17415 let old_name = rename.old_name;
17416 let new_name = rename.editor.read(cx).text(cx);
17417
17418 let rename = self.semantics_provider.as_ref()?.perform_rename(
17419 &buffer,
17420 start,
17421 new_name.clone(),
17422 cx,
17423 )?;
17424
17425 Some(cx.spawn_in(window, async move |editor, cx| {
17426 let project_transaction = rename.await?;
17427 Self::open_project_transaction(
17428 &editor,
17429 workspace,
17430 project_transaction,
17431 format!("Rename: {} → {}", old_name, new_name),
17432 cx,
17433 )
17434 .await?;
17435
17436 editor.update(cx, |editor, cx| {
17437 editor.refresh_document_highlights(cx);
17438 })?;
17439 Ok(())
17440 }))
17441 }
17442
17443 fn take_rename(
17444 &mut self,
17445 moving_cursor: bool,
17446 window: &mut Window,
17447 cx: &mut Context<Self>,
17448 ) -> Option<RenameState> {
17449 let rename = self.pending_rename.take()?;
17450 if rename.editor.focus_handle(cx).is_focused(window) {
17451 window.focus(&self.focus_handle);
17452 }
17453
17454 self.remove_blocks(
17455 [rename.block_id].into_iter().collect(),
17456 Some(Autoscroll::fit()),
17457 cx,
17458 );
17459 self.clear_highlights::<Rename>(cx);
17460 self.show_local_selections = true;
17461
17462 if moving_cursor {
17463 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17464 editor
17465 .selections
17466 .newest::<usize>(&editor.display_snapshot(cx))
17467 .head()
17468 });
17469
17470 // Update the selection to match the position of the selection inside
17471 // the rename editor.
17472 let snapshot = self.buffer.read(cx).read(cx);
17473 let rename_range = rename.range.to_offset(&snapshot);
17474 let cursor_in_editor = snapshot
17475 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17476 .min(rename_range.end);
17477 drop(snapshot);
17478
17479 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17480 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17481 });
17482 } else {
17483 self.refresh_document_highlights(cx);
17484 }
17485
17486 Some(rename)
17487 }
17488
17489 pub fn pending_rename(&self) -> Option<&RenameState> {
17490 self.pending_rename.as_ref()
17491 }
17492
17493 fn format(
17494 &mut self,
17495 _: &Format,
17496 window: &mut Window,
17497 cx: &mut Context<Self>,
17498 ) -> Option<Task<Result<()>>> {
17499 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17500
17501 let project = match &self.project {
17502 Some(project) => project.clone(),
17503 None => return None,
17504 };
17505
17506 Some(self.perform_format(
17507 project,
17508 FormatTrigger::Manual,
17509 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17510 window,
17511 cx,
17512 ))
17513 }
17514
17515 fn format_selections(
17516 &mut self,
17517 _: &FormatSelections,
17518 window: &mut Window,
17519 cx: &mut Context<Self>,
17520 ) -> Option<Task<Result<()>>> {
17521 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17522
17523 let project = match &self.project {
17524 Some(project) => project.clone(),
17525 None => return None,
17526 };
17527
17528 let ranges = self
17529 .selections
17530 .all_adjusted(&self.display_snapshot(cx))
17531 .into_iter()
17532 .map(|selection| selection.range())
17533 .collect_vec();
17534
17535 Some(self.perform_format(
17536 project,
17537 FormatTrigger::Manual,
17538 FormatTarget::Ranges(ranges),
17539 window,
17540 cx,
17541 ))
17542 }
17543
17544 fn perform_format(
17545 &mut self,
17546 project: Entity<Project>,
17547 trigger: FormatTrigger,
17548 target: FormatTarget,
17549 window: &mut Window,
17550 cx: &mut Context<Self>,
17551 ) -> Task<Result<()>> {
17552 let buffer = self.buffer.clone();
17553 let (buffers, target) = match target {
17554 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17555 FormatTarget::Ranges(selection_ranges) => {
17556 let multi_buffer = buffer.read(cx);
17557 let snapshot = multi_buffer.read(cx);
17558 let mut buffers = HashSet::default();
17559 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17560 BTreeMap::new();
17561 for selection_range in selection_ranges {
17562 for (buffer, buffer_range, _) in
17563 snapshot.range_to_buffer_ranges(selection_range)
17564 {
17565 let buffer_id = buffer.remote_id();
17566 let start = buffer.anchor_before(buffer_range.start);
17567 let end = buffer.anchor_after(buffer_range.end);
17568 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17569 buffer_id_to_ranges
17570 .entry(buffer_id)
17571 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17572 .or_insert_with(|| vec![start..end]);
17573 }
17574 }
17575 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17576 }
17577 };
17578
17579 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17580 let selections_prev = transaction_id_prev
17581 .and_then(|transaction_id_prev| {
17582 // default to selections as they were after the last edit, if we have them,
17583 // instead of how they are now.
17584 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17585 // will take you back to where you made the last edit, instead of staying where you scrolled
17586 self.selection_history
17587 .transaction(transaction_id_prev)
17588 .map(|t| t.0.clone())
17589 })
17590 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17591
17592 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17593 let format = project.update(cx, |project, cx| {
17594 project.format(buffers, target, true, trigger, cx)
17595 });
17596
17597 cx.spawn_in(window, async move |editor, cx| {
17598 let transaction = futures::select_biased! {
17599 transaction = format.log_err().fuse() => transaction,
17600 () = timeout => {
17601 log::warn!("timed out waiting for formatting");
17602 None
17603 }
17604 };
17605
17606 buffer
17607 .update(cx, |buffer, cx| {
17608 if let Some(transaction) = transaction
17609 && !buffer.is_singleton()
17610 {
17611 buffer.push_transaction(&transaction.0, cx);
17612 }
17613 cx.notify();
17614 })
17615 .ok();
17616
17617 if let Some(transaction_id_now) =
17618 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17619 {
17620 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17621 if has_new_transaction {
17622 _ = editor.update(cx, |editor, _| {
17623 editor
17624 .selection_history
17625 .insert_transaction(transaction_id_now, selections_prev);
17626 });
17627 }
17628 }
17629
17630 Ok(())
17631 })
17632 }
17633
17634 fn organize_imports(
17635 &mut self,
17636 _: &OrganizeImports,
17637 window: &mut Window,
17638 cx: &mut Context<Self>,
17639 ) -> Option<Task<Result<()>>> {
17640 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17641 let project = match &self.project {
17642 Some(project) => project.clone(),
17643 None => return None,
17644 };
17645 Some(self.perform_code_action_kind(
17646 project,
17647 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17648 window,
17649 cx,
17650 ))
17651 }
17652
17653 fn perform_code_action_kind(
17654 &mut self,
17655 project: Entity<Project>,
17656 kind: CodeActionKind,
17657 window: &mut Window,
17658 cx: &mut Context<Self>,
17659 ) -> Task<Result<()>> {
17660 let buffer = self.buffer.clone();
17661 let buffers = buffer.read(cx).all_buffers();
17662 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17663 let apply_action = project.update(cx, |project, cx| {
17664 project.apply_code_action_kind(buffers, kind, true, cx)
17665 });
17666 cx.spawn_in(window, async move |_, cx| {
17667 let transaction = futures::select_biased! {
17668 () = timeout => {
17669 log::warn!("timed out waiting for executing code action");
17670 None
17671 }
17672 transaction = apply_action.log_err().fuse() => transaction,
17673 };
17674 buffer
17675 .update(cx, |buffer, cx| {
17676 // check if we need this
17677 if let Some(transaction) = transaction
17678 && !buffer.is_singleton()
17679 {
17680 buffer.push_transaction(&transaction.0, cx);
17681 }
17682 cx.notify();
17683 })
17684 .ok();
17685 Ok(())
17686 })
17687 }
17688
17689 pub fn restart_language_server(
17690 &mut self,
17691 _: &RestartLanguageServer,
17692 _: &mut Window,
17693 cx: &mut Context<Self>,
17694 ) {
17695 if let Some(project) = self.project.clone() {
17696 self.buffer.update(cx, |multi_buffer, cx| {
17697 project.update(cx, |project, cx| {
17698 project.restart_language_servers_for_buffers(
17699 multi_buffer.all_buffers().into_iter().collect(),
17700 HashSet::default(),
17701 cx,
17702 );
17703 });
17704 })
17705 }
17706 }
17707
17708 pub fn stop_language_server(
17709 &mut self,
17710 _: &StopLanguageServer,
17711 _: &mut Window,
17712 cx: &mut Context<Self>,
17713 ) {
17714 if let Some(project) = self.project.clone() {
17715 self.buffer.update(cx, |multi_buffer, cx| {
17716 project.update(cx, |project, cx| {
17717 project.stop_language_servers_for_buffers(
17718 multi_buffer.all_buffers().into_iter().collect(),
17719 HashSet::default(),
17720 cx,
17721 );
17722 });
17723 });
17724 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17725 }
17726 }
17727
17728 fn cancel_language_server_work(
17729 workspace: &mut Workspace,
17730 _: &actions::CancelLanguageServerWork,
17731 _: &mut Window,
17732 cx: &mut Context<Workspace>,
17733 ) {
17734 let project = workspace.project();
17735 let buffers = workspace
17736 .active_item(cx)
17737 .and_then(|item| item.act_as::<Editor>(cx))
17738 .map_or(HashSet::default(), |editor| {
17739 editor.read(cx).buffer.read(cx).all_buffers()
17740 });
17741 project.update(cx, |project, cx| {
17742 project.cancel_language_server_work_for_buffers(buffers, cx);
17743 });
17744 }
17745
17746 fn show_character_palette(
17747 &mut self,
17748 _: &ShowCharacterPalette,
17749 window: &mut Window,
17750 _: &mut Context<Self>,
17751 ) {
17752 window.show_character_palette();
17753 }
17754
17755 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17756 if !self.diagnostics_enabled() {
17757 return;
17758 }
17759
17760 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17761 let buffer = self.buffer.read(cx).snapshot(cx);
17762 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17763 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17764 let is_valid = buffer
17765 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17766 .any(|entry| {
17767 entry.diagnostic.is_primary
17768 && !entry.range.is_empty()
17769 && entry.range.start == primary_range_start
17770 && entry.diagnostic.message == active_diagnostics.active_message
17771 });
17772
17773 if !is_valid {
17774 self.dismiss_diagnostics(cx);
17775 }
17776 }
17777 }
17778
17779 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17780 match &self.active_diagnostics {
17781 ActiveDiagnostic::Group(group) => Some(group),
17782 _ => None,
17783 }
17784 }
17785
17786 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17787 if !self.diagnostics_enabled() {
17788 return;
17789 }
17790 self.dismiss_diagnostics(cx);
17791 self.active_diagnostics = ActiveDiagnostic::All;
17792 }
17793
17794 fn activate_diagnostics(
17795 &mut self,
17796 buffer_id: BufferId,
17797 diagnostic: DiagnosticEntryRef<'_, usize>,
17798 window: &mut Window,
17799 cx: &mut Context<Self>,
17800 ) {
17801 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17802 return;
17803 }
17804 self.dismiss_diagnostics(cx);
17805 let snapshot = self.snapshot(window, cx);
17806 let buffer = self.buffer.read(cx).snapshot(cx);
17807 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17808 return;
17809 };
17810
17811 let diagnostic_group = buffer
17812 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17813 .collect::<Vec<_>>();
17814
17815 let blocks =
17816 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17817
17818 let blocks = self.display_map.update(cx, |display_map, cx| {
17819 display_map.insert_blocks(blocks, cx).into_iter().collect()
17820 });
17821 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17822 active_range: buffer.anchor_before(diagnostic.range.start)
17823 ..buffer.anchor_after(diagnostic.range.end),
17824 active_message: diagnostic.diagnostic.message.clone(),
17825 group_id: diagnostic.diagnostic.group_id,
17826 blocks,
17827 });
17828 cx.notify();
17829 }
17830
17831 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17832 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17833 return;
17834 };
17835
17836 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17837 if let ActiveDiagnostic::Group(group) = prev {
17838 self.display_map.update(cx, |display_map, cx| {
17839 display_map.remove_blocks(group.blocks, cx);
17840 });
17841 cx.notify();
17842 }
17843 }
17844
17845 /// Disable inline diagnostics rendering for this editor.
17846 pub fn disable_inline_diagnostics(&mut self) {
17847 self.inline_diagnostics_enabled = false;
17848 self.inline_diagnostics_update = Task::ready(());
17849 self.inline_diagnostics.clear();
17850 }
17851
17852 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17853 self.diagnostics_enabled = false;
17854 self.dismiss_diagnostics(cx);
17855 self.inline_diagnostics_update = Task::ready(());
17856 self.inline_diagnostics.clear();
17857 }
17858
17859 pub fn disable_word_completions(&mut self) {
17860 self.word_completions_enabled = false;
17861 }
17862
17863 pub fn diagnostics_enabled(&self) -> bool {
17864 self.diagnostics_enabled && self.mode.is_full()
17865 }
17866
17867 pub fn inline_diagnostics_enabled(&self) -> bool {
17868 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17869 }
17870
17871 pub fn show_inline_diagnostics(&self) -> bool {
17872 self.show_inline_diagnostics
17873 }
17874
17875 pub fn toggle_inline_diagnostics(
17876 &mut self,
17877 _: &ToggleInlineDiagnostics,
17878 window: &mut Window,
17879 cx: &mut Context<Editor>,
17880 ) {
17881 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17882 self.refresh_inline_diagnostics(false, window, cx);
17883 }
17884
17885 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17886 self.diagnostics_max_severity = severity;
17887 self.display_map.update(cx, |display_map, _| {
17888 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17889 });
17890 }
17891
17892 pub fn toggle_diagnostics(
17893 &mut self,
17894 _: &ToggleDiagnostics,
17895 window: &mut Window,
17896 cx: &mut Context<Editor>,
17897 ) {
17898 if !self.diagnostics_enabled() {
17899 return;
17900 }
17901
17902 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17903 EditorSettings::get_global(cx)
17904 .diagnostics_max_severity
17905 .filter(|severity| severity != &DiagnosticSeverity::Off)
17906 .unwrap_or(DiagnosticSeverity::Hint)
17907 } else {
17908 DiagnosticSeverity::Off
17909 };
17910 self.set_max_diagnostics_severity(new_severity, cx);
17911 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17912 self.active_diagnostics = ActiveDiagnostic::None;
17913 self.inline_diagnostics_update = Task::ready(());
17914 self.inline_diagnostics.clear();
17915 } else {
17916 self.refresh_inline_diagnostics(false, window, cx);
17917 }
17918
17919 cx.notify();
17920 }
17921
17922 pub fn toggle_minimap(
17923 &mut self,
17924 _: &ToggleMinimap,
17925 window: &mut Window,
17926 cx: &mut Context<Editor>,
17927 ) {
17928 if self.supports_minimap(cx) {
17929 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17930 }
17931 }
17932
17933 fn refresh_inline_diagnostics(
17934 &mut self,
17935 debounce: bool,
17936 window: &mut Window,
17937 cx: &mut Context<Self>,
17938 ) {
17939 let max_severity = ProjectSettings::get_global(cx)
17940 .diagnostics
17941 .inline
17942 .max_severity
17943 .unwrap_or(self.diagnostics_max_severity);
17944
17945 if !self.inline_diagnostics_enabled()
17946 || !self.diagnostics_enabled()
17947 || !self.show_inline_diagnostics
17948 || max_severity == DiagnosticSeverity::Off
17949 {
17950 self.inline_diagnostics_update = Task::ready(());
17951 self.inline_diagnostics.clear();
17952 return;
17953 }
17954
17955 let debounce_ms = ProjectSettings::get_global(cx)
17956 .diagnostics
17957 .inline
17958 .update_debounce_ms;
17959 let debounce = if debounce && debounce_ms > 0 {
17960 Some(Duration::from_millis(debounce_ms))
17961 } else {
17962 None
17963 };
17964 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17965 if let Some(debounce) = debounce {
17966 cx.background_executor().timer(debounce).await;
17967 }
17968 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17969 editor
17970 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17971 .ok()
17972 }) else {
17973 return;
17974 };
17975
17976 let new_inline_diagnostics = cx
17977 .background_spawn(async move {
17978 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17979 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17980 let message = diagnostic_entry
17981 .diagnostic
17982 .message
17983 .split_once('\n')
17984 .map(|(line, _)| line)
17985 .map(SharedString::new)
17986 .unwrap_or_else(|| {
17987 SharedString::new(&*diagnostic_entry.diagnostic.message)
17988 });
17989 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17990 let (Ok(i) | Err(i)) = inline_diagnostics
17991 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17992 inline_diagnostics.insert(
17993 i,
17994 (
17995 start_anchor,
17996 InlineDiagnostic {
17997 message,
17998 group_id: diagnostic_entry.diagnostic.group_id,
17999 start: diagnostic_entry.range.start.to_point(&snapshot),
18000 is_primary: diagnostic_entry.diagnostic.is_primary,
18001 severity: diagnostic_entry.diagnostic.severity,
18002 },
18003 ),
18004 );
18005 }
18006 inline_diagnostics
18007 })
18008 .await;
18009
18010 editor
18011 .update(cx, |editor, cx| {
18012 editor.inline_diagnostics = new_inline_diagnostics;
18013 cx.notify();
18014 })
18015 .ok();
18016 });
18017 }
18018
18019 fn pull_diagnostics(
18020 &mut self,
18021 buffer_id: Option<BufferId>,
18022 window: &Window,
18023 cx: &mut Context<Self>,
18024 ) -> Option<()> {
18025 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18026 return None;
18027 }
18028 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18029 .diagnostics
18030 .lsp_pull_diagnostics;
18031 if !pull_diagnostics_settings.enabled {
18032 return None;
18033 }
18034 let project = self.project()?.downgrade();
18035 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18036 let mut buffers = self.buffer.read(cx).all_buffers();
18037 buffers.retain(|buffer| {
18038 let buffer_id_to_retain = buffer.read(cx).remote_id();
18039 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18040 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18041 });
18042 if buffers.is_empty() {
18043 self.pull_diagnostics_task = Task::ready(());
18044 return None;
18045 }
18046
18047 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18048 cx.background_executor().timer(debounce).await;
18049
18050 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18051 buffers
18052 .into_iter()
18053 .filter_map(|buffer| {
18054 project
18055 .update(cx, |project, cx| {
18056 project.lsp_store().update(cx, |lsp_store, cx| {
18057 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18058 })
18059 })
18060 .ok()
18061 })
18062 .collect::<FuturesUnordered<_>>()
18063 }) else {
18064 return;
18065 };
18066
18067 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18068 match pull_task {
18069 Ok(()) => {
18070 if editor
18071 .update_in(cx, |editor, window, cx| {
18072 editor.update_diagnostics_state(window, cx);
18073 })
18074 .is_err()
18075 {
18076 return;
18077 }
18078 }
18079 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18080 }
18081 }
18082 });
18083
18084 Some(())
18085 }
18086
18087 pub fn set_selections_from_remote(
18088 &mut self,
18089 selections: Vec<Selection<Anchor>>,
18090 pending_selection: Option<Selection<Anchor>>,
18091 window: &mut Window,
18092 cx: &mut Context<Self>,
18093 ) {
18094 let old_cursor_position = self.selections.newest_anchor().head();
18095 self.selections.change_with(cx, |s| {
18096 s.select_anchors(selections);
18097 if let Some(pending_selection) = pending_selection {
18098 s.set_pending(pending_selection, SelectMode::Character);
18099 } else {
18100 s.clear_pending();
18101 }
18102 });
18103 self.selections_did_change(
18104 false,
18105 &old_cursor_position,
18106 SelectionEffects::default(),
18107 window,
18108 cx,
18109 );
18110 }
18111
18112 pub fn transact(
18113 &mut self,
18114 window: &mut Window,
18115 cx: &mut Context<Self>,
18116 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18117 ) -> Option<TransactionId> {
18118 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18119 this.start_transaction_at(Instant::now(), window, cx);
18120 update(this, window, cx);
18121 this.end_transaction_at(Instant::now(), cx)
18122 })
18123 }
18124
18125 pub fn start_transaction_at(
18126 &mut self,
18127 now: Instant,
18128 window: &mut Window,
18129 cx: &mut Context<Self>,
18130 ) -> Option<TransactionId> {
18131 self.end_selection(window, cx);
18132 if let Some(tx_id) = self
18133 .buffer
18134 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18135 {
18136 self.selection_history
18137 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18138 cx.emit(EditorEvent::TransactionBegun {
18139 transaction_id: tx_id,
18140 });
18141 Some(tx_id)
18142 } else {
18143 None
18144 }
18145 }
18146
18147 pub fn end_transaction_at(
18148 &mut self,
18149 now: Instant,
18150 cx: &mut Context<Self>,
18151 ) -> Option<TransactionId> {
18152 if let Some(transaction_id) = self
18153 .buffer
18154 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18155 {
18156 if let Some((_, end_selections)) =
18157 self.selection_history.transaction_mut(transaction_id)
18158 {
18159 *end_selections = Some(self.selections.disjoint_anchors_arc());
18160 } else {
18161 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18162 }
18163
18164 cx.emit(EditorEvent::Edited { transaction_id });
18165 Some(transaction_id)
18166 } else {
18167 None
18168 }
18169 }
18170
18171 pub fn modify_transaction_selection_history(
18172 &mut self,
18173 transaction_id: TransactionId,
18174 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18175 ) -> bool {
18176 self.selection_history
18177 .transaction_mut(transaction_id)
18178 .map(modify)
18179 .is_some()
18180 }
18181
18182 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18183 if self.selection_mark_mode {
18184 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18185 s.move_with(|_, sel| {
18186 sel.collapse_to(sel.head(), SelectionGoal::None);
18187 });
18188 })
18189 }
18190 self.selection_mark_mode = true;
18191 cx.notify();
18192 }
18193
18194 pub fn swap_selection_ends(
18195 &mut self,
18196 _: &actions::SwapSelectionEnds,
18197 window: &mut Window,
18198 cx: &mut Context<Self>,
18199 ) {
18200 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18201 s.move_with(|_, sel| {
18202 if sel.start != sel.end {
18203 sel.reversed = !sel.reversed
18204 }
18205 });
18206 });
18207 self.request_autoscroll(Autoscroll::newest(), cx);
18208 cx.notify();
18209 }
18210
18211 pub fn toggle_focus(
18212 workspace: &mut Workspace,
18213 _: &actions::ToggleFocus,
18214 window: &mut Window,
18215 cx: &mut Context<Workspace>,
18216 ) {
18217 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18218 return;
18219 };
18220 workspace.activate_item(&item, true, true, window, cx);
18221 }
18222
18223 pub fn toggle_fold(
18224 &mut self,
18225 _: &actions::ToggleFold,
18226 window: &mut Window,
18227 cx: &mut Context<Self>,
18228 ) {
18229 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18230 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18231 let selection = self.selections.newest::<Point>(&display_map);
18232
18233 let range = if selection.is_empty() {
18234 let point = selection.head().to_display_point(&display_map);
18235 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18236 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18237 .to_point(&display_map);
18238 start..end
18239 } else {
18240 selection.range()
18241 };
18242 if display_map.folds_in_range(range).next().is_some() {
18243 self.unfold_lines(&Default::default(), window, cx)
18244 } else {
18245 self.fold(&Default::default(), window, cx)
18246 }
18247 } else {
18248 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18249 let buffer_ids: HashSet<_> = self
18250 .selections
18251 .disjoint_anchor_ranges()
18252 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18253 .collect();
18254
18255 let should_unfold = buffer_ids
18256 .iter()
18257 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18258
18259 for buffer_id in buffer_ids {
18260 if should_unfold {
18261 self.unfold_buffer(buffer_id, cx);
18262 } else {
18263 self.fold_buffer(buffer_id, cx);
18264 }
18265 }
18266 }
18267 }
18268
18269 pub fn toggle_fold_recursive(
18270 &mut self,
18271 _: &actions::ToggleFoldRecursive,
18272 window: &mut Window,
18273 cx: &mut Context<Self>,
18274 ) {
18275 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18276
18277 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18278 let range = if selection.is_empty() {
18279 let point = selection.head().to_display_point(&display_map);
18280 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18281 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18282 .to_point(&display_map);
18283 start..end
18284 } else {
18285 selection.range()
18286 };
18287 if display_map.folds_in_range(range).next().is_some() {
18288 self.unfold_recursive(&Default::default(), window, cx)
18289 } else {
18290 self.fold_recursive(&Default::default(), window, cx)
18291 }
18292 }
18293
18294 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18295 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18296 let mut to_fold = Vec::new();
18297 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18298 let selections = self.selections.all_adjusted(&display_map);
18299
18300 for selection in selections {
18301 let range = selection.range().sorted();
18302 let buffer_start_row = range.start.row;
18303
18304 if range.start.row != range.end.row {
18305 let mut found = false;
18306 let mut row = range.start.row;
18307 while row <= range.end.row {
18308 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18309 {
18310 found = true;
18311 row = crease.range().end.row + 1;
18312 to_fold.push(crease);
18313 } else {
18314 row += 1
18315 }
18316 }
18317 if found {
18318 continue;
18319 }
18320 }
18321
18322 for row in (0..=range.start.row).rev() {
18323 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18324 && crease.range().end.row >= buffer_start_row
18325 {
18326 to_fold.push(crease);
18327 if row <= range.start.row {
18328 break;
18329 }
18330 }
18331 }
18332 }
18333
18334 self.fold_creases(to_fold, true, window, cx);
18335 } else {
18336 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18337 let buffer_ids = self
18338 .selections
18339 .disjoint_anchor_ranges()
18340 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18341 .collect::<HashSet<_>>();
18342 for buffer_id in buffer_ids {
18343 self.fold_buffer(buffer_id, cx);
18344 }
18345 }
18346 }
18347
18348 pub fn toggle_fold_all(
18349 &mut self,
18350 _: &actions::ToggleFoldAll,
18351 window: &mut Window,
18352 cx: &mut Context<Self>,
18353 ) {
18354 if self.buffer.read(cx).is_singleton() {
18355 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18356 let has_folds = display_map
18357 .folds_in_range(0..display_map.buffer_snapshot().len())
18358 .next()
18359 .is_some();
18360
18361 if has_folds {
18362 self.unfold_all(&actions::UnfoldAll, window, cx);
18363 } else {
18364 self.fold_all(&actions::FoldAll, window, cx);
18365 }
18366 } else {
18367 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18368 let should_unfold = buffer_ids
18369 .iter()
18370 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18371
18372 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18373 editor
18374 .update_in(cx, |editor, _, cx| {
18375 for buffer_id in buffer_ids {
18376 if should_unfold {
18377 editor.unfold_buffer(buffer_id, cx);
18378 } else {
18379 editor.fold_buffer(buffer_id, cx);
18380 }
18381 }
18382 })
18383 .ok();
18384 });
18385 }
18386 }
18387
18388 fn fold_at_level(
18389 &mut self,
18390 fold_at: &FoldAtLevel,
18391 window: &mut Window,
18392 cx: &mut Context<Self>,
18393 ) {
18394 if !self.buffer.read(cx).is_singleton() {
18395 return;
18396 }
18397
18398 let fold_at_level = fold_at.0;
18399 let snapshot = self.buffer.read(cx).snapshot(cx);
18400 let mut to_fold = Vec::new();
18401 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18402
18403 let row_ranges_to_keep: Vec<Range<u32>> = self
18404 .selections
18405 .all::<Point>(&self.display_snapshot(cx))
18406 .into_iter()
18407 .map(|sel| sel.start.row..sel.end.row)
18408 .collect();
18409
18410 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18411 while start_row < end_row {
18412 match self
18413 .snapshot(window, cx)
18414 .crease_for_buffer_row(MultiBufferRow(start_row))
18415 {
18416 Some(crease) => {
18417 let nested_start_row = crease.range().start.row + 1;
18418 let nested_end_row = crease.range().end.row;
18419
18420 if current_level < fold_at_level {
18421 stack.push((nested_start_row, nested_end_row, current_level + 1));
18422 } else if current_level == fold_at_level {
18423 // Fold iff there is no selection completely contained within the fold region
18424 if !row_ranges_to_keep.iter().any(|selection| {
18425 selection.end >= nested_start_row
18426 && selection.start <= nested_end_row
18427 }) {
18428 to_fold.push(crease);
18429 }
18430 }
18431
18432 start_row = nested_end_row + 1;
18433 }
18434 None => start_row += 1,
18435 }
18436 }
18437 }
18438
18439 self.fold_creases(to_fold, true, window, cx);
18440 }
18441
18442 pub fn fold_at_level_1(
18443 &mut self,
18444 _: &actions::FoldAtLevel1,
18445 window: &mut Window,
18446 cx: &mut Context<Self>,
18447 ) {
18448 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18449 }
18450
18451 pub fn fold_at_level_2(
18452 &mut self,
18453 _: &actions::FoldAtLevel2,
18454 window: &mut Window,
18455 cx: &mut Context<Self>,
18456 ) {
18457 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18458 }
18459
18460 pub fn fold_at_level_3(
18461 &mut self,
18462 _: &actions::FoldAtLevel3,
18463 window: &mut Window,
18464 cx: &mut Context<Self>,
18465 ) {
18466 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18467 }
18468
18469 pub fn fold_at_level_4(
18470 &mut self,
18471 _: &actions::FoldAtLevel4,
18472 window: &mut Window,
18473 cx: &mut Context<Self>,
18474 ) {
18475 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18476 }
18477
18478 pub fn fold_at_level_5(
18479 &mut self,
18480 _: &actions::FoldAtLevel5,
18481 window: &mut Window,
18482 cx: &mut Context<Self>,
18483 ) {
18484 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18485 }
18486
18487 pub fn fold_at_level_6(
18488 &mut self,
18489 _: &actions::FoldAtLevel6,
18490 window: &mut Window,
18491 cx: &mut Context<Self>,
18492 ) {
18493 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18494 }
18495
18496 pub fn fold_at_level_7(
18497 &mut self,
18498 _: &actions::FoldAtLevel7,
18499 window: &mut Window,
18500 cx: &mut Context<Self>,
18501 ) {
18502 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18503 }
18504
18505 pub fn fold_at_level_8(
18506 &mut self,
18507 _: &actions::FoldAtLevel8,
18508 window: &mut Window,
18509 cx: &mut Context<Self>,
18510 ) {
18511 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18512 }
18513
18514 pub fn fold_at_level_9(
18515 &mut self,
18516 _: &actions::FoldAtLevel9,
18517 window: &mut Window,
18518 cx: &mut Context<Self>,
18519 ) {
18520 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18521 }
18522
18523 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18524 if self.buffer.read(cx).is_singleton() {
18525 let mut fold_ranges = Vec::new();
18526 let snapshot = self.buffer.read(cx).snapshot(cx);
18527
18528 for row in 0..snapshot.max_row().0 {
18529 if let Some(foldable_range) = self
18530 .snapshot(window, cx)
18531 .crease_for_buffer_row(MultiBufferRow(row))
18532 {
18533 fold_ranges.push(foldable_range);
18534 }
18535 }
18536
18537 self.fold_creases(fold_ranges, true, window, cx);
18538 } else {
18539 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18540 editor
18541 .update_in(cx, |editor, _, cx| {
18542 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18543 editor.fold_buffer(buffer_id, cx);
18544 }
18545 })
18546 .ok();
18547 });
18548 }
18549 }
18550
18551 pub fn fold_function_bodies(
18552 &mut self,
18553 _: &actions::FoldFunctionBodies,
18554 window: &mut Window,
18555 cx: &mut Context<Self>,
18556 ) {
18557 let snapshot = self.buffer.read(cx).snapshot(cx);
18558
18559 let ranges = snapshot
18560 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18561 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18562 .collect::<Vec<_>>();
18563
18564 let creases = ranges
18565 .into_iter()
18566 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18567 .collect();
18568
18569 self.fold_creases(creases, true, window, cx);
18570 }
18571
18572 pub fn fold_recursive(
18573 &mut self,
18574 _: &actions::FoldRecursive,
18575 window: &mut Window,
18576 cx: &mut Context<Self>,
18577 ) {
18578 let mut to_fold = Vec::new();
18579 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18580 let selections = self.selections.all_adjusted(&display_map);
18581
18582 for selection in selections {
18583 let range = selection.range().sorted();
18584 let buffer_start_row = range.start.row;
18585
18586 if range.start.row != range.end.row {
18587 let mut found = false;
18588 for row in range.start.row..=range.end.row {
18589 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18590 found = true;
18591 to_fold.push(crease);
18592 }
18593 }
18594 if found {
18595 continue;
18596 }
18597 }
18598
18599 for row in (0..=range.start.row).rev() {
18600 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18601 if crease.range().end.row >= buffer_start_row {
18602 to_fold.push(crease);
18603 } else {
18604 break;
18605 }
18606 }
18607 }
18608 }
18609
18610 self.fold_creases(to_fold, true, window, cx);
18611 }
18612
18613 pub fn fold_at(
18614 &mut self,
18615 buffer_row: MultiBufferRow,
18616 window: &mut Window,
18617 cx: &mut Context<Self>,
18618 ) {
18619 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18620
18621 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18622 let autoscroll = self
18623 .selections
18624 .all::<Point>(&display_map)
18625 .iter()
18626 .any(|selection| crease.range().overlaps(&selection.range()));
18627
18628 self.fold_creases(vec![crease], autoscroll, window, cx);
18629 }
18630 }
18631
18632 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18633 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18634 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18635 let buffer = display_map.buffer_snapshot();
18636 let selections = self.selections.all::<Point>(&display_map);
18637 let ranges = selections
18638 .iter()
18639 .map(|s| {
18640 let range = s.display_range(&display_map).sorted();
18641 let mut start = range.start.to_point(&display_map);
18642 let mut end = range.end.to_point(&display_map);
18643 start.column = 0;
18644 end.column = buffer.line_len(MultiBufferRow(end.row));
18645 start..end
18646 })
18647 .collect::<Vec<_>>();
18648
18649 self.unfold_ranges(&ranges, true, true, cx);
18650 } else {
18651 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18652 let buffer_ids = self
18653 .selections
18654 .disjoint_anchor_ranges()
18655 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18656 .collect::<HashSet<_>>();
18657 for buffer_id in buffer_ids {
18658 self.unfold_buffer(buffer_id, cx);
18659 }
18660 }
18661 }
18662
18663 pub fn unfold_recursive(
18664 &mut self,
18665 _: &UnfoldRecursive,
18666 _window: &mut Window,
18667 cx: &mut Context<Self>,
18668 ) {
18669 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18670 let selections = self.selections.all::<Point>(&display_map);
18671 let ranges = selections
18672 .iter()
18673 .map(|s| {
18674 let mut range = s.display_range(&display_map).sorted();
18675 *range.start.column_mut() = 0;
18676 *range.end.column_mut() = display_map.line_len(range.end.row());
18677 let start = range.start.to_point(&display_map);
18678 let end = range.end.to_point(&display_map);
18679 start..end
18680 })
18681 .collect::<Vec<_>>();
18682
18683 self.unfold_ranges(&ranges, true, true, cx);
18684 }
18685
18686 pub fn unfold_at(
18687 &mut self,
18688 buffer_row: MultiBufferRow,
18689 _window: &mut Window,
18690 cx: &mut Context<Self>,
18691 ) {
18692 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18693
18694 let intersection_range = Point::new(buffer_row.0, 0)
18695 ..Point::new(
18696 buffer_row.0,
18697 display_map.buffer_snapshot().line_len(buffer_row),
18698 );
18699
18700 let autoscroll = self
18701 .selections
18702 .all::<Point>(&display_map)
18703 .iter()
18704 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18705
18706 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18707 }
18708
18709 pub fn unfold_all(
18710 &mut self,
18711 _: &actions::UnfoldAll,
18712 _window: &mut Window,
18713 cx: &mut Context<Self>,
18714 ) {
18715 if self.buffer.read(cx).is_singleton() {
18716 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18717 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18718 } else {
18719 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18720 editor
18721 .update(cx, |editor, cx| {
18722 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18723 editor.unfold_buffer(buffer_id, cx);
18724 }
18725 })
18726 .ok();
18727 });
18728 }
18729 }
18730
18731 pub fn fold_selected_ranges(
18732 &mut self,
18733 _: &FoldSelectedRanges,
18734 window: &mut Window,
18735 cx: &mut Context<Self>,
18736 ) {
18737 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18738 let selections = self.selections.all_adjusted(&display_map);
18739 let ranges = selections
18740 .into_iter()
18741 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18742 .collect::<Vec<_>>();
18743 self.fold_creases(ranges, true, window, cx);
18744 }
18745
18746 pub fn fold_ranges<T: ToOffset + Clone>(
18747 &mut self,
18748 ranges: Vec<Range<T>>,
18749 auto_scroll: bool,
18750 window: &mut Window,
18751 cx: &mut Context<Self>,
18752 ) {
18753 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18754 let ranges = ranges
18755 .into_iter()
18756 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18757 .collect::<Vec<_>>();
18758 self.fold_creases(ranges, auto_scroll, window, cx);
18759 }
18760
18761 pub fn fold_creases<T: ToOffset + Clone>(
18762 &mut self,
18763 creases: Vec<Crease<T>>,
18764 auto_scroll: bool,
18765 _window: &mut Window,
18766 cx: &mut Context<Self>,
18767 ) {
18768 if creases.is_empty() {
18769 return;
18770 }
18771
18772 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18773
18774 if auto_scroll {
18775 self.request_autoscroll(Autoscroll::fit(), cx);
18776 }
18777
18778 cx.notify();
18779
18780 self.scrollbar_marker_state.dirty = true;
18781 self.folds_did_change(cx);
18782 }
18783
18784 /// Removes any folds whose ranges intersect any of the given ranges.
18785 pub fn unfold_ranges<T: ToOffset + Clone>(
18786 &mut self,
18787 ranges: &[Range<T>],
18788 inclusive: bool,
18789 auto_scroll: bool,
18790 cx: &mut Context<Self>,
18791 ) {
18792 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18793 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18794 });
18795 self.folds_did_change(cx);
18796 }
18797
18798 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18799 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18800 return;
18801 }
18802 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18803 self.display_map.update(cx, |display_map, cx| {
18804 display_map.fold_buffers([buffer_id], cx)
18805 });
18806 cx.emit(EditorEvent::BufferFoldToggled {
18807 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18808 folded: true,
18809 });
18810 cx.notify();
18811 }
18812
18813 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18814 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18815 return;
18816 }
18817 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18818 self.display_map.update(cx, |display_map, cx| {
18819 display_map.unfold_buffers([buffer_id], cx);
18820 });
18821 cx.emit(EditorEvent::BufferFoldToggled {
18822 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18823 folded: false,
18824 });
18825 cx.notify();
18826 }
18827
18828 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18829 self.display_map.read(cx).is_buffer_folded(buffer)
18830 }
18831
18832 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18833 self.display_map.read(cx).folded_buffers()
18834 }
18835
18836 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18837 self.display_map.update(cx, |display_map, cx| {
18838 display_map.disable_header_for_buffer(buffer_id, cx);
18839 });
18840 cx.notify();
18841 }
18842
18843 /// Removes any folds with the given ranges.
18844 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18845 &mut self,
18846 ranges: &[Range<T>],
18847 type_id: TypeId,
18848 auto_scroll: bool,
18849 cx: &mut Context<Self>,
18850 ) {
18851 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18852 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18853 });
18854 self.folds_did_change(cx);
18855 }
18856
18857 fn remove_folds_with<T: ToOffset + Clone>(
18858 &mut self,
18859 ranges: &[Range<T>],
18860 auto_scroll: bool,
18861 cx: &mut Context<Self>,
18862 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18863 ) {
18864 if ranges.is_empty() {
18865 return;
18866 }
18867
18868 let mut buffers_affected = HashSet::default();
18869 let multi_buffer = self.buffer().read(cx);
18870 for range in ranges {
18871 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18872 buffers_affected.insert(buffer.read(cx).remote_id());
18873 };
18874 }
18875
18876 self.display_map.update(cx, update);
18877
18878 if auto_scroll {
18879 self.request_autoscroll(Autoscroll::fit(), cx);
18880 }
18881
18882 cx.notify();
18883 self.scrollbar_marker_state.dirty = true;
18884 self.active_indent_guides_state.dirty = true;
18885 }
18886
18887 pub fn update_renderer_widths(
18888 &mut self,
18889 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18890 cx: &mut Context<Self>,
18891 ) -> bool {
18892 self.display_map
18893 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18894 }
18895
18896 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18897 self.display_map.read(cx).fold_placeholder.clone()
18898 }
18899
18900 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18901 self.buffer.update(cx, |buffer, cx| {
18902 buffer.set_all_diff_hunks_expanded(cx);
18903 });
18904 }
18905
18906 pub fn expand_all_diff_hunks(
18907 &mut self,
18908 _: &ExpandAllDiffHunks,
18909 _window: &mut Window,
18910 cx: &mut Context<Self>,
18911 ) {
18912 self.buffer.update(cx, |buffer, cx| {
18913 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18914 });
18915 }
18916
18917 pub fn collapse_all_diff_hunks(
18918 &mut self,
18919 _: &CollapseAllDiffHunks,
18920 _window: &mut Window,
18921 cx: &mut Context<Self>,
18922 ) {
18923 self.buffer.update(cx, |buffer, cx| {
18924 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18925 });
18926 }
18927
18928 pub fn toggle_selected_diff_hunks(
18929 &mut self,
18930 _: &ToggleSelectedDiffHunks,
18931 _window: &mut Window,
18932 cx: &mut Context<Self>,
18933 ) {
18934 let ranges: Vec<_> = self
18935 .selections
18936 .disjoint_anchors()
18937 .iter()
18938 .map(|s| s.range())
18939 .collect();
18940 self.toggle_diff_hunks_in_ranges(ranges, cx);
18941 }
18942
18943 pub fn diff_hunks_in_ranges<'a>(
18944 &'a self,
18945 ranges: &'a [Range<Anchor>],
18946 buffer: &'a MultiBufferSnapshot,
18947 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18948 ranges.iter().flat_map(move |range| {
18949 let end_excerpt_id = range.end.excerpt_id;
18950 let range = range.to_point(buffer);
18951 let mut peek_end = range.end;
18952 if range.end.row < buffer.max_row().0 {
18953 peek_end = Point::new(range.end.row + 1, 0);
18954 }
18955 buffer
18956 .diff_hunks_in_range(range.start..peek_end)
18957 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18958 })
18959 }
18960
18961 pub fn has_stageable_diff_hunks_in_ranges(
18962 &self,
18963 ranges: &[Range<Anchor>],
18964 snapshot: &MultiBufferSnapshot,
18965 ) -> bool {
18966 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18967 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18968 }
18969
18970 pub fn toggle_staged_selected_diff_hunks(
18971 &mut self,
18972 _: &::git::ToggleStaged,
18973 _: &mut Window,
18974 cx: &mut Context<Self>,
18975 ) {
18976 let snapshot = self.buffer.read(cx).snapshot(cx);
18977 let ranges: Vec<_> = self
18978 .selections
18979 .disjoint_anchors()
18980 .iter()
18981 .map(|s| s.range())
18982 .collect();
18983 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18984 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18985 }
18986
18987 pub fn set_render_diff_hunk_controls(
18988 &mut self,
18989 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18990 cx: &mut Context<Self>,
18991 ) {
18992 self.render_diff_hunk_controls = render_diff_hunk_controls;
18993 cx.notify();
18994 }
18995
18996 pub fn stage_and_next(
18997 &mut self,
18998 _: &::git::StageAndNext,
18999 window: &mut Window,
19000 cx: &mut Context<Self>,
19001 ) {
19002 self.do_stage_or_unstage_and_next(true, window, cx);
19003 }
19004
19005 pub fn unstage_and_next(
19006 &mut self,
19007 _: &::git::UnstageAndNext,
19008 window: &mut Window,
19009 cx: &mut Context<Self>,
19010 ) {
19011 self.do_stage_or_unstage_and_next(false, window, cx);
19012 }
19013
19014 pub fn stage_or_unstage_diff_hunks(
19015 &mut self,
19016 stage: bool,
19017 ranges: Vec<Range<Anchor>>,
19018 cx: &mut Context<Self>,
19019 ) {
19020 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19021 cx.spawn(async move |this, cx| {
19022 task.await?;
19023 this.update(cx, |this, cx| {
19024 let snapshot = this.buffer.read(cx).snapshot(cx);
19025 let chunk_by = this
19026 .diff_hunks_in_ranges(&ranges, &snapshot)
19027 .chunk_by(|hunk| hunk.buffer_id);
19028 for (buffer_id, hunks) in &chunk_by {
19029 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19030 }
19031 })
19032 })
19033 .detach_and_log_err(cx);
19034 }
19035
19036 fn save_buffers_for_ranges_if_needed(
19037 &mut self,
19038 ranges: &[Range<Anchor>],
19039 cx: &mut Context<Editor>,
19040 ) -> Task<Result<()>> {
19041 let multibuffer = self.buffer.read(cx);
19042 let snapshot = multibuffer.read(cx);
19043 let buffer_ids: HashSet<_> = ranges
19044 .iter()
19045 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19046 .collect();
19047 drop(snapshot);
19048
19049 let mut buffers = HashSet::default();
19050 for buffer_id in buffer_ids {
19051 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19052 let buffer = buffer_entity.read(cx);
19053 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19054 {
19055 buffers.insert(buffer_entity);
19056 }
19057 }
19058 }
19059
19060 if let Some(project) = &self.project {
19061 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19062 } else {
19063 Task::ready(Ok(()))
19064 }
19065 }
19066
19067 fn do_stage_or_unstage_and_next(
19068 &mut self,
19069 stage: bool,
19070 window: &mut Window,
19071 cx: &mut Context<Self>,
19072 ) {
19073 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19074
19075 if ranges.iter().any(|range| range.start != range.end) {
19076 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19077 return;
19078 }
19079
19080 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19081 let snapshot = self.snapshot(window, cx);
19082 let position = self
19083 .selections
19084 .newest::<Point>(&snapshot.display_snapshot)
19085 .head();
19086 let mut row = snapshot
19087 .buffer_snapshot()
19088 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19089 .find(|hunk| hunk.row_range.start.0 > position.row)
19090 .map(|hunk| hunk.row_range.start);
19091
19092 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19093 // Outside of the project diff editor, wrap around to the beginning.
19094 if !all_diff_hunks_expanded {
19095 row = row.or_else(|| {
19096 snapshot
19097 .buffer_snapshot()
19098 .diff_hunks_in_range(Point::zero()..position)
19099 .find(|hunk| hunk.row_range.end.0 < position.row)
19100 .map(|hunk| hunk.row_range.start)
19101 });
19102 }
19103
19104 if let Some(row) = row {
19105 let destination = Point::new(row.0, 0);
19106 let autoscroll = Autoscroll::center();
19107
19108 self.unfold_ranges(&[destination..destination], false, false, cx);
19109 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19110 s.select_ranges([destination..destination]);
19111 });
19112 }
19113 }
19114
19115 fn do_stage_or_unstage(
19116 &self,
19117 stage: bool,
19118 buffer_id: BufferId,
19119 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19120 cx: &mut App,
19121 ) -> Option<()> {
19122 let project = self.project()?;
19123 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19124 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19125 let buffer_snapshot = buffer.read(cx).snapshot();
19126 let file_exists = buffer_snapshot
19127 .file()
19128 .is_some_and(|file| file.disk_state().exists());
19129 diff.update(cx, |diff, cx| {
19130 diff.stage_or_unstage_hunks(
19131 stage,
19132 &hunks
19133 .map(|hunk| buffer_diff::DiffHunk {
19134 buffer_range: hunk.buffer_range,
19135 diff_base_byte_range: hunk.diff_base_byte_range,
19136 secondary_status: hunk.secondary_status,
19137 range: Point::zero()..Point::zero(), // unused
19138 })
19139 .collect::<Vec<_>>(),
19140 &buffer_snapshot,
19141 file_exists,
19142 cx,
19143 )
19144 });
19145 None
19146 }
19147
19148 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19149 let ranges: Vec<_> = self
19150 .selections
19151 .disjoint_anchors()
19152 .iter()
19153 .map(|s| s.range())
19154 .collect();
19155 self.buffer
19156 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19157 }
19158
19159 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19160 self.buffer.update(cx, |buffer, cx| {
19161 let ranges = vec![Anchor::min()..Anchor::max()];
19162 if !buffer.all_diff_hunks_expanded()
19163 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19164 {
19165 buffer.collapse_diff_hunks(ranges, cx);
19166 true
19167 } else {
19168 false
19169 }
19170 })
19171 }
19172
19173 fn toggle_diff_hunks_in_ranges(
19174 &mut self,
19175 ranges: Vec<Range<Anchor>>,
19176 cx: &mut Context<Editor>,
19177 ) {
19178 self.buffer.update(cx, |buffer, cx| {
19179 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19180 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19181 })
19182 }
19183
19184 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19185 self.buffer.update(cx, |buffer, cx| {
19186 let snapshot = buffer.snapshot(cx);
19187 let excerpt_id = range.end.excerpt_id;
19188 let point_range = range.to_point(&snapshot);
19189 let expand = !buffer.single_hunk_is_expanded(range, cx);
19190 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19191 })
19192 }
19193
19194 pub(crate) fn apply_all_diff_hunks(
19195 &mut self,
19196 _: &ApplyAllDiffHunks,
19197 window: &mut Window,
19198 cx: &mut Context<Self>,
19199 ) {
19200 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19201
19202 let buffers = self.buffer.read(cx).all_buffers();
19203 for branch_buffer in buffers {
19204 branch_buffer.update(cx, |branch_buffer, cx| {
19205 branch_buffer.merge_into_base(Vec::new(), cx);
19206 });
19207 }
19208
19209 if let Some(project) = self.project.clone() {
19210 self.save(
19211 SaveOptions {
19212 format: true,
19213 autosave: false,
19214 },
19215 project,
19216 window,
19217 cx,
19218 )
19219 .detach_and_log_err(cx);
19220 }
19221 }
19222
19223 pub(crate) fn apply_selected_diff_hunks(
19224 &mut self,
19225 _: &ApplyDiffHunk,
19226 window: &mut Window,
19227 cx: &mut Context<Self>,
19228 ) {
19229 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19230 let snapshot = self.snapshot(window, cx);
19231 let hunks = snapshot.hunks_for_ranges(
19232 self.selections
19233 .all(&snapshot.display_snapshot)
19234 .into_iter()
19235 .map(|selection| selection.range()),
19236 );
19237 let mut ranges_by_buffer = HashMap::default();
19238 self.transact(window, cx, |editor, _window, cx| {
19239 for hunk in hunks {
19240 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19241 ranges_by_buffer
19242 .entry(buffer.clone())
19243 .or_insert_with(Vec::new)
19244 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19245 }
19246 }
19247
19248 for (buffer, ranges) in ranges_by_buffer {
19249 buffer.update(cx, |buffer, cx| {
19250 buffer.merge_into_base(ranges, cx);
19251 });
19252 }
19253 });
19254
19255 if let Some(project) = self.project.clone() {
19256 self.save(
19257 SaveOptions {
19258 format: true,
19259 autosave: false,
19260 },
19261 project,
19262 window,
19263 cx,
19264 )
19265 .detach_and_log_err(cx);
19266 }
19267 }
19268
19269 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19270 if hovered != self.gutter_hovered {
19271 self.gutter_hovered = hovered;
19272 cx.notify();
19273 }
19274 }
19275
19276 pub fn insert_blocks(
19277 &mut self,
19278 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19279 autoscroll: Option<Autoscroll>,
19280 cx: &mut Context<Self>,
19281 ) -> Vec<CustomBlockId> {
19282 let blocks = self
19283 .display_map
19284 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19285 if let Some(autoscroll) = autoscroll {
19286 self.request_autoscroll(autoscroll, cx);
19287 }
19288 cx.notify();
19289 blocks
19290 }
19291
19292 pub fn resize_blocks(
19293 &mut self,
19294 heights: HashMap<CustomBlockId, u32>,
19295 autoscroll: Option<Autoscroll>,
19296 cx: &mut Context<Self>,
19297 ) {
19298 self.display_map
19299 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19300 if let Some(autoscroll) = autoscroll {
19301 self.request_autoscroll(autoscroll, cx);
19302 }
19303 cx.notify();
19304 }
19305
19306 pub fn replace_blocks(
19307 &mut self,
19308 renderers: HashMap<CustomBlockId, RenderBlock>,
19309 autoscroll: Option<Autoscroll>,
19310 cx: &mut Context<Self>,
19311 ) {
19312 self.display_map
19313 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19314 if let Some(autoscroll) = autoscroll {
19315 self.request_autoscroll(autoscroll, cx);
19316 }
19317 cx.notify();
19318 }
19319
19320 pub fn remove_blocks(
19321 &mut self,
19322 block_ids: HashSet<CustomBlockId>,
19323 autoscroll: Option<Autoscroll>,
19324 cx: &mut Context<Self>,
19325 ) {
19326 self.display_map.update(cx, |display_map, cx| {
19327 display_map.remove_blocks(block_ids, cx)
19328 });
19329 if let Some(autoscroll) = autoscroll {
19330 self.request_autoscroll(autoscroll, cx);
19331 }
19332 cx.notify();
19333 }
19334
19335 pub fn row_for_block(
19336 &self,
19337 block_id: CustomBlockId,
19338 cx: &mut Context<Self>,
19339 ) -> Option<DisplayRow> {
19340 self.display_map
19341 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19342 }
19343
19344 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19345 self.focused_block = Some(focused_block);
19346 }
19347
19348 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19349 self.focused_block.take()
19350 }
19351
19352 pub fn insert_creases(
19353 &mut self,
19354 creases: impl IntoIterator<Item = Crease<Anchor>>,
19355 cx: &mut Context<Self>,
19356 ) -> Vec<CreaseId> {
19357 self.display_map
19358 .update(cx, |map, cx| map.insert_creases(creases, cx))
19359 }
19360
19361 pub fn remove_creases(
19362 &mut self,
19363 ids: impl IntoIterator<Item = CreaseId>,
19364 cx: &mut Context<Self>,
19365 ) -> Vec<(CreaseId, Range<Anchor>)> {
19366 self.display_map
19367 .update(cx, |map, cx| map.remove_creases(ids, cx))
19368 }
19369
19370 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19371 self.display_map
19372 .update(cx, |map, cx| map.snapshot(cx))
19373 .longest_row()
19374 }
19375
19376 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19377 self.display_map
19378 .update(cx, |map, cx| map.snapshot(cx))
19379 .max_point()
19380 }
19381
19382 pub fn text(&self, cx: &App) -> String {
19383 self.buffer.read(cx).read(cx).text()
19384 }
19385
19386 pub fn is_empty(&self, cx: &App) -> bool {
19387 self.buffer.read(cx).read(cx).is_empty()
19388 }
19389
19390 pub fn text_option(&self, cx: &App) -> Option<String> {
19391 let text = self.text(cx);
19392 let text = text.trim();
19393
19394 if text.is_empty() {
19395 return None;
19396 }
19397
19398 Some(text.to_string())
19399 }
19400
19401 pub fn set_text(
19402 &mut self,
19403 text: impl Into<Arc<str>>,
19404 window: &mut Window,
19405 cx: &mut Context<Self>,
19406 ) {
19407 self.transact(window, cx, |this, _, cx| {
19408 this.buffer
19409 .read(cx)
19410 .as_singleton()
19411 .expect("you can only call set_text on editors for singleton buffers")
19412 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19413 });
19414 }
19415
19416 pub fn display_text(&self, cx: &mut App) -> String {
19417 self.display_map
19418 .update(cx, |map, cx| map.snapshot(cx))
19419 .text()
19420 }
19421
19422 fn create_minimap(
19423 &self,
19424 minimap_settings: MinimapSettings,
19425 window: &mut Window,
19426 cx: &mut Context<Self>,
19427 ) -> Option<Entity<Self>> {
19428 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19429 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19430 }
19431
19432 fn initialize_new_minimap(
19433 &self,
19434 minimap_settings: MinimapSettings,
19435 window: &mut Window,
19436 cx: &mut Context<Self>,
19437 ) -> Entity<Self> {
19438 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19439
19440 let mut minimap = Editor::new_internal(
19441 EditorMode::Minimap {
19442 parent: cx.weak_entity(),
19443 },
19444 self.buffer.clone(),
19445 None,
19446 Some(self.display_map.clone()),
19447 window,
19448 cx,
19449 );
19450 minimap.scroll_manager.clone_state(&self.scroll_manager);
19451 minimap.set_text_style_refinement(TextStyleRefinement {
19452 font_size: Some(MINIMAP_FONT_SIZE),
19453 font_weight: Some(MINIMAP_FONT_WEIGHT),
19454 ..Default::default()
19455 });
19456 minimap.update_minimap_configuration(minimap_settings, cx);
19457 cx.new(|_| minimap)
19458 }
19459
19460 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19461 let current_line_highlight = minimap_settings
19462 .current_line_highlight
19463 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19464 self.set_current_line_highlight(Some(current_line_highlight));
19465 }
19466
19467 pub fn minimap(&self) -> Option<&Entity<Self>> {
19468 self.minimap
19469 .as_ref()
19470 .filter(|_| self.minimap_visibility.visible())
19471 }
19472
19473 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19474 let mut wrap_guides = smallvec![];
19475
19476 if self.show_wrap_guides == Some(false) {
19477 return wrap_guides;
19478 }
19479
19480 let settings = self.buffer.read(cx).language_settings(cx);
19481 if settings.show_wrap_guides {
19482 match self.soft_wrap_mode(cx) {
19483 SoftWrap::Column(soft_wrap) => {
19484 wrap_guides.push((soft_wrap as usize, true));
19485 }
19486 SoftWrap::Bounded(soft_wrap) => {
19487 wrap_guides.push((soft_wrap as usize, true));
19488 }
19489 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19490 }
19491 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19492 }
19493
19494 wrap_guides
19495 }
19496
19497 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19498 let settings = self.buffer.read(cx).language_settings(cx);
19499 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19500 match mode {
19501 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19502 SoftWrap::None
19503 }
19504 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19505 language_settings::SoftWrap::PreferredLineLength => {
19506 SoftWrap::Column(settings.preferred_line_length)
19507 }
19508 language_settings::SoftWrap::Bounded => {
19509 SoftWrap::Bounded(settings.preferred_line_length)
19510 }
19511 }
19512 }
19513
19514 pub fn set_soft_wrap_mode(
19515 &mut self,
19516 mode: language_settings::SoftWrap,
19517
19518 cx: &mut Context<Self>,
19519 ) {
19520 self.soft_wrap_mode_override = Some(mode);
19521 cx.notify();
19522 }
19523
19524 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19525 self.hard_wrap = hard_wrap;
19526 cx.notify();
19527 }
19528
19529 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19530 self.text_style_refinement = Some(style);
19531 }
19532
19533 /// called by the Element so we know what style we were most recently rendered with.
19534 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19535 // We intentionally do not inform the display map about the minimap style
19536 // so that wrapping is not recalculated and stays consistent for the editor
19537 // and its linked minimap.
19538 if !self.mode.is_minimap() {
19539 let font = style.text.font();
19540 let font_size = style.text.font_size.to_pixels(window.rem_size());
19541 let display_map = self
19542 .placeholder_display_map
19543 .as_ref()
19544 .filter(|_| self.is_empty(cx))
19545 .unwrap_or(&self.display_map);
19546
19547 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19548 }
19549 self.style = Some(style);
19550 }
19551
19552 pub fn style(&self) -> Option<&EditorStyle> {
19553 self.style.as_ref()
19554 }
19555
19556 // Called by the element. This method is not designed to be called outside of the editor
19557 // element's layout code because it does not notify when rewrapping is computed synchronously.
19558 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19559 if self.is_empty(cx) {
19560 self.placeholder_display_map
19561 .as_ref()
19562 .map_or(false, |display_map| {
19563 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19564 })
19565 } else {
19566 self.display_map
19567 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19568 }
19569 }
19570
19571 pub fn set_soft_wrap(&mut self) {
19572 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19573 }
19574
19575 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19576 if self.soft_wrap_mode_override.is_some() {
19577 self.soft_wrap_mode_override.take();
19578 } else {
19579 let soft_wrap = match self.soft_wrap_mode(cx) {
19580 SoftWrap::GitDiff => return,
19581 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19582 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19583 language_settings::SoftWrap::None
19584 }
19585 };
19586 self.soft_wrap_mode_override = Some(soft_wrap);
19587 }
19588 cx.notify();
19589 }
19590
19591 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19592 let Some(workspace) = self.workspace() else {
19593 return;
19594 };
19595 let fs = workspace.read(cx).app_state().fs.clone();
19596 let current_show = TabBarSettings::get_global(cx).show;
19597 update_settings_file(fs, cx, move |setting, _| {
19598 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19599 });
19600 }
19601
19602 pub fn toggle_indent_guides(
19603 &mut self,
19604 _: &ToggleIndentGuides,
19605 _: &mut Window,
19606 cx: &mut Context<Self>,
19607 ) {
19608 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19609 self.buffer
19610 .read(cx)
19611 .language_settings(cx)
19612 .indent_guides
19613 .enabled
19614 });
19615 self.show_indent_guides = Some(!currently_enabled);
19616 cx.notify();
19617 }
19618
19619 fn should_show_indent_guides(&self) -> Option<bool> {
19620 self.show_indent_guides
19621 }
19622
19623 pub fn toggle_line_numbers(
19624 &mut self,
19625 _: &ToggleLineNumbers,
19626 _: &mut Window,
19627 cx: &mut Context<Self>,
19628 ) {
19629 let mut editor_settings = EditorSettings::get_global(cx).clone();
19630 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19631 EditorSettings::override_global(editor_settings, cx);
19632 }
19633
19634 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19635 if let Some(show_line_numbers) = self.show_line_numbers {
19636 return show_line_numbers;
19637 }
19638 EditorSettings::get_global(cx).gutter.line_numbers
19639 }
19640
19641 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19642 match (
19643 self.use_relative_line_numbers,
19644 EditorSettings::get_global(cx).relative_line_numbers,
19645 ) {
19646 (None, setting) => setting,
19647 (Some(false), _) => RelativeLineNumbers::Disabled,
19648 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19649 (Some(true), _) => RelativeLineNumbers::Enabled,
19650 }
19651 }
19652
19653 pub fn toggle_relative_line_numbers(
19654 &mut self,
19655 _: &ToggleRelativeLineNumbers,
19656 _: &mut Window,
19657 cx: &mut Context<Self>,
19658 ) {
19659 let is_relative = self.relative_line_numbers(cx);
19660 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19661 }
19662
19663 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19664 self.use_relative_line_numbers = is_relative;
19665 cx.notify();
19666 }
19667
19668 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19669 self.show_gutter = show_gutter;
19670 cx.notify();
19671 }
19672
19673 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19674 self.show_scrollbars = ScrollbarAxes {
19675 horizontal: show,
19676 vertical: show,
19677 };
19678 cx.notify();
19679 }
19680
19681 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19682 self.show_scrollbars.vertical = show;
19683 cx.notify();
19684 }
19685
19686 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19687 self.show_scrollbars.horizontal = show;
19688 cx.notify();
19689 }
19690
19691 pub fn set_minimap_visibility(
19692 &mut self,
19693 minimap_visibility: MinimapVisibility,
19694 window: &mut Window,
19695 cx: &mut Context<Self>,
19696 ) {
19697 if self.minimap_visibility != minimap_visibility {
19698 if minimap_visibility.visible() && self.minimap.is_none() {
19699 let minimap_settings = EditorSettings::get_global(cx).minimap;
19700 self.minimap =
19701 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19702 }
19703 self.minimap_visibility = minimap_visibility;
19704 cx.notify();
19705 }
19706 }
19707
19708 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19709 self.set_show_scrollbars(false, cx);
19710 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19711 }
19712
19713 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19714 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19715 }
19716
19717 /// Normally the text in full mode and auto height editors is padded on the
19718 /// left side by roughly half a character width for improved hit testing.
19719 ///
19720 /// Use this method to disable this for cases where this is not wanted (e.g.
19721 /// if you want to align the editor text with some other text above or below)
19722 /// or if you want to add this padding to single-line editors.
19723 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19724 self.offset_content = offset_content;
19725 cx.notify();
19726 }
19727
19728 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19729 self.show_line_numbers = Some(show_line_numbers);
19730 cx.notify();
19731 }
19732
19733 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19734 self.disable_expand_excerpt_buttons = true;
19735 cx.notify();
19736 }
19737
19738 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19739 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19740 cx.notify();
19741 }
19742
19743 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19744 self.show_code_actions = Some(show_code_actions);
19745 cx.notify();
19746 }
19747
19748 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19749 self.show_runnables = Some(show_runnables);
19750 cx.notify();
19751 }
19752
19753 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19754 self.show_breakpoints = Some(show_breakpoints);
19755 cx.notify();
19756 }
19757
19758 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19759 if self.display_map.read(cx).masked != masked {
19760 self.display_map.update(cx, |map, _| map.masked = masked);
19761 }
19762 cx.notify()
19763 }
19764
19765 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19766 self.show_wrap_guides = Some(show_wrap_guides);
19767 cx.notify();
19768 }
19769
19770 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19771 self.show_indent_guides = Some(show_indent_guides);
19772 cx.notify();
19773 }
19774
19775 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19776 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19777 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19778 && let Some(dir) = file.abs_path(cx).parent()
19779 {
19780 return Some(dir.to_owned());
19781 }
19782 }
19783
19784 None
19785 }
19786
19787 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19788 self.active_excerpt(cx)?
19789 .1
19790 .read(cx)
19791 .file()
19792 .and_then(|f| f.as_local())
19793 }
19794
19795 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19796 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19797 let buffer = buffer.read(cx);
19798 if let Some(project_path) = buffer.project_path(cx) {
19799 let project = self.project()?.read(cx);
19800 project.absolute_path(&project_path, cx)
19801 } else {
19802 buffer
19803 .file()
19804 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19805 }
19806 })
19807 }
19808
19809 pub fn reveal_in_finder(
19810 &mut self,
19811 _: &RevealInFileManager,
19812 _window: &mut Window,
19813 cx: &mut Context<Self>,
19814 ) {
19815 if let Some(target) = self.target_file(cx) {
19816 cx.reveal_path(&target.abs_path(cx));
19817 }
19818 }
19819
19820 pub fn copy_path(
19821 &mut self,
19822 _: &zed_actions::workspace::CopyPath,
19823 _window: &mut Window,
19824 cx: &mut Context<Self>,
19825 ) {
19826 if let Some(path) = self.target_file_abs_path(cx)
19827 && let Some(path) = path.to_str()
19828 {
19829 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19830 } else {
19831 cx.propagate();
19832 }
19833 }
19834
19835 pub fn copy_relative_path(
19836 &mut self,
19837 _: &zed_actions::workspace::CopyRelativePath,
19838 _window: &mut Window,
19839 cx: &mut Context<Self>,
19840 ) {
19841 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19842 let project = self.project()?.read(cx);
19843 let path = buffer.read(cx).file()?.path();
19844 let path = path.display(project.path_style(cx));
19845 Some(path)
19846 }) {
19847 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19848 } else {
19849 cx.propagate();
19850 }
19851 }
19852
19853 /// Returns the project path for the editor's buffer, if any buffer is
19854 /// opened in the editor.
19855 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19856 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19857 buffer.read(cx).project_path(cx)
19858 } else {
19859 None
19860 }
19861 }
19862
19863 // Returns true if the editor handled a go-to-line request
19864 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19865 maybe!({
19866 let breakpoint_store = self.breakpoint_store.as_ref()?;
19867
19868 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19869 else {
19870 self.clear_row_highlights::<ActiveDebugLine>();
19871 return None;
19872 };
19873
19874 let position = active_stack_frame.position;
19875 let buffer_id = position.buffer_id?;
19876 let snapshot = self
19877 .project
19878 .as_ref()?
19879 .read(cx)
19880 .buffer_for_id(buffer_id, cx)?
19881 .read(cx)
19882 .snapshot();
19883
19884 let mut handled = false;
19885 for (id, ExcerptRange { context, .. }) in
19886 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19887 {
19888 if context.start.cmp(&position, &snapshot).is_ge()
19889 || context.end.cmp(&position, &snapshot).is_lt()
19890 {
19891 continue;
19892 }
19893 let snapshot = self.buffer.read(cx).snapshot(cx);
19894 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19895
19896 handled = true;
19897 self.clear_row_highlights::<ActiveDebugLine>();
19898
19899 self.go_to_line::<ActiveDebugLine>(
19900 multibuffer_anchor,
19901 Some(cx.theme().colors().editor_debugger_active_line_background),
19902 window,
19903 cx,
19904 );
19905
19906 cx.notify();
19907 }
19908
19909 handled.then_some(())
19910 })
19911 .is_some()
19912 }
19913
19914 pub fn copy_file_name_without_extension(
19915 &mut self,
19916 _: &CopyFileNameWithoutExtension,
19917 _: &mut Window,
19918 cx: &mut Context<Self>,
19919 ) {
19920 if let Some(file) = self.target_file(cx)
19921 && let Some(file_stem) = file.path().file_stem()
19922 {
19923 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19924 }
19925 }
19926
19927 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19928 if let Some(file) = self.target_file(cx)
19929 && let Some(name) = file.path().file_name()
19930 {
19931 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19932 }
19933 }
19934
19935 pub fn toggle_git_blame(
19936 &mut self,
19937 _: &::git::Blame,
19938 window: &mut Window,
19939 cx: &mut Context<Self>,
19940 ) {
19941 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19942
19943 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19944 self.start_git_blame(true, window, cx);
19945 }
19946
19947 cx.notify();
19948 }
19949
19950 pub fn toggle_git_blame_inline(
19951 &mut self,
19952 _: &ToggleGitBlameInline,
19953 window: &mut Window,
19954 cx: &mut Context<Self>,
19955 ) {
19956 self.toggle_git_blame_inline_internal(true, window, cx);
19957 cx.notify();
19958 }
19959
19960 pub fn open_git_blame_commit(
19961 &mut self,
19962 _: &OpenGitBlameCommit,
19963 window: &mut Window,
19964 cx: &mut Context<Self>,
19965 ) {
19966 self.open_git_blame_commit_internal(window, cx);
19967 }
19968
19969 fn open_git_blame_commit_internal(
19970 &mut self,
19971 window: &mut Window,
19972 cx: &mut Context<Self>,
19973 ) -> Option<()> {
19974 let blame = self.blame.as_ref()?;
19975 let snapshot = self.snapshot(window, cx);
19976 let cursor = self
19977 .selections
19978 .newest::<Point>(&snapshot.display_snapshot)
19979 .head();
19980 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19981 let (_, blame_entry) = blame
19982 .update(cx, |blame, cx| {
19983 blame
19984 .blame_for_rows(
19985 &[RowInfo {
19986 buffer_id: Some(buffer.remote_id()),
19987 buffer_row: Some(point.row),
19988 ..Default::default()
19989 }],
19990 cx,
19991 )
19992 .next()
19993 })
19994 .flatten()?;
19995 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19996 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19997 let workspace = self.workspace()?.downgrade();
19998 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19999 None
20000 }
20001
20002 pub fn git_blame_inline_enabled(&self) -> bool {
20003 self.git_blame_inline_enabled
20004 }
20005
20006 pub fn toggle_selection_menu(
20007 &mut self,
20008 _: &ToggleSelectionMenu,
20009 _: &mut Window,
20010 cx: &mut Context<Self>,
20011 ) {
20012 self.show_selection_menu = self
20013 .show_selection_menu
20014 .map(|show_selections_menu| !show_selections_menu)
20015 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20016
20017 cx.notify();
20018 }
20019
20020 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20021 self.show_selection_menu
20022 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20023 }
20024
20025 fn start_git_blame(
20026 &mut self,
20027 user_triggered: bool,
20028 window: &mut Window,
20029 cx: &mut Context<Self>,
20030 ) {
20031 if let Some(project) = self.project() {
20032 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20033 && buffer.read(cx).file().is_none()
20034 {
20035 return;
20036 }
20037
20038 let focused = self.focus_handle(cx).contains_focused(window, cx);
20039
20040 let project = project.clone();
20041 let blame = cx
20042 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20043 self.blame_subscription =
20044 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20045 self.blame = Some(blame);
20046 }
20047 }
20048
20049 fn toggle_git_blame_inline_internal(
20050 &mut self,
20051 user_triggered: bool,
20052 window: &mut Window,
20053 cx: &mut Context<Self>,
20054 ) {
20055 if self.git_blame_inline_enabled {
20056 self.git_blame_inline_enabled = false;
20057 self.show_git_blame_inline = false;
20058 self.show_git_blame_inline_delay_task.take();
20059 } else {
20060 self.git_blame_inline_enabled = true;
20061 self.start_git_blame_inline(user_triggered, window, cx);
20062 }
20063
20064 cx.notify();
20065 }
20066
20067 fn start_git_blame_inline(
20068 &mut self,
20069 user_triggered: bool,
20070 window: &mut Window,
20071 cx: &mut Context<Self>,
20072 ) {
20073 self.start_git_blame(user_triggered, window, cx);
20074
20075 if ProjectSettings::get_global(cx)
20076 .git
20077 .inline_blame_delay()
20078 .is_some()
20079 {
20080 self.start_inline_blame_timer(window, cx);
20081 } else {
20082 self.show_git_blame_inline = true
20083 }
20084 }
20085
20086 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20087 self.blame.as_ref()
20088 }
20089
20090 pub fn show_git_blame_gutter(&self) -> bool {
20091 self.show_git_blame_gutter
20092 }
20093
20094 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20095 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20096 }
20097
20098 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20099 self.show_git_blame_inline
20100 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20101 && !self.newest_selection_head_on_empty_line(cx)
20102 && self.has_blame_entries(cx)
20103 }
20104
20105 fn has_blame_entries(&self, cx: &App) -> bool {
20106 self.blame()
20107 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20108 }
20109
20110 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20111 let cursor_anchor = self.selections.newest_anchor().head();
20112
20113 let snapshot = self.buffer.read(cx).snapshot(cx);
20114 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20115
20116 snapshot.line_len(buffer_row) == 0
20117 }
20118
20119 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20120 let buffer_and_selection = maybe!({
20121 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20122 let selection_range = selection.range();
20123
20124 let multi_buffer = self.buffer().read(cx);
20125 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20126 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20127
20128 let (buffer, range, _) = if selection.reversed {
20129 buffer_ranges.first()
20130 } else {
20131 buffer_ranges.last()
20132 }?;
20133
20134 let selection = text::ToPoint::to_point(&range.start, buffer).row
20135 ..text::ToPoint::to_point(&range.end, buffer).row;
20136 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20137 });
20138
20139 let Some((buffer, selection)) = buffer_and_selection else {
20140 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20141 };
20142
20143 let Some(project) = self.project() else {
20144 return Task::ready(Err(anyhow!("editor does not have project")));
20145 };
20146
20147 project.update(cx, |project, cx| {
20148 project.get_permalink_to_line(&buffer, selection, cx)
20149 })
20150 }
20151
20152 pub fn copy_permalink_to_line(
20153 &mut self,
20154 _: &CopyPermalinkToLine,
20155 window: &mut Window,
20156 cx: &mut Context<Self>,
20157 ) {
20158 let permalink_task = self.get_permalink_to_line(cx);
20159 let workspace = self.workspace();
20160
20161 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20162 Ok(permalink) => {
20163 cx.update(|_, cx| {
20164 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20165 })
20166 .ok();
20167 }
20168 Err(err) => {
20169 let message = format!("Failed to copy permalink: {err}");
20170
20171 anyhow::Result::<()>::Err(err).log_err();
20172
20173 if let Some(workspace) = workspace {
20174 workspace
20175 .update_in(cx, |workspace, _, cx| {
20176 struct CopyPermalinkToLine;
20177
20178 workspace.show_toast(
20179 Toast::new(
20180 NotificationId::unique::<CopyPermalinkToLine>(),
20181 message,
20182 ),
20183 cx,
20184 )
20185 })
20186 .ok();
20187 }
20188 }
20189 })
20190 .detach();
20191 }
20192
20193 pub fn copy_file_location(
20194 &mut self,
20195 _: &CopyFileLocation,
20196 _: &mut Window,
20197 cx: &mut Context<Self>,
20198 ) {
20199 let selection = self
20200 .selections
20201 .newest::<Point>(&self.display_snapshot(cx))
20202 .start
20203 .row
20204 + 1;
20205 if let Some(file) = self.target_file(cx) {
20206 let path = file.path().display(file.path_style(cx));
20207 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20208 }
20209 }
20210
20211 pub fn open_permalink_to_line(
20212 &mut self,
20213 _: &OpenPermalinkToLine,
20214 window: &mut Window,
20215 cx: &mut Context<Self>,
20216 ) {
20217 let permalink_task = self.get_permalink_to_line(cx);
20218 let workspace = self.workspace();
20219
20220 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20221 Ok(permalink) => {
20222 cx.update(|_, cx| {
20223 cx.open_url(permalink.as_ref());
20224 })
20225 .ok();
20226 }
20227 Err(err) => {
20228 let message = format!("Failed to open permalink: {err}");
20229
20230 anyhow::Result::<()>::Err(err).log_err();
20231
20232 if let Some(workspace) = workspace {
20233 workspace
20234 .update(cx, |workspace, cx| {
20235 struct OpenPermalinkToLine;
20236
20237 workspace.show_toast(
20238 Toast::new(
20239 NotificationId::unique::<OpenPermalinkToLine>(),
20240 message,
20241 ),
20242 cx,
20243 )
20244 })
20245 .ok();
20246 }
20247 }
20248 })
20249 .detach();
20250 }
20251
20252 pub fn insert_uuid_v4(
20253 &mut self,
20254 _: &InsertUuidV4,
20255 window: &mut Window,
20256 cx: &mut Context<Self>,
20257 ) {
20258 self.insert_uuid(UuidVersion::V4, window, cx);
20259 }
20260
20261 pub fn insert_uuid_v7(
20262 &mut self,
20263 _: &InsertUuidV7,
20264 window: &mut Window,
20265 cx: &mut Context<Self>,
20266 ) {
20267 self.insert_uuid(UuidVersion::V7, window, cx);
20268 }
20269
20270 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20271 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20272 self.transact(window, cx, |this, window, cx| {
20273 let edits = this
20274 .selections
20275 .all::<Point>(&this.display_snapshot(cx))
20276 .into_iter()
20277 .map(|selection| {
20278 let uuid = match version {
20279 UuidVersion::V4 => uuid::Uuid::new_v4(),
20280 UuidVersion::V7 => uuid::Uuid::now_v7(),
20281 };
20282
20283 (selection.range(), uuid.to_string())
20284 });
20285 this.edit(edits, cx);
20286 this.refresh_edit_prediction(true, false, window, cx);
20287 });
20288 }
20289
20290 pub fn open_selections_in_multibuffer(
20291 &mut self,
20292 _: &OpenSelectionsInMultibuffer,
20293 window: &mut Window,
20294 cx: &mut Context<Self>,
20295 ) {
20296 let multibuffer = self.buffer.read(cx);
20297
20298 let Some(buffer) = multibuffer.as_singleton() else {
20299 return;
20300 };
20301
20302 let Some(workspace) = self.workspace() else {
20303 return;
20304 };
20305
20306 let title = multibuffer.title(cx).to_string();
20307
20308 let locations = self
20309 .selections
20310 .all_anchors(cx)
20311 .iter()
20312 .map(|selection| {
20313 (
20314 buffer.clone(),
20315 (selection.start.text_anchor..selection.end.text_anchor)
20316 .to_point(buffer.read(cx)),
20317 )
20318 })
20319 .into_group_map();
20320
20321 cx.spawn_in(window, async move |_, cx| {
20322 workspace.update_in(cx, |workspace, window, cx| {
20323 Self::open_locations_in_multibuffer(
20324 workspace,
20325 locations,
20326 format!("Selections for '{title}'"),
20327 false,
20328 MultibufferSelectionMode::All,
20329 window,
20330 cx,
20331 );
20332 })
20333 })
20334 .detach();
20335 }
20336
20337 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20338 /// last highlight added will be used.
20339 ///
20340 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20341 pub fn highlight_rows<T: 'static>(
20342 &mut self,
20343 range: Range<Anchor>,
20344 color: Hsla,
20345 options: RowHighlightOptions,
20346 cx: &mut Context<Self>,
20347 ) {
20348 let snapshot = self.buffer().read(cx).snapshot(cx);
20349 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20350 let ix = row_highlights.binary_search_by(|highlight| {
20351 Ordering::Equal
20352 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20353 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20354 });
20355
20356 if let Err(mut ix) = ix {
20357 let index = post_inc(&mut self.highlight_order);
20358
20359 // If this range intersects with the preceding highlight, then merge it with
20360 // the preceding highlight. Otherwise insert a new highlight.
20361 let mut merged = false;
20362 if ix > 0 {
20363 let prev_highlight = &mut row_highlights[ix - 1];
20364 if prev_highlight
20365 .range
20366 .end
20367 .cmp(&range.start, &snapshot)
20368 .is_ge()
20369 {
20370 ix -= 1;
20371 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20372 prev_highlight.range.end = range.end;
20373 }
20374 merged = true;
20375 prev_highlight.index = index;
20376 prev_highlight.color = color;
20377 prev_highlight.options = options;
20378 }
20379 }
20380
20381 if !merged {
20382 row_highlights.insert(
20383 ix,
20384 RowHighlight {
20385 range,
20386 index,
20387 color,
20388 options,
20389 type_id: TypeId::of::<T>(),
20390 },
20391 );
20392 }
20393
20394 // If any of the following highlights intersect with this one, merge them.
20395 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20396 let highlight = &row_highlights[ix];
20397 if next_highlight
20398 .range
20399 .start
20400 .cmp(&highlight.range.end, &snapshot)
20401 .is_le()
20402 {
20403 if next_highlight
20404 .range
20405 .end
20406 .cmp(&highlight.range.end, &snapshot)
20407 .is_gt()
20408 {
20409 row_highlights[ix].range.end = next_highlight.range.end;
20410 }
20411 row_highlights.remove(ix + 1);
20412 } else {
20413 break;
20414 }
20415 }
20416 }
20417 }
20418
20419 /// Remove any highlighted row ranges of the given type that intersect the
20420 /// given ranges.
20421 pub fn remove_highlighted_rows<T: 'static>(
20422 &mut self,
20423 ranges_to_remove: Vec<Range<Anchor>>,
20424 cx: &mut Context<Self>,
20425 ) {
20426 let snapshot = self.buffer().read(cx).snapshot(cx);
20427 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20428 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20429 row_highlights.retain(|highlight| {
20430 while let Some(range_to_remove) = ranges_to_remove.peek() {
20431 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20432 Ordering::Less | Ordering::Equal => {
20433 ranges_to_remove.next();
20434 }
20435 Ordering::Greater => {
20436 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20437 Ordering::Less | Ordering::Equal => {
20438 return false;
20439 }
20440 Ordering::Greater => break,
20441 }
20442 }
20443 }
20444 }
20445
20446 true
20447 })
20448 }
20449
20450 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20451 pub fn clear_row_highlights<T: 'static>(&mut self) {
20452 self.highlighted_rows.remove(&TypeId::of::<T>());
20453 }
20454
20455 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20456 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20457 self.highlighted_rows
20458 .get(&TypeId::of::<T>())
20459 .map_or(&[] as &[_], |vec| vec.as_slice())
20460 .iter()
20461 .map(|highlight| (highlight.range.clone(), highlight.color))
20462 }
20463
20464 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20465 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20466 /// Allows to ignore certain kinds of highlights.
20467 pub fn highlighted_display_rows(
20468 &self,
20469 window: &mut Window,
20470 cx: &mut App,
20471 ) -> BTreeMap<DisplayRow, LineHighlight> {
20472 let snapshot = self.snapshot(window, cx);
20473 let mut used_highlight_orders = HashMap::default();
20474 self.highlighted_rows
20475 .iter()
20476 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20477 .fold(
20478 BTreeMap::<DisplayRow, LineHighlight>::new(),
20479 |mut unique_rows, highlight| {
20480 let start = highlight.range.start.to_display_point(&snapshot);
20481 let end = highlight.range.end.to_display_point(&snapshot);
20482 let start_row = start.row().0;
20483 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20484 && end.column() == 0
20485 {
20486 end.row().0.saturating_sub(1)
20487 } else {
20488 end.row().0
20489 };
20490 for row in start_row..=end_row {
20491 let used_index =
20492 used_highlight_orders.entry(row).or_insert(highlight.index);
20493 if highlight.index >= *used_index {
20494 *used_index = highlight.index;
20495 unique_rows.insert(
20496 DisplayRow(row),
20497 LineHighlight {
20498 include_gutter: highlight.options.include_gutter,
20499 border: None,
20500 background: highlight.color.into(),
20501 type_id: Some(highlight.type_id),
20502 },
20503 );
20504 }
20505 }
20506 unique_rows
20507 },
20508 )
20509 }
20510
20511 pub fn highlighted_display_row_for_autoscroll(
20512 &self,
20513 snapshot: &DisplaySnapshot,
20514 ) -> Option<DisplayRow> {
20515 self.highlighted_rows
20516 .values()
20517 .flat_map(|highlighted_rows| highlighted_rows.iter())
20518 .filter_map(|highlight| {
20519 if highlight.options.autoscroll {
20520 Some(highlight.range.start.to_display_point(snapshot).row())
20521 } else {
20522 None
20523 }
20524 })
20525 .min()
20526 }
20527
20528 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20529 self.highlight_background::<SearchWithinRange>(
20530 ranges,
20531 |colors| colors.colors().editor_document_highlight_read_background,
20532 cx,
20533 )
20534 }
20535
20536 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20537 self.breadcrumb_header = Some(new_header);
20538 }
20539
20540 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20541 self.clear_background_highlights::<SearchWithinRange>(cx);
20542 }
20543
20544 pub fn highlight_background<T: 'static>(
20545 &mut self,
20546 ranges: &[Range<Anchor>],
20547 color_fetcher: fn(&Theme) -> Hsla,
20548 cx: &mut Context<Self>,
20549 ) {
20550 self.background_highlights.insert(
20551 HighlightKey::Type(TypeId::of::<T>()),
20552 (color_fetcher, Arc::from(ranges)),
20553 );
20554 self.scrollbar_marker_state.dirty = true;
20555 cx.notify();
20556 }
20557
20558 pub fn highlight_background_key<T: 'static>(
20559 &mut self,
20560 key: usize,
20561 ranges: &[Range<Anchor>],
20562 color_fetcher: fn(&Theme) -> Hsla,
20563 cx: &mut Context<Self>,
20564 ) {
20565 self.background_highlights.insert(
20566 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20567 (color_fetcher, Arc::from(ranges)),
20568 );
20569 self.scrollbar_marker_state.dirty = true;
20570 cx.notify();
20571 }
20572
20573 pub fn clear_background_highlights<T: 'static>(
20574 &mut self,
20575 cx: &mut Context<Self>,
20576 ) -> Option<BackgroundHighlight> {
20577 let text_highlights = self
20578 .background_highlights
20579 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20580 if !text_highlights.1.is_empty() {
20581 self.scrollbar_marker_state.dirty = true;
20582 cx.notify();
20583 }
20584 Some(text_highlights)
20585 }
20586
20587 pub fn highlight_gutter<T: 'static>(
20588 &mut self,
20589 ranges: impl Into<Vec<Range<Anchor>>>,
20590 color_fetcher: fn(&App) -> Hsla,
20591 cx: &mut Context<Self>,
20592 ) {
20593 self.gutter_highlights
20594 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20595 cx.notify();
20596 }
20597
20598 pub fn clear_gutter_highlights<T: 'static>(
20599 &mut self,
20600 cx: &mut Context<Self>,
20601 ) -> Option<GutterHighlight> {
20602 cx.notify();
20603 self.gutter_highlights.remove(&TypeId::of::<T>())
20604 }
20605
20606 pub fn insert_gutter_highlight<T: 'static>(
20607 &mut self,
20608 range: Range<Anchor>,
20609 color_fetcher: fn(&App) -> Hsla,
20610 cx: &mut Context<Self>,
20611 ) {
20612 let snapshot = self.buffer().read(cx).snapshot(cx);
20613 let mut highlights = self
20614 .gutter_highlights
20615 .remove(&TypeId::of::<T>())
20616 .map(|(_, highlights)| highlights)
20617 .unwrap_or_default();
20618 let ix = highlights.binary_search_by(|highlight| {
20619 Ordering::Equal
20620 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20621 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20622 });
20623 if let Err(ix) = ix {
20624 highlights.insert(ix, range);
20625 }
20626 self.gutter_highlights
20627 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20628 }
20629
20630 pub fn remove_gutter_highlights<T: 'static>(
20631 &mut self,
20632 ranges_to_remove: Vec<Range<Anchor>>,
20633 cx: &mut Context<Self>,
20634 ) {
20635 let snapshot = self.buffer().read(cx).snapshot(cx);
20636 let Some((color_fetcher, mut gutter_highlights)) =
20637 self.gutter_highlights.remove(&TypeId::of::<T>())
20638 else {
20639 return;
20640 };
20641 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20642 gutter_highlights.retain(|highlight| {
20643 while let Some(range_to_remove) = ranges_to_remove.peek() {
20644 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20645 Ordering::Less | Ordering::Equal => {
20646 ranges_to_remove.next();
20647 }
20648 Ordering::Greater => {
20649 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20650 Ordering::Less | Ordering::Equal => {
20651 return false;
20652 }
20653 Ordering::Greater => break,
20654 }
20655 }
20656 }
20657 }
20658
20659 true
20660 });
20661 self.gutter_highlights
20662 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20663 }
20664
20665 #[cfg(feature = "test-support")]
20666 pub fn all_text_highlights(
20667 &self,
20668 window: &mut Window,
20669 cx: &mut Context<Self>,
20670 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20671 let snapshot = self.snapshot(window, cx);
20672 self.display_map.update(cx, |display_map, _| {
20673 display_map
20674 .all_text_highlights()
20675 .map(|highlight| {
20676 let (style, ranges) = highlight.as_ref();
20677 (
20678 *style,
20679 ranges
20680 .iter()
20681 .map(|range| range.clone().to_display_points(&snapshot))
20682 .collect(),
20683 )
20684 })
20685 .collect()
20686 })
20687 }
20688
20689 #[cfg(feature = "test-support")]
20690 pub fn all_text_background_highlights(
20691 &self,
20692 window: &mut Window,
20693 cx: &mut Context<Self>,
20694 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20695 let snapshot = self.snapshot(window, cx);
20696 let buffer = &snapshot.buffer_snapshot();
20697 let start = buffer.anchor_before(0);
20698 let end = buffer.anchor_after(buffer.len());
20699 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20700 }
20701
20702 #[cfg(any(test, feature = "test-support"))]
20703 pub fn sorted_background_highlights_in_range(
20704 &self,
20705 search_range: Range<Anchor>,
20706 display_snapshot: &DisplaySnapshot,
20707 theme: &Theme,
20708 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20709 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20710 res.sort_by(|a, b| {
20711 a.0.start
20712 .cmp(&b.0.start)
20713 .then_with(|| a.0.end.cmp(&b.0.end))
20714 .then_with(|| a.1.cmp(&b.1))
20715 });
20716 res
20717 }
20718
20719 #[cfg(feature = "test-support")]
20720 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20721 let snapshot = self.buffer().read(cx).snapshot(cx);
20722
20723 let highlights = self
20724 .background_highlights
20725 .get(&HighlightKey::Type(TypeId::of::<
20726 items::BufferSearchHighlights,
20727 >()));
20728
20729 if let Some((_color, ranges)) = highlights {
20730 ranges
20731 .iter()
20732 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20733 .collect_vec()
20734 } else {
20735 vec![]
20736 }
20737 }
20738
20739 fn document_highlights_for_position<'a>(
20740 &'a self,
20741 position: Anchor,
20742 buffer: &'a MultiBufferSnapshot,
20743 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20744 let read_highlights = self
20745 .background_highlights
20746 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20747 .map(|h| &h.1);
20748 let write_highlights = self
20749 .background_highlights
20750 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20751 .map(|h| &h.1);
20752 let left_position = position.bias_left(buffer);
20753 let right_position = position.bias_right(buffer);
20754 read_highlights
20755 .into_iter()
20756 .chain(write_highlights)
20757 .flat_map(move |ranges| {
20758 let start_ix = match ranges.binary_search_by(|probe| {
20759 let cmp = probe.end.cmp(&left_position, buffer);
20760 if cmp.is_ge() {
20761 Ordering::Greater
20762 } else {
20763 Ordering::Less
20764 }
20765 }) {
20766 Ok(i) | Err(i) => i,
20767 };
20768
20769 ranges[start_ix..]
20770 .iter()
20771 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20772 })
20773 }
20774
20775 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20776 self.background_highlights
20777 .get(&HighlightKey::Type(TypeId::of::<T>()))
20778 .is_some_and(|(_, highlights)| !highlights.is_empty())
20779 }
20780
20781 /// Returns all background highlights for a given range.
20782 ///
20783 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20784 pub fn background_highlights_in_range(
20785 &self,
20786 search_range: Range<Anchor>,
20787 display_snapshot: &DisplaySnapshot,
20788 theme: &Theme,
20789 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20790 let mut results = Vec::new();
20791 for (color_fetcher, ranges) in self.background_highlights.values() {
20792 let color = color_fetcher(theme);
20793 let start_ix = match ranges.binary_search_by(|probe| {
20794 let cmp = probe
20795 .end
20796 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20797 if cmp.is_gt() {
20798 Ordering::Greater
20799 } else {
20800 Ordering::Less
20801 }
20802 }) {
20803 Ok(i) | Err(i) => i,
20804 };
20805 for range in &ranges[start_ix..] {
20806 if range
20807 .start
20808 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20809 .is_ge()
20810 {
20811 break;
20812 }
20813
20814 let start = range.start.to_display_point(display_snapshot);
20815 let end = range.end.to_display_point(display_snapshot);
20816 results.push((start..end, color))
20817 }
20818 }
20819 results
20820 }
20821
20822 pub fn gutter_highlights_in_range(
20823 &self,
20824 search_range: Range<Anchor>,
20825 display_snapshot: &DisplaySnapshot,
20826 cx: &App,
20827 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20828 let mut results = Vec::new();
20829 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20830 let color = color_fetcher(cx);
20831 let start_ix = match ranges.binary_search_by(|probe| {
20832 let cmp = probe
20833 .end
20834 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20835 if cmp.is_gt() {
20836 Ordering::Greater
20837 } else {
20838 Ordering::Less
20839 }
20840 }) {
20841 Ok(i) | Err(i) => i,
20842 };
20843 for range in &ranges[start_ix..] {
20844 if range
20845 .start
20846 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20847 .is_ge()
20848 {
20849 break;
20850 }
20851
20852 let start = range.start.to_display_point(display_snapshot);
20853 let end = range.end.to_display_point(display_snapshot);
20854 results.push((start..end, color))
20855 }
20856 }
20857 results
20858 }
20859
20860 /// Get the text ranges corresponding to the redaction query
20861 pub fn redacted_ranges(
20862 &self,
20863 search_range: Range<Anchor>,
20864 display_snapshot: &DisplaySnapshot,
20865 cx: &App,
20866 ) -> Vec<Range<DisplayPoint>> {
20867 display_snapshot
20868 .buffer_snapshot()
20869 .redacted_ranges(search_range, |file| {
20870 if let Some(file) = file {
20871 file.is_private()
20872 && EditorSettings::get(
20873 Some(SettingsLocation {
20874 worktree_id: file.worktree_id(cx),
20875 path: file.path().as_ref(),
20876 }),
20877 cx,
20878 )
20879 .redact_private_values
20880 } else {
20881 false
20882 }
20883 })
20884 .map(|range| {
20885 range.start.to_display_point(display_snapshot)
20886 ..range.end.to_display_point(display_snapshot)
20887 })
20888 .collect()
20889 }
20890
20891 pub fn highlight_text_key<T: 'static>(
20892 &mut self,
20893 key: usize,
20894 ranges: Vec<Range<Anchor>>,
20895 style: HighlightStyle,
20896 cx: &mut Context<Self>,
20897 ) {
20898 self.display_map.update(cx, |map, _| {
20899 map.highlight_text(
20900 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20901 ranges,
20902 style,
20903 );
20904 });
20905 cx.notify();
20906 }
20907
20908 pub fn highlight_text<T: 'static>(
20909 &mut self,
20910 ranges: Vec<Range<Anchor>>,
20911 style: HighlightStyle,
20912 cx: &mut Context<Self>,
20913 ) {
20914 self.display_map.update(cx, |map, _| {
20915 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20916 });
20917 cx.notify();
20918 }
20919
20920 pub fn text_highlights<'a, T: 'static>(
20921 &'a self,
20922 cx: &'a App,
20923 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20924 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20925 }
20926
20927 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20928 let cleared = self
20929 .display_map
20930 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20931 if cleared {
20932 cx.notify();
20933 }
20934 }
20935
20936 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20937 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20938 && self.focus_handle.is_focused(window)
20939 }
20940
20941 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20942 self.show_cursor_when_unfocused = is_enabled;
20943 cx.notify();
20944 }
20945
20946 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20947 cx.notify();
20948 }
20949
20950 fn on_debug_session_event(
20951 &mut self,
20952 _session: Entity<Session>,
20953 event: &SessionEvent,
20954 cx: &mut Context<Self>,
20955 ) {
20956 if let SessionEvent::InvalidateInlineValue = event {
20957 self.refresh_inline_values(cx);
20958 }
20959 }
20960
20961 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20962 let Some(project) = self.project.clone() else {
20963 return;
20964 };
20965
20966 if !self.inline_value_cache.enabled {
20967 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20968 self.splice_inlays(&inlays, Vec::new(), cx);
20969 return;
20970 }
20971
20972 let current_execution_position = self
20973 .highlighted_rows
20974 .get(&TypeId::of::<ActiveDebugLine>())
20975 .and_then(|lines| lines.last().map(|line| line.range.end));
20976
20977 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20978 let inline_values = editor
20979 .update(cx, |editor, cx| {
20980 let Some(current_execution_position) = current_execution_position else {
20981 return Some(Task::ready(Ok(Vec::new())));
20982 };
20983
20984 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20985 let snapshot = buffer.snapshot(cx);
20986
20987 let excerpt = snapshot.excerpt_containing(
20988 current_execution_position..current_execution_position,
20989 )?;
20990
20991 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20992 })?;
20993
20994 let range =
20995 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20996
20997 project.inline_values(buffer, range, cx)
20998 })
20999 .ok()
21000 .flatten()?
21001 .await
21002 .context("refreshing debugger inlays")
21003 .log_err()?;
21004
21005 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21006
21007 for (buffer_id, inline_value) in inline_values
21008 .into_iter()
21009 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21010 {
21011 buffer_inline_values
21012 .entry(buffer_id)
21013 .or_default()
21014 .push(inline_value);
21015 }
21016
21017 editor
21018 .update(cx, |editor, cx| {
21019 let snapshot = editor.buffer.read(cx).snapshot(cx);
21020 let mut new_inlays = Vec::default();
21021
21022 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21023 let buffer_id = buffer_snapshot.remote_id();
21024 buffer_inline_values
21025 .get(&buffer_id)
21026 .into_iter()
21027 .flatten()
21028 .for_each(|hint| {
21029 let inlay = Inlay::debugger(
21030 post_inc(&mut editor.next_inlay_id),
21031 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
21032 hint.text(),
21033 );
21034 if !inlay.text().chars().contains(&'\n') {
21035 new_inlays.push(inlay);
21036 }
21037 });
21038 }
21039
21040 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21041 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21042
21043 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21044 })
21045 .ok()?;
21046 Some(())
21047 });
21048 }
21049
21050 fn on_buffer_event(
21051 &mut self,
21052 multibuffer: &Entity<MultiBuffer>,
21053 event: &multi_buffer::Event,
21054 window: &mut Window,
21055 cx: &mut Context<Self>,
21056 ) {
21057 match event {
21058 multi_buffer::Event::Edited { edited_buffer } => {
21059 self.scrollbar_marker_state.dirty = true;
21060 self.active_indent_guides_state.dirty = true;
21061 self.refresh_active_diagnostics(cx);
21062 self.refresh_code_actions(window, cx);
21063 self.refresh_selected_text_highlights(true, window, cx);
21064 self.refresh_single_line_folds(window, cx);
21065 self.refresh_matching_bracket_highlights(window, cx);
21066 if self.has_active_edit_prediction() {
21067 self.update_visible_edit_prediction(window, cx);
21068 }
21069
21070 if let Some(buffer) = edited_buffer {
21071 if buffer.read(cx).file().is_none() {
21072 cx.emit(EditorEvent::TitleChanged);
21073 }
21074
21075 if self.project.is_some() {
21076 let buffer_id = buffer.read(cx).remote_id();
21077 self.register_buffer(buffer_id, cx);
21078 self.update_lsp_data(Some(buffer_id), window, cx);
21079 self.refresh_inlay_hints(
21080 InlayHintRefreshReason::BufferEdited(buffer_id),
21081 cx,
21082 );
21083 }
21084 }
21085
21086 cx.emit(EditorEvent::BufferEdited);
21087 cx.emit(SearchEvent::MatchesInvalidated);
21088
21089 let Some(project) = &self.project else { return };
21090 let (telemetry, is_via_ssh) = {
21091 let project = project.read(cx);
21092 let telemetry = project.client().telemetry().clone();
21093 let is_via_ssh = project.is_via_remote_server();
21094 (telemetry, is_via_ssh)
21095 };
21096 telemetry.log_edit_event("editor", is_via_ssh);
21097 }
21098 multi_buffer::Event::ExcerptsAdded {
21099 buffer,
21100 predecessor,
21101 excerpts,
21102 } => {
21103 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21104 let buffer_id = buffer.read(cx).remote_id();
21105 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21106 && let Some(project) = &self.project
21107 {
21108 update_uncommitted_diff_for_buffer(
21109 cx.entity(),
21110 project,
21111 [buffer.clone()],
21112 self.buffer.clone(),
21113 cx,
21114 )
21115 .detach();
21116 }
21117 self.update_lsp_data(Some(buffer_id), window, cx);
21118 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21119 cx.emit(EditorEvent::ExcerptsAdded {
21120 buffer: buffer.clone(),
21121 predecessor: *predecessor,
21122 excerpts: excerpts.clone(),
21123 });
21124 }
21125 multi_buffer::Event::ExcerptsRemoved {
21126 ids,
21127 removed_buffer_ids,
21128 } => {
21129 if let Some(inlay_hints) = &mut self.inlay_hints {
21130 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21131 }
21132 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21133 for buffer_id in removed_buffer_ids {
21134 self.registered_buffers.remove(buffer_id);
21135 }
21136 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21137 cx.emit(EditorEvent::ExcerptsRemoved {
21138 ids: ids.clone(),
21139 removed_buffer_ids: removed_buffer_ids.clone(),
21140 });
21141 }
21142 multi_buffer::Event::ExcerptsEdited {
21143 excerpt_ids,
21144 buffer_ids,
21145 } => {
21146 self.display_map.update(cx, |map, cx| {
21147 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21148 });
21149 cx.emit(EditorEvent::ExcerptsEdited {
21150 ids: excerpt_ids.clone(),
21151 });
21152 }
21153 multi_buffer::Event::ExcerptsExpanded { ids } => {
21154 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21155 self.refresh_document_highlights(cx);
21156 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21157 }
21158 multi_buffer::Event::Reparsed(buffer_id) => {
21159 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21160 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21161
21162 cx.emit(EditorEvent::Reparsed(*buffer_id));
21163 }
21164 multi_buffer::Event::DiffHunksToggled => {
21165 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21166 }
21167 multi_buffer::Event::LanguageChanged(buffer_id) => {
21168 self.registered_buffers.remove(&buffer_id);
21169 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21170 cx.emit(EditorEvent::Reparsed(*buffer_id));
21171 cx.notify();
21172 }
21173 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21174 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21175 multi_buffer::Event::FileHandleChanged
21176 | multi_buffer::Event::Reloaded
21177 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21178 multi_buffer::Event::DiagnosticsUpdated => {
21179 self.update_diagnostics_state(window, cx);
21180 }
21181 _ => {}
21182 };
21183 }
21184
21185 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21186 if !self.diagnostics_enabled() {
21187 return;
21188 }
21189 self.refresh_active_diagnostics(cx);
21190 self.refresh_inline_diagnostics(true, window, cx);
21191 self.scrollbar_marker_state.dirty = true;
21192 cx.notify();
21193 }
21194
21195 pub fn start_temporary_diff_override(&mut self) {
21196 self.load_diff_task.take();
21197 self.temporary_diff_override = true;
21198 }
21199
21200 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21201 self.temporary_diff_override = false;
21202 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21203 self.buffer.update(cx, |buffer, cx| {
21204 buffer.set_all_diff_hunks_collapsed(cx);
21205 });
21206
21207 if let Some(project) = self.project.clone() {
21208 self.load_diff_task = Some(
21209 update_uncommitted_diff_for_buffer(
21210 cx.entity(),
21211 &project,
21212 self.buffer.read(cx).all_buffers(),
21213 self.buffer.clone(),
21214 cx,
21215 )
21216 .shared(),
21217 );
21218 }
21219 }
21220
21221 fn on_display_map_changed(
21222 &mut self,
21223 _: Entity<DisplayMap>,
21224 _: &mut Window,
21225 cx: &mut Context<Self>,
21226 ) {
21227 cx.notify();
21228 }
21229
21230 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21231 if self.diagnostics_enabled() {
21232 let new_severity = EditorSettings::get_global(cx)
21233 .diagnostics_max_severity
21234 .unwrap_or(DiagnosticSeverity::Hint);
21235 self.set_max_diagnostics_severity(new_severity, cx);
21236 }
21237 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21238 self.update_edit_prediction_settings(cx);
21239 self.refresh_edit_prediction(true, false, window, cx);
21240 self.refresh_inline_values(cx);
21241 self.refresh_inlay_hints(
21242 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21243 self.selections.newest_anchor().head(),
21244 &self.buffer.read(cx).snapshot(cx),
21245 cx,
21246 )),
21247 cx,
21248 );
21249
21250 let old_cursor_shape = self.cursor_shape;
21251 let old_show_breadcrumbs = self.show_breadcrumbs;
21252
21253 {
21254 let editor_settings = EditorSettings::get_global(cx);
21255 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21256 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21257 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21258 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21259 }
21260
21261 if old_cursor_shape != self.cursor_shape {
21262 cx.emit(EditorEvent::CursorShapeChanged);
21263 }
21264
21265 if old_show_breadcrumbs != self.show_breadcrumbs {
21266 cx.emit(EditorEvent::BreadcrumbsChanged);
21267 }
21268
21269 let project_settings = ProjectSettings::get_global(cx);
21270 self.buffer_serialization = self
21271 .should_serialize_buffer()
21272 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21273
21274 if self.mode.is_full() {
21275 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21276 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21277 if self.show_inline_diagnostics != show_inline_diagnostics {
21278 self.show_inline_diagnostics = show_inline_diagnostics;
21279 self.refresh_inline_diagnostics(false, window, cx);
21280 }
21281
21282 if self.git_blame_inline_enabled != inline_blame_enabled {
21283 self.toggle_git_blame_inline_internal(false, window, cx);
21284 }
21285
21286 let minimap_settings = EditorSettings::get_global(cx).minimap;
21287 if self.minimap_visibility != MinimapVisibility::Disabled {
21288 if self.minimap_visibility.settings_visibility()
21289 != minimap_settings.minimap_enabled()
21290 {
21291 self.set_minimap_visibility(
21292 MinimapVisibility::for_mode(self.mode(), cx),
21293 window,
21294 cx,
21295 );
21296 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21297 minimap_entity.update(cx, |minimap_editor, cx| {
21298 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21299 })
21300 }
21301 }
21302 }
21303
21304 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21305 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21306 }) {
21307 if !inlay_splice.is_empty() {
21308 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21309 }
21310 self.refresh_colors_for_visible_range(None, window, cx);
21311 }
21312
21313 cx.notify();
21314 }
21315
21316 pub fn set_searchable(&mut self, searchable: bool) {
21317 self.searchable = searchable;
21318 }
21319
21320 pub fn searchable(&self) -> bool {
21321 self.searchable
21322 }
21323
21324 pub fn open_excerpts_in_split(
21325 &mut self,
21326 _: &OpenExcerptsSplit,
21327 window: &mut Window,
21328 cx: &mut Context<Self>,
21329 ) {
21330 self.open_excerpts_common(None, true, window, cx)
21331 }
21332
21333 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21334 self.open_excerpts_common(None, false, window, cx)
21335 }
21336
21337 fn open_excerpts_common(
21338 &mut self,
21339 jump_data: Option<JumpData>,
21340 split: bool,
21341 window: &mut Window,
21342 cx: &mut Context<Self>,
21343 ) {
21344 let Some(workspace) = self.workspace() else {
21345 cx.propagate();
21346 return;
21347 };
21348
21349 if self.buffer.read(cx).is_singleton() {
21350 cx.propagate();
21351 return;
21352 }
21353
21354 let mut new_selections_by_buffer = HashMap::default();
21355 match &jump_data {
21356 Some(JumpData::MultiBufferPoint {
21357 excerpt_id,
21358 position,
21359 anchor,
21360 line_offset_from_top,
21361 }) => {
21362 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21363 if let Some(buffer) = multi_buffer_snapshot
21364 .buffer_id_for_excerpt(*excerpt_id)
21365 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21366 {
21367 let buffer_snapshot = buffer.read(cx).snapshot();
21368 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21369 language::ToPoint::to_point(anchor, &buffer_snapshot)
21370 } else {
21371 buffer_snapshot.clip_point(*position, Bias::Left)
21372 };
21373 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21374 new_selections_by_buffer.insert(
21375 buffer,
21376 (
21377 vec![jump_to_offset..jump_to_offset],
21378 Some(*line_offset_from_top),
21379 ),
21380 );
21381 }
21382 }
21383 Some(JumpData::MultiBufferRow {
21384 row,
21385 line_offset_from_top,
21386 }) => {
21387 let point = MultiBufferPoint::new(row.0, 0);
21388 if let Some((buffer, buffer_point, _)) =
21389 self.buffer.read(cx).point_to_buffer_point(point, cx)
21390 {
21391 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21392 new_selections_by_buffer
21393 .entry(buffer)
21394 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21395 .0
21396 .push(buffer_offset..buffer_offset)
21397 }
21398 }
21399 None => {
21400 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21401 let multi_buffer = self.buffer.read(cx);
21402 for selection in selections {
21403 for (snapshot, range, _, anchor) in multi_buffer
21404 .snapshot(cx)
21405 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21406 {
21407 if let Some(anchor) = anchor {
21408 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21409 else {
21410 continue;
21411 };
21412 let offset = text::ToOffset::to_offset(
21413 &anchor.text_anchor,
21414 &buffer_handle.read(cx).snapshot(),
21415 );
21416 let range = offset..offset;
21417 new_selections_by_buffer
21418 .entry(buffer_handle)
21419 .or_insert((Vec::new(), None))
21420 .0
21421 .push(range)
21422 } else {
21423 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21424 else {
21425 continue;
21426 };
21427 new_selections_by_buffer
21428 .entry(buffer_handle)
21429 .or_insert((Vec::new(), None))
21430 .0
21431 .push(range)
21432 }
21433 }
21434 }
21435 }
21436 }
21437
21438 new_selections_by_buffer
21439 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21440
21441 if new_selections_by_buffer.is_empty() {
21442 return;
21443 }
21444
21445 // We defer the pane interaction because we ourselves are a workspace item
21446 // and activating a new item causes the pane to call a method on us reentrantly,
21447 // which panics if we're on the stack.
21448 window.defer(cx, move |window, cx| {
21449 workspace.update(cx, |workspace, cx| {
21450 let pane = if split {
21451 workspace.adjacent_pane(window, cx)
21452 } else {
21453 workspace.active_pane().clone()
21454 };
21455
21456 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21457 let editor = buffer
21458 .read(cx)
21459 .file()
21460 .is_none()
21461 .then(|| {
21462 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21463 // so `workspace.open_project_item` will never find them, always opening a new editor.
21464 // Instead, we try to activate the existing editor in the pane first.
21465 let (editor, pane_item_index) =
21466 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21467 let editor = item.downcast::<Editor>()?;
21468 let singleton_buffer =
21469 editor.read(cx).buffer().read(cx).as_singleton()?;
21470 if singleton_buffer == buffer {
21471 Some((editor, i))
21472 } else {
21473 None
21474 }
21475 })?;
21476 pane.update(cx, |pane, cx| {
21477 pane.activate_item(pane_item_index, true, true, window, cx)
21478 });
21479 Some(editor)
21480 })
21481 .flatten()
21482 .unwrap_or_else(|| {
21483 workspace.open_project_item::<Self>(
21484 pane.clone(),
21485 buffer,
21486 true,
21487 true,
21488 window,
21489 cx,
21490 )
21491 });
21492
21493 editor.update(cx, |editor, cx| {
21494 let autoscroll = match scroll_offset {
21495 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21496 None => Autoscroll::newest(),
21497 };
21498 let nav_history = editor.nav_history.take();
21499 editor.change_selections(
21500 SelectionEffects::scroll(autoscroll),
21501 window,
21502 cx,
21503 |s| {
21504 s.select_ranges(ranges);
21505 },
21506 );
21507 editor.nav_history = nav_history;
21508 });
21509 }
21510 })
21511 });
21512 }
21513
21514 // For now, don't allow opening excerpts in buffers that aren't backed by
21515 // regular project files.
21516 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21517 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21518 }
21519
21520 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21521 let snapshot = self.buffer.read(cx).read(cx);
21522 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21523 Some(
21524 ranges
21525 .iter()
21526 .map(move |range| {
21527 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21528 })
21529 .collect(),
21530 )
21531 }
21532
21533 fn selection_replacement_ranges(
21534 &self,
21535 range: Range<OffsetUtf16>,
21536 cx: &mut App,
21537 ) -> Vec<Range<OffsetUtf16>> {
21538 let selections = self
21539 .selections
21540 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21541 let newest_selection = selections
21542 .iter()
21543 .max_by_key(|selection| selection.id)
21544 .unwrap();
21545 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21546 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21547 let snapshot = self.buffer.read(cx).read(cx);
21548 selections
21549 .into_iter()
21550 .map(|mut selection| {
21551 selection.start.0 =
21552 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21553 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21554 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21555 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21556 })
21557 .collect()
21558 }
21559
21560 fn report_editor_event(
21561 &self,
21562 reported_event: ReportEditorEvent,
21563 file_extension: Option<String>,
21564 cx: &App,
21565 ) {
21566 if cfg!(any(test, feature = "test-support")) {
21567 return;
21568 }
21569
21570 let Some(project) = &self.project else { return };
21571
21572 // If None, we are in a file without an extension
21573 let file = self
21574 .buffer
21575 .read(cx)
21576 .as_singleton()
21577 .and_then(|b| b.read(cx).file());
21578 let file_extension = file_extension.or(file
21579 .as_ref()
21580 .and_then(|file| Path::new(file.file_name(cx)).extension())
21581 .and_then(|e| e.to_str())
21582 .map(|a| a.to_string()));
21583
21584 let vim_mode = vim_flavor(cx).is_some();
21585
21586 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21587 let copilot_enabled = edit_predictions_provider
21588 == language::language_settings::EditPredictionProvider::Copilot;
21589 let copilot_enabled_for_language = self
21590 .buffer
21591 .read(cx)
21592 .language_settings(cx)
21593 .show_edit_predictions;
21594
21595 let project = project.read(cx);
21596 let event_type = reported_event.event_type();
21597
21598 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21599 telemetry::event!(
21600 event_type,
21601 type = if auto_saved {"autosave"} else {"manual"},
21602 file_extension,
21603 vim_mode,
21604 copilot_enabled,
21605 copilot_enabled_for_language,
21606 edit_predictions_provider,
21607 is_via_ssh = project.is_via_remote_server(),
21608 );
21609 } else {
21610 telemetry::event!(
21611 event_type,
21612 file_extension,
21613 vim_mode,
21614 copilot_enabled,
21615 copilot_enabled_for_language,
21616 edit_predictions_provider,
21617 is_via_ssh = project.is_via_remote_server(),
21618 );
21619 };
21620 }
21621
21622 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21623 /// with each line being an array of {text, highlight} objects.
21624 fn copy_highlight_json(
21625 &mut self,
21626 _: &CopyHighlightJson,
21627 window: &mut Window,
21628 cx: &mut Context<Self>,
21629 ) {
21630 #[derive(Serialize)]
21631 struct Chunk<'a> {
21632 text: String,
21633 highlight: Option<&'a str>,
21634 }
21635
21636 let snapshot = self.buffer.read(cx).snapshot(cx);
21637 let range = self
21638 .selected_text_range(false, window, cx)
21639 .and_then(|selection| {
21640 if selection.range.is_empty() {
21641 None
21642 } else {
21643 Some(
21644 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21645 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21646 )
21647 }
21648 })
21649 .unwrap_or_else(|| 0..snapshot.len());
21650
21651 let chunks = snapshot.chunks(range, true);
21652 let mut lines = Vec::new();
21653 let mut line: VecDeque<Chunk> = VecDeque::new();
21654
21655 let Some(style) = self.style.as_ref() else {
21656 return;
21657 };
21658
21659 for chunk in chunks {
21660 let highlight = chunk
21661 .syntax_highlight_id
21662 .and_then(|id| id.name(&style.syntax));
21663 let mut chunk_lines = chunk.text.split('\n').peekable();
21664 while let Some(text) = chunk_lines.next() {
21665 let mut merged_with_last_token = false;
21666 if let Some(last_token) = line.back_mut()
21667 && last_token.highlight == highlight
21668 {
21669 last_token.text.push_str(text);
21670 merged_with_last_token = true;
21671 }
21672
21673 if !merged_with_last_token {
21674 line.push_back(Chunk {
21675 text: text.into(),
21676 highlight,
21677 });
21678 }
21679
21680 if chunk_lines.peek().is_some() {
21681 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21682 line.pop_front();
21683 }
21684 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21685 line.pop_back();
21686 }
21687
21688 lines.push(mem::take(&mut line));
21689 }
21690 }
21691 }
21692
21693 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21694 return;
21695 };
21696 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21697 }
21698
21699 pub fn open_context_menu(
21700 &mut self,
21701 _: &OpenContextMenu,
21702 window: &mut Window,
21703 cx: &mut Context<Self>,
21704 ) {
21705 self.request_autoscroll(Autoscroll::newest(), cx);
21706 let position = self
21707 .selections
21708 .newest_display(&self.display_snapshot(cx))
21709 .start;
21710 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21711 }
21712
21713 pub fn replay_insert_event(
21714 &mut self,
21715 text: &str,
21716 relative_utf16_range: Option<Range<isize>>,
21717 window: &mut Window,
21718 cx: &mut Context<Self>,
21719 ) {
21720 if !self.input_enabled {
21721 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21722 return;
21723 }
21724 if let Some(relative_utf16_range) = relative_utf16_range {
21725 let selections = self
21726 .selections
21727 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21728 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21729 let new_ranges = selections.into_iter().map(|range| {
21730 let start = OffsetUtf16(
21731 range
21732 .head()
21733 .0
21734 .saturating_add_signed(relative_utf16_range.start),
21735 );
21736 let end = OffsetUtf16(
21737 range
21738 .head()
21739 .0
21740 .saturating_add_signed(relative_utf16_range.end),
21741 );
21742 start..end
21743 });
21744 s.select_ranges(new_ranges);
21745 });
21746 }
21747
21748 self.handle_input(text, window, cx);
21749 }
21750
21751 pub fn is_focused(&self, window: &Window) -> bool {
21752 self.focus_handle.is_focused(window)
21753 }
21754
21755 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21756 cx.emit(EditorEvent::Focused);
21757
21758 if let Some(descendant) = self
21759 .last_focused_descendant
21760 .take()
21761 .and_then(|descendant| descendant.upgrade())
21762 {
21763 window.focus(&descendant);
21764 } else {
21765 if let Some(blame) = self.blame.as_ref() {
21766 blame.update(cx, GitBlame::focus)
21767 }
21768
21769 self.blink_manager.update(cx, BlinkManager::enable);
21770 self.show_cursor_names(window, cx);
21771 self.buffer.update(cx, |buffer, cx| {
21772 buffer.finalize_last_transaction(cx);
21773 if self.leader_id.is_none() {
21774 buffer.set_active_selections(
21775 &self.selections.disjoint_anchors_arc(),
21776 self.selections.line_mode(),
21777 self.cursor_shape,
21778 cx,
21779 );
21780 }
21781 });
21782 }
21783 }
21784
21785 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21786 cx.emit(EditorEvent::FocusedIn)
21787 }
21788
21789 fn handle_focus_out(
21790 &mut self,
21791 event: FocusOutEvent,
21792 _window: &mut Window,
21793 cx: &mut Context<Self>,
21794 ) {
21795 if event.blurred != self.focus_handle {
21796 self.last_focused_descendant = Some(event.blurred);
21797 }
21798 self.selection_drag_state = SelectionDragState::None;
21799 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21800 }
21801
21802 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21803 self.blink_manager.update(cx, BlinkManager::disable);
21804 self.buffer
21805 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21806
21807 if let Some(blame) = self.blame.as_ref() {
21808 blame.update(cx, GitBlame::blur)
21809 }
21810 if !self.hover_state.focused(window, cx) {
21811 hide_hover(self, cx);
21812 }
21813 if !self
21814 .context_menu
21815 .borrow()
21816 .as_ref()
21817 .is_some_and(|context_menu| context_menu.focused(window, cx))
21818 {
21819 self.hide_context_menu(window, cx);
21820 }
21821 self.take_active_edit_prediction(cx);
21822 cx.emit(EditorEvent::Blurred);
21823 cx.notify();
21824 }
21825
21826 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21827 let mut pending: String = window
21828 .pending_input_keystrokes()
21829 .into_iter()
21830 .flatten()
21831 .filter_map(|keystroke| {
21832 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21833 keystroke.key_char.clone()
21834 } else {
21835 None
21836 }
21837 })
21838 .collect();
21839
21840 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21841 pending = "".to_string();
21842 }
21843
21844 let existing_pending = self
21845 .text_highlights::<PendingInput>(cx)
21846 .map(|(_, ranges)| ranges.to_vec());
21847 if existing_pending.is_none() && pending.is_empty() {
21848 return;
21849 }
21850 let transaction =
21851 self.transact(window, cx, |this, window, cx| {
21852 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21853 let edits = selections
21854 .iter()
21855 .map(|selection| (selection.end..selection.end, pending.clone()));
21856 this.edit(edits, cx);
21857 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21858 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21859 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21860 }));
21861 });
21862 if let Some(existing_ranges) = existing_pending {
21863 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21864 this.edit(edits, cx);
21865 }
21866 });
21867
21868 let snapshot = self.snapshot(window, cx);
21869 let ranges = self
21870 .selections
21871 .all::<usize>(&snapshot.display_snapshot)
21872 .into_iter()
21873 .map(|selection| {
21874 snapshot.buffer_snapshot().anchor_after(selection.end)
21875 ..snapshot
21876 .buffer_snapshot()
21877 .anchor_before(selection.end + pending.len())
21878 })
21879 .collect();
21880
21881 if pending.is_empty() {
21882 self.clear_highlights::<PendingInput>(cx);
21883 } else {
21884 self.highlight_text::<PendingInput>(
21885 ranges,
21886 HighlightStyle {
21887 underline: Some(UnderlineStyle {
21888 thickness: px(1.),
21889 color: None,
21890 wavy: false,
21891 }),
21892 ..Default::default()
21893 },
21894 cx,
21895 );
21896 }
21897
21898 self.ime_transaction = self.ime_transaction.or(transaction);
21899 if let Some(transaction) = self.ime_transaction {
21900 self.buffer.update(cx, |buffer, cx| {
21901 buffer.group_until_transaction(transaction, cx);
21902 });
21903 }
21904
21905 if self.text_highlights::<PendingInput>(cx).is_none() {
21906 self.ime_transaction.take();
21907 }
21908 }
21909
21910 pub fn register_action_renderer(
21911 &mut self,
21912 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21913 ) -> Subscription {
21914 let id = self.next_editor_action_id.post_inc();
21915 self.editor_actions
21916 .borrow_mut()
21917 .insert(id, Box::new(listener));
21918
21919 let editor_actions = self.editor_actions.clone();
21920 Subscription::new(move || {
21921 editor_actions.borrow_mut().remove(&id);
21922 })
21923 }
21924
21925 pub fn register_action<A: Action>(
21926 &mut self,
21927 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21928 ) -> Subscription {
21929 let id = self.next_editor_action_id.post_inc();
21930 let listener = Arc::new(listener);
21931 self.editor_actions.borrow_mut().insert(
21932 id,
21933 Box::new(move |_, window, _| {
21934 let listener = listener.clone();
21935 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21936 let action = action.downcast_ref().unwrap();
21937 if phase == DispatchPhase::Bubble {
21938 listener(action, window, cx)
21939 }
21940 })
21941 }),
21942 );
21943
21944 let editor_actions = self.editor_actions.clone();
21945 Subscription::new(move || {
21946 editor_actions.borrow_mut().remove(&id);
21947 })
21948 }
21949
21950 pub fn file_header_size(&self) -> u32 {
21951 FILE_HEADER_HEIGHT
21952 }
21953
21954 pub fn restore(
21955 &mut self,
21956 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21957 window: &mut Window,
21958 cx: &mut Context<Self>,
21959 ) {
21960 let workspace = self.workspace();
21961 let project = self.project();
21962 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21963 let mut tasks = Vec::new();
21964 for (buffer_id, changes) in revert_changes {
21965 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21966 buffer.update(cx, |buffer, cx| {
21967 buffer.edit(
21968 changes
21969 .into_iter()
21970 .map(|(range, text)| (range, text.to_string())),
21971 None,
21972 cx,
21973 );
21974 });
21975
21976 if let Some(project) =
21977 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21978 {
21979 project.update(cx, |project, cx| {
21980 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21981 })
21982 }
21983 }
21984 }
21985 tasks
21986 });
21987 cx.spawn_in(window, async move |_, cx| {
21988 for (buffer, task) in save_tasks {
21989 let result = task.await;
21990 if result.is_err() {
21991 let Some(path) = buffer
21992 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21993 .ok()
21994 else {
21995 continue;
21996 };
21997 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21998 let Some(task) = cx
21999 .update_window_entity(workspace, |workspace, window, cx| {
22000 workspace
22001 .open_path_preview(path, None, false, false, false, window, cx)
22002 })
22003 .ok()
22004 else {
22005 continue;
22006 };
22007 task.await.log_err();
22008 }
22009 }
22010 }
22011 })
22012 .detach();
22013 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22014 selections.refresh()
22015 });
22016 }
22017
22018 pub fn to_pixel_point(
22019 &self,
22020 source: multi_buffer::Anchor,
22021 editor_snapshot: &EditorSnapshot,
22022 window: &mut Window,
22023 ) -> Option<gpui::Point<Pixels>> {
22024 let source_point = source.to_display_point(editor_snapshot);
22025 self.display_to_pixel_point(source_point, editor_snapshot, window)
22026 }
22027
22028 pub fn display_to_pixel_point(
22029 &self,
22030 source: DisplayPoint,
22031 editor_snapshot: &EditorSnapshot,
22032 window: &mut Window,
22033 ) -> Option<gpui::Point<Pixels>> {
22034 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22035 let text_layout_details = self.text_layout_details(window);
22036 let scroll_top = text_layout_details
22037 .scroll_anchor
22038 .scroll_position(editor_snapshot)
22039 .y;
22040
22041 if source.row().as_f64() < scroll_top.floor() {
22042 return None;
22043 }
22044 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22045 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22046 Some(gpui::Point::new(source_x, source_y))
22047 }
22048
22049 pub fn has_visible_completions_menu(&self) -> bool {
22050 !self.edit_prediction_preview_is_active()
22051 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22052 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22053 })
22054 }
22055
22056 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22057 if self.mode.is_minimap() {
22058 return;
22059 }
22060 self.addons
22061 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22062 }
22063
22064 pub fn unregister_addon<T: Addon>(&mut self) {
22065 self.addons.remove(&std::any::TypeId::of::<T>());
22066 }
22067
22068 pub fn addon<T: Addon>(&self) -> Option<&T> {
22069 let type_id = std::any::TypeId::of::<T>();
22070 self.addons
22071 .get(&type_id)
22072 .and_then(|item| item.to_any().downcast_ref::<T>())
22073 }
22074
22075 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22076 let type_id = std::any::TypeId::of::<T>();
22077 self.addons
22078 .get_mut(&type_id)
22079 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22080 }
22081
22082 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22083 let text_layout_details = self.text_layout_details(window);
22084 let style = &text_layout_details.editor_style;
22085 let font_id = window.text_system().resolve_font(&style.text.font());
22086 let font_size = style.text.font_size.to_pixels(window.rem_size());
22087 let line_height = style.text.line_height_in_pixels(window.rem_size());
22088 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22089 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22090
22091 CharacterDimensions {
22092 em_width,
22093 em_advance,
22094 line_height,
22095 }
22096 }
22097
22098 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22099 self.load_diff_task.clone()
22100 }
22101
22102 fn read_metadata_from_db(
22103 &mut self,
22104 item_id: u64,
22105 workspace_id: WorkspaceId,
22106 window: &mut Window,
22107 cx: &mut Context<Editor>,
22108 ) {
22109 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22110 && !self.mode.is_minimap()
22111 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22112 {
22113 let buffer_snapshot = OnceCell::new();
22114
22115 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22116 && !folds.is_empty()
22117 {
22118 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22119 self.fold_ranges(
22120 folds
22121 .into_iter()
22122 .map(|(start, end)| {
22123 snapshot.clip_offset(start, Bias::Left)
22124 ..snapshot.clip_offset(end, Bias::Right)
22125 })
22126 .collect(),
22127 false,
22128 window,
22129 cx,
22130 );
22131 }
22132
22133 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22134 && !selections.is_empty()
22135 {
22136 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22137 // skip adding the initial selection to selection history
22138 self.selection_history.mode = SelectionHistoryMode::Skipping;
22139 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22140 s.select_ranges(selections.into_iter().map(|(start, end)| {
22141 snapshot.clip_offset(start, Bias::Left)
22142 ..snapshot.clip_offset(end, Bias::Right)
22143 }));
22144 });
22145 self.selection_history.mode = SelectionHistoryMode::Normal;
22146 };
22147 }
22148
22149 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22150 }
22151
22152 fn update_lsp_data(
22153 &mut self,
22154 for_buffer: Option<BufferId>,
22155 window: &mut Window,
22156 cx: &mut Context<'_, Self>,
22157 ) {
22158 self.pull_diagnostics(for_buffer, window, cx);
22159 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22160 }
22161
22162 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22163 if self.ignore_lsp_data() {
22164 return;
22165 }
22166 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22167 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22168 }
22169 }
22170
22171 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22172 if !self.registered_buffers.contains_key(&buffer_id)
22173 && let Some(project) = self.project.as_ref()
22174 {
22175 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22176 project.update(cx, |project, cx| {
22177 self.registered_buffers.insert(
22178 buffer_id,
22179 project.register_buffer_with_language_servers(&buffer, cx),
22180 );
22181 });
22182 } else {
22183 self.registered_buffers.remove(&buffer_id);
22184 }
22185 }
22186 }
22187
22188 fn ignore_lsp_data(&self) -> bool {
22189 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22190 // skip any LSP updates for it.
22191 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22192 }
22193}
22194
22195fn edit_for_markdown_paste<'a>(
22196 buffer: &MultiBufferSnapshot,
22197 range: Range<usize>,
22198 to_insert: &'a str,
22199 url: Option<url::Url>,
22200) -> (Range<usize>, Cow<'a, str>) {
22201 if url.is_none() {
22202 return (range, Cow::Borrowed(to_insert));
22203 };
22204
22205 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22206
22207 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22208 Cow::Borrowed(to_insert)
22209 } else {
22210 Cow::Owned(format!("[{old_text}]({to_insert})"))
22211 };
22212 (range, new_text)
22213}
22214
22215#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
22216pub enum VimFlavor {
22217 Vim,
22218 Helix,
22219}
22220
22221pub fn vim_flavor(cx: &App) -> Option<VimFlavor> {
22222 if vim_mode_setting::HelixModeSetting::try_get(cx)
22223 .map(|helix_mode| helix_mode.0)
22224 .unwrap_or(false)
22225 {
22226 Some(VimFlavor::Helix)
22227 } else if vim_mode_setting::VimModeSetting::try_get(cx)
22228 .map(|vim_mode| vim_mode.0)
22229 .unwrap_or(false)
22230 {
22231 Some(VimFlavor::Vim)
22232 } else {
22233 None // neither vim nor helix mode
22234 }
22235}
22236
22237fn process_completion_for_edit(
22238 completion: &Completion,
22239 intent: CompletionIntent,
22240 buffer: &Entity<Buffer>,
22241 cursor_position: &text::Anchor,
22242 cx: &mut Context<Editor>,
22243) -> CompletionEdit {
22244 let buffer = buffer.read(cx);
22245 let buffer_snapshot = buffer.snapshot();
22246 let (snippet, new_text) = if completion.is_snippet() {
22247 let mut snippet_source = completion.new_text.clone();
22248 // Workaround for typescript language server issues so that methods don't expand within
22249 // strings and functions with type expressions. The previous point is used because the query
22250 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22251 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22252 let previous_point = if previous_point.column > 0 {
22253 cursor_position.to_previous_offset(&buffer_snapshot)
22254 } else {
22255 cursor_position.to_offset(&buffer_snapshot)
22256 };
22257 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22258 && scope.prefers_label_for_snippet_in_completion()
22259 && let Some(label) = completion.label()
22260 && matches!(
22261 completion.kind(),
22262 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22263 )
22264 {
22265 snippet_source = label;
22266 }
22267 match Snippet::parse(&snippet_source).log_err() {
22268 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22269 None => (None, completion.new_text.clone()),
22270 }
22271 } else {
22272 (None, completion.new_text.clone())
22273 };
22274
22275 let mut range_to_replace = {
22276 let replace_range = &completion.replace_range;
22277 if let CompletionSource::Lsp {
22278 insert_range: Some(insert_range),
22279 ..
22280 } = &completion.source
22281 {
22282 debug_assert_eq!(
22283 insert_range.start, replace_range.start,
22284 "insert_range and replace_range should start at the same position"
22285 );
22286 debug_assert!(
22287 insert_range
22288 .start
22289 .cmp(cursor_position, &buffer_snapshot)
22290 .is_le(),
22291 "insert_range should start before or at cursor position"
22292 );
22293 debug_assert!(
22294 replace_range
22295 .start
22296 .cmp(cursor_position, &buffer_snapshot)
22297 .is_le(),
22298 "replace_range should start before or at cursor position"
22299 );
22300
22301 let should_replace = match intent {
22302 CompletionIntent::CompleteWithInsert => false,
22303 CompletionIntent::CompleteWithReplace => true,
22304 CompletionIntent::Complete | CompletionIntent::Compose => {
22305 let insert_mode =
22306 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22307 .completions
22308 .lsp_insert_mode;
22309 match insert_mode {
22310 LspInsertMode::Insert => false,
22311 LspInsertMode::Replace => true,
22312 LspInsertMode::ReplaceSubsequence => {
22313 let mut text_to_replace = buffer.chars_for_range(
22314 buffer.anchor_before(replace_range.start)
22315 ..buffer.anchor_after(replace_range.end),
22316 );
22317 let mut current_needle = text_to_replace.next();
22318 for haystack_ch in completion.label.text.chars() {
22319 if let Some(needle_ch) = current_needle
22320 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22321 {
22322 current_needle = text_to_replace.next();
22323 }
22324 }
22325 current_needle.is_none()
22326 }
22327 LspInsertMode::ReplaceSuffix => {
22328 if replace_range
22329 .end
22330 .cmp(cursor_position, &buffer_snapshot)
22331 .is_gt()
22332 {
22333 let range_after_cursor = *cursor_position..replace_range.end;
22334 let text_after_cursor = buffer
22335 .text_for_range(
22336 buffer.anchor_before(range_after_cursor.start)
22337 ..buffer.anchor_after(range_after_cursor.end),
22338 )
22339 .collect::<String>()
22340 .to_ascii_lowercase();
22341 completion
22342 .label
22343 .text
22344 .to_ascii_lowercase()
22345 .ends_with(&text_after_cursor)
22346 } else {
22347 true
22348 }
22349 }
22350 }
22351 }
22352 };
22353
22354 if should_replace {
22355 replace_range.clone()
22356 } else {
22357 insert_range.clone()
22358 }
22359 } else {
22360 replace_range.clone()
22361 }
22362 };
22363
22364 if range_to_replace
22365 .end
22366 .cmp(cursor_position, &buffer_snapshot)
22367 .is_lt()
22368 {
22369 range_to_replace.end = *cursor_position;
22370 }
22371
22372 CompletionEdit {
22373 new_text,
22374 replace_range: range_to_replace.to_offset(buffer),
22375 snippet,
22376 }
22377}
22378
22379struct CompletionEdit {
22380 new_text: String,
22381 replace_range: Range<usize>,
22382 snippet: Option<Snippet>,
22383}
22384
22385fn insert_extra_newline_brackets(
22386 buffer: &MultiBufferSnapshot,
22387 range: Range<usize>,
22388 language: &language::LanguageScope,
22389) -> bool {
22390 let leading_whitespace_len = buffer
22391 .reversed_chars_at(range.start)
22392 .take_while(|c| c.is_whitespace() && *c != '\n')
22393 .map(|c| c.len_utf8())
22394 .sum::<usize>();
22395 let trailing_whitespace_len = buffer
22396 .chars_at(range.end)
22397 .take_while(|c| c.is_whitespace() && *c != '\n')
22398 .map(|c| c.len_utf8())
22399 .sum::<usize>();
22400 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22401
22402 language.brackets().any(|(pair, enabled)| {
22403 let pair_start = pair.start.trim_end();
22404 let pair_end = pair.end.trim_start();
22405
22406 enabled
22407 && pair.newline
22408 && buffer.contains_str_at(range.end, pair_end)
22409 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22410 })
22411}
22412
22413fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22414 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22415 [(buffer, range, _)] => (*buffer, range.clone()),
22416 _ => return false,
22417 };
22418 let pair = {
22419 let mut result: Option<BracketMatch> = None;
22420
22421 for pair in buffer
22422 .all_bracket_ranges(range.clone())
22423 .filter(move |pair| {
22424 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22425 })
22426 {
22427 let len = pair.close_range.end - pair.open_range.start;
22428
22429 if let Some(existing) = &result {
22430 let existing_len = existing.close_range.end - existing.open_range.start;
22431 if len > existing_len {
22432 continue;
22433 }
22434 }
22435
22436 result = Some(pair);
22437 }
22438
22439 result
22440 };
22441 let Some(pair) = pair else {
22442 return false;
22443 };
22444 pair.newline_only
22445 && buffer
22446 .chars_for_range(pair.open_range.end..range.start)
22447 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22448 .all(|c| c.is_whitespace() && c != '\n')
22449}
22450
22451fn update_uncommitted_diff_for_buffer(
22452 editor: Entity<Editor>,
22453 project: &Entity<Project>,
22454 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22455 buffer: Entity<MultiBuffer>,
22456 cx: &mut App,
22457) -> Task<()> {
22458 let mut tasks = Vec::new();
22459 project.update(cx, |project, cx| {
22460 for buffer in buffers {
22461 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22462 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22463 }
22464 }
22465 });
22466 cx.spawn(async move |cx| {
22467 let diffs = future::join_all(tasks).await;
22468 if editor
22469 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22470 .unwrap_or(false)
22471 {
22472 return;
22473 }
22474
22475 buffer
22476 .update(cx, |buffer, cx| {
22477 for diff in diffs.into_iter().flatten() {
22478 buffer.add_diff(diff, cx);
22479 }
22480 })
22481 .ok();
22482 })
22483}
22484
22485fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22486 let tab_size = tab_size.get() as usize;
22487 let mut width = offset;
22488
22489 for ch in text.chars() {
22490 width += if ch == '\t' {
22491 tab_size - (width % tab_size)
22492 } else {
22493 1
22494 };
22495 }
22496
22497 width - offset
22498}
22499
22500#[cfg(test)]
22501mod tests {
22502 use super::*;
22503
22504 #[test]
22505 fn test_string_size_with_expanded_tabs() {
22506 let nz = |val| NonZeroU32::new(val).unwrap();
22507 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22508 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22509 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22510 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22511 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22512 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22513 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22514 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22515 }
22516}
22517
22518/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22519struct WordBreakingTokenizer<'a> {
22520 input: &'a str,
22521}
22522
22523impl<'a> WordBreakingTokenizer<'a> {
22524 fn new(input: &'a str) -> Self {
22525 Self { input }
22526 }
22527}
22528
22529fn is_char_ideographic(ch: char) -> bool {
22530 use unicode_script::Script::*;
22531 use unicode_script::UnicodeScript;
22532 matches!(ch.script(), Han | Tangut | Yi)
22533}
22534
22535fn is_grapheme_ideographic(text: &str) -> bool {
22536 text.chars().any(is_char_ideographic)
22537}
22538
22539fn is_grapheme_whitespace(text: &str) -> bool {
22540 text.chars().any(|x| x.is_whitespace())
22541}
22542
22543fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22544 text.chars()
22545 .next()
22546 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22547}
22548
22549#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22550enum WordBreakToken<'a> {
22551 Word { token: &'a str, grapheme_len: usize },
22552 InlineWhitespace { token: &'a str, grapheme_len: usize },
22553 Newline,
22554}
22555
22556impl<'a> Iterator for WordBreakingTokenizer<'a> {
22557 /// Yields a span, the count of graphemes in the token, and whether it was
22558 /// whitespace. Note that it also breaks at word boundaries.
22559 type Item = WordBreakToken<'a>;
22560
22561 fn next(&mut self) -> Option<Self::Item> {
22562 use unicode_segmentation::UnicodeSegmentation;
22563 if self.input.is_empty() {
22564 return None;
22565 }
22566
22567 let mut iter = self.input.graphemes(true).peekable();
22568 let mut offset = 0;
22569 let mut grapheme_len = 0;
22570 if let Some(first_grapheme) = iter.next() {
22571 let is_newline = first_grapheme == "\n";
22572 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22573 offset += first_grapheme.len();
22574 grapheme_len += 1;
22575 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22576 if let Some(grapheme) = iter.peek().copied()
22577 && should_stay_with_preceding_ideograph(grapheme)
22578 {
22579 offset += grapheme.len();
22580 grapheme_len += 1;
22581 }
22582 } else {
22583 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22584 let mut next_word_bound = words.peek().copied();
22585 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22586 next_word_bound = words.next();
22587 }
22588 while let Some(grapheme) = iter.peek().copied() {
22589 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22590 break;
22591 };
22592 if is_grapheme_whitespace(grapheme) != is_whitespace
22593 || (grapheme == "\n") != is_newline
22594 {
22595 break;
22596 };
22597 offset += grapheme.len();
22598 grapheme_len += 1;
22599 iter.next();
22600 }
22601 }
22602 let token = &self.input[..offset];
22603 self.input = &self.input[offset..];
22604 if token == "\n" {
22605 Some(WordBreakToken::Newline)
22606 } else if is_whitespace {
22607 Some(WordBreakToken::InlineWhitespace {
22608 token,
22609 grapheme_len,
22610 })
22611 } else {
22612 Some(WordBreakToken::Word {
22613 token,
22614 grapheme_len,
22615 })
22616 }
22617 } else {
22618 None
22619 }
22620 }
22621}
22622
22623#[test]
22624fn test_word_breaking_tokenizer() {
22625 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22626 ("", &[]),
22627 (" ", &[whitespace(" ", 2)]),
22628 ("Ʒ", &[word("Ʒ", 1)]),
22629 ("Ǽ", &[word("Ǽ", 1)]),
22630 ("⋑", &[word("⋑", 1)]),
22631 ("⋑⋑", &[word("⋑⋑", 2)]),
22632 (
22633 "原理,进而",
22634 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22635 ),
22636 (
22637 "hello world",
22638 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22639 ),
22640 (
22641 "hello, world",
22642 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22643 ),
22644 (
22645 " hello world",
22646 &[
22647 whitespace(" ", 2),
22648 word("hello", 5),
22649 whitespace(" ", 1),
22650 word("world", 5),
22651 ],
22652 ),
22653 (
22654 "这是什么 \n 钢笔",
22655 &[
22656 word("这", 1),
22657 word("是", 1),
22658 word("什", 1),
22659 word("么", 1),
22660 whitespace(" ", 1),
22661 newline(),
22662 whitespace(" ", 1),
22663 word("钢", 1),
22664 word("笔", 1),
22665 ],
22666 ),
22667 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22668 ];
22669
22670 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22671 WordBreakToken::Word {
22672 token,
22673 grapheme_len,
22674 }
22675 }
22676
22677 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22678 WordBreakToken::InlineWhitespace {
22679 token,
22680 grapheme_len,
22681 }
22682 }
22683
22684 fn newline() -> WordBreakToken<'static> {
22685 WordBreakToken::Newline
22686 }
22687
22688 for (input, result) in tests {
22689 assert_eq!(
22690 WordBreakingTokenizer::new(input)
22691 .collect::<Vec<_>>()
22692 .as_slice(),
22693 *result,
22694 );
22695 }
22696}
22697
22698fn wrap_with_prefix(
22699 first_line_prefix: String,
22700 subsequent_lines_prefix: String,
22701 unwrapped_text: String,
22702 wrap_column: usize,
22703 tab_size: NonZeroU32,
22704 preserve_existing_whitespace: bool,
22705) -> String {
22706 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22707 let subsequent_lines_prefix_len =
22708 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22709 let mut wrapped_text = String::new();
22710 let mut current_line = first_line_prefix;
22711 let mut is_first_line = true;
22712
22713 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22714 let mut current_line_len = first_line_prefix_len;
22715 let mut in_whitespace = false;
22716 for token in tokenizer {
22717 let have_preceding_whitespace = in_whitespace;
22718 match token {
22719 WordBreakToken::Word {
22720 token,
22721 grapheme_len,
22722 } => {
22723 in_whitespace = false;
22724 let current_prefix_len = if is_first_line {
22725 first_line_prefix_len
22726 } else {
22727 subsequent_lines_prefix_len
22728 };
22729 if current_line_len + grapheme_len > wrap_column
22730 && current_line_len != current_prefix_len
22731 {
22732 wrapped_text.push_str(current_line.trim_end());
22733 wrapped_text.push('\n');
22734 is_first_line = false;
22735 current_line = subsequent_lines_prefix.clone();
22736 current_line_len = subsequent_lines_prefix_len;
22737 }
22738 current_line.push_str(token);
22739 current_line_len += grapheme_len;
22740 }
22741 WordBreakToken::InlineWhitespace {
22742 mut token,
22743 mut grapheme_len,
22744 } => {
22745 in_whitespace = true;
22746 if have_preceding_whitespace && !preserve_existing_whitespace {
22747 continue;
22748 }
22749 if !preserve_existing_whitespace {
22750 // Keep a single whitespace grapheme as-is
22751 if let Some(first) =
22752 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22753 {
22754 token = first;
22755 } else {
22756 token = " ";
22757 }
22758 grapheme_len = 1;
22759 }
22760 let current_prefix_len = if is_first_line {
22761 first_line_prefix_len
22762 } else {
22763 subsequent_lines_prefix_len
22764 };
22765 if current_line_len + grapheme_len > wrap_column {
22766 wrapped_text.push_str(current_line.trim_end());
22767 wrapped_text.push('\n');
22768 is_first_line = false;
22769 current_line = subsequent_lines_prefix.clone();
22770 current_line_len = subsequent_lines_prefix_len;
22771 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22772 current_line.push_str(token);
22773 current_line_len += grapheme_len;
22774 }
22775 }
22776 WordBreakToken::Newline => {
22777 in_whitespace = true;
22778 let current_prefix_len = if is_first_line {
22779 first_line_prefix_len
22780 } else {
22781 subsequent_lines_prefix_len
22782 };
22783 if preserve_existing_whitespace {
22784 wrapped_text.push_str(current_line.trim_end());
22785 wrapped_text.push('\n');
22786 is_first_line = false;
22787 current_line = subsequent_lines_prefix.clone();
22788 current_line_len = subsequent_lines_prefix_len;
22789 } else if have_preceding_whitespace {
22790 continue;
22791 } else if current_line_len + 1 > wrap_column
22792 && current_line_len != current_prefix_len
22793 {
22794 wrapped_text.push_str(current_line.trim_end());
22795 wrapped_text.push('\n');
22796 is_first_line = false;
22797 current_line = subsequent_lines_prefix.clone();
22798 current_line_len = subsequent_lines_prefix_len;
22799 } else if current_line_len != current_prefix_len {
22800 current_line.push(' ');
22801 current_line_len += 1;
22802 }
22803 }
22804 }
22805 }
22806
22807 if !current_line.is_empty() {
22808 wrapped_text.push_str(¤t_line);
22809 }
22810 wrapped_text
22811}
22812
22813#[test]
22814fn test_wrap_with_prefix() {
22815 assert_eq!(
22816 wrap_with_prefix(
22817 "# ".to_string(),
22818 "# ".to_string(),
22819 "abcdefg".to_string(),
22820 4,
22821 NonZeroU32::new(4).unwrap(),
22822 false,
22823 ),
22824 "# abcdefg"
22825 );
22826 assert_eq!(
22827 wrap_with_prefix(
22828 "".to_string(),
22829 "".to_string(),
22830 "\thello world".to_string(),
22831 8,
22832 NonZeroU32::new(4).unwrap(),
22833 false,
22834 ),
22835 "hello\nworld"
22836 );
22837 assert_eq!(
22838 wrap_with_prefix(
22839 "// ".to_string(),
22840 "// ".to_string(),
22841 "xx \nyy zz aa bb cc".to_string(),
22842 12,
22843 NonZeroU32::new(4).unwrap(),
22844 false,
22845 ),
22846 "// xx yy zz\n// aa bb cc"
22847 );
22848 assert_eq!(
22849 wrap_with_prefix(
22850 String::new(),
22851 String::new(),
22852 "这是什么 \n 钢笔".to_string(),
22853 3,
22854 NonZeroU32::new(4).unwrap(),
22855 false,
22856 ),
22857 "这是什\n么 钢\n笔"
22858 );
22859 assert_eq!(
22860 wrap_with_prefix(
22861 String::new(),
22862 String::new(),
22863 format!("foo{}bar", '\u{2009}'), // thin space
22864 80,
22865 NonZeroU32::new(4).unwrap(),
22866 false,
22867 ),
22868 format!("foo{}bar", '\u{2009}')
22869 );
22870}
22871
22872pub trait CollaborationHub {
22873 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22874 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22875 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22876}
22877
22878impl CollaborationHub for Entity<Project> {
22879 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22880 self.read(cx).collaborators()
22881 }
22882
22883 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22884 self.read(cx).user_store().read(cx).participant_indices()
22885 }
22886
22887 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22888 let this = self.read(cx);
22889 let user_ids = this.collaborators().values().map(|c| c.user_id);
22890 this.user_store().read(cx).participant_names(user_ids, cx)
22891 }
22892}
22893
22894pub trait SemanticsProvider {
22895 fn hover(
22896 &self,
22897 buffer: &Entity<Buffer>,
22898 position: text::Anchor,
22899 cx: &mut App,
22900 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22901
22902 fn inline_values(
22903 &self,
22904 buffer_handle: Entity<Buffer>,
22905 range: Range<text::Anchor>,
22906 cx: &mut App,
22907 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22908
22909 fn applicable_inlay_chunks(
22910 &self,
22911 buffer: &Entity<Buffer>,
22912 ranges: &[Range<text::Anchor>],
22913 cx: &mut App,
22914 ) -> Vec<Range<BufferRow>>;
22915
22916 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
22917
22918 fn inlay_hints(
22919 &self,
22920 invalidate: InvalidationStrategy,
22921 buffer: Entity<Buffer>,
22922 ranges: Vec<Range<text::Anchor>>,
22923 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
22924 cx: &mut App,
22925 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
22926
22927 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22928
22929 fn document_highlights(
22930 &self,
22931 buffer: &Entity<Buffer>,
22932 position: text::Anchor,
22933 cx: &mut App,
22934 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22935
22936 fn definitions(
22937 &self,
22938 buffer: &Entity<Buffer>,
22939 position: text::Anchor,
22940 kind: GotoDefinitionKind,
22941 cx: &mut App,
22942 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22943
22944 fn range_for_rename(
22945 &self,
22946 buffer: &Entity<Buffer>,
22947 position: text::Anchor,
22948 cx: &mut App,
22949 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22950
22951 fn perform_rename(
22952 &self,
22953 buffer: &Entity<Buffer>,
22954 position: text::Anchor,
22955 new_name: String,
22956 cx: &mut App,
22957 ) -> Option<Task<Result<ProjectTransaction>>>;
22958}
22959
22960pub trait CompletionProvider {
22961 fn completions(
22962 &self,
22963 excerpt_id: ExcerptId,
22964 buffer: &Entity<Buffer>,
22965 buffer_position: text::Anchor,
22966 trigger: CompletionContext,
22967 window: &mut Window,
22968 cx: &mut Context<Editor>,
22969 ) -> Task<Result<Vec<CompletionResponse>>>;
22970
22971 fn resolve_completions(
22972 &self,
22973 _buffer: Entity<Buffer>,
22974 _completion_indices: Vec<usize>,
22975 _completions: Rc<RefCell<Box<[Completion]>>>,
22976 _cx: &mut Context<Editor>,
22977 ) -> Task<Result<bool>> {
22978 Task::ready(Ok(false))
22979 }
22980
22981 fn apply_additional_edits_for_completion(
22982 &self,
22983 _buffer: Entity<Buffer>,
22984 _completions: Rc<RefCell<Box<[Completion]>>>,
22985 _completion_index: usize,
22986 _push_to_history: bool,
22987 _cx: &mut Context<Editor>,
22988 ) -> Task<Result<Option<language::Transaction>>> {
22989 Task::ready(Ok(None))
22990 }
22991
22992 fn is_completion_trigger(
22993 &self,
22994 buffer: &Entity<Buffer>,
22995 position: language::Anchor,
22996 text: &str,
22997 trigger_in_words: bool,
22998 menu_is_open: bool,
22999 cx: &mut Context<Editor>,
23000 ) -> bool;
23001
23002 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23003
23004 fn sort_completions(&self) -> bool {
23005 true
23006 }
23007
23008 fn filter_completions(&self) -> bool {
23009 true
23010 }
23011}
23012
23013pub trait CodeActionProvider {
23014 fn id(&self) -> Arc<str>;
23015
23016 fn code_actions(
23017 &self,
23018 buffer: &Entity<Buffer>,
23019 range: Range<text::Anchor>,
23020 window: &mut Window,
23021 cx: &mut App,
23022 ) -> Task<Result<Vec<CodeAction>>>;
23023
23024 fn apply_code_action(
23025 &self,
23026 buffer_handle: Entity<Buffer>,
23027 action: CodeAction,
23028 excerpt_id: ExcerptId,
23029 push_to_history: bool,
23030 window: &mut Window,
23031 cx: &mut App,
23032 ) -> Task<Result<ProjectTransaction>>;
23033}
23034
23035impl CodeActionProvider for Entity<Project> {
23036 fn id(&self) -> Arc<str> {
23037 "project".into()
23038 }
23039
23040 fn code_actions(
23041 &self,
23042 buffer: &Entity<Buffer>,
23043 range: Range<text::Anchor>,
23044 _window: &mut Window,
23045 cx: &mut App,
23046 ) -> Task<Result<Vec<CodeAction>>> {
23047 self.update(cx, |project, cx| {
23048 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23049 let code_actions = project.code_actions(buffer, range, None, cx);
23050 cx.background_spawn(async move {
23051 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23052 Ok(code_lens_actions
23053 .context("code lens fetch")?
23054 .into_iter()
23055 .flatten()
23056 .chain(
23057 code_actions
23058 .context("code action fetch")?
23059 .into_iter()
23060 .flatten(),
23061 )
23062 .collect())
23063 })
23064 })
23065 }
23066
23067 fn apply_code_action(
23068 &self,
23069 buffer_handle: Entity<Buffer>,
23070 action: CodeAction,
23071 _excerpt_id: ExcerptId,
23072 push_to_history: bool,
23073 _window: &mut Window,
23074 cx: &mut App,
23075 ) -> Task<Result<ProjectTransaction>> {
23076 self.update(cx, |project, cx| {
23077 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23078 })
23079 }
23080}
23081
23082fn snippet_completions(
23083 project: &Project,
23084 buffer: &Entity<Buffer>,
23085 buffer_position: text::Anchor,
23086 cx: &mut App,
23087) -> Task<Result<CompletionResponse>> {
23088 let languages = buffer.read(cx).languages_at(buffer_position);
23089 let snippet_store = project.snippets().read(cx);
23090
23091 let scopes: Vec<_> = languages
23092 .iter()
23093 .filter_map(|language| {
23094 let language_name = language.lsp_id();
23095 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23096
23097 if snippets.is_empty() {
23098 None
23099 } else {
23100 Some((language.default_scope(), snippets))
23101 }
23102 })
23103 .collect();
23104
23105 if scopes.is_empty() {
23106 return Task::ready(Ok(CompletionResponse {
23107 completions: vec![],
23108 display_options: CompletionDisplayOptions::default(),
23109 is_incomplete: false,
23110 }));
23111 }
23112
23113 let snapshot = buffer.read(cx).text_snapshot();
23114 let executor = cx.background_executor().clone();
23115
23116 cx.background_spawn(async move {
23117 let mut is_incomplete = false;
23118 let mut completions: Vec<Completion> = Vec::new();
23119 for (scope, snippets) in scopes.into_iter() {
23120 let classifier =
23121 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
23122
23123 const MAX_WORD_PREFIX_LEN: usize = 128;
23124 let last_word: String = snapshot
23125 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
23126 .take(MAX_WORD_PREFIX_LEN)
23127 .take_while(|c| classifier.is_word(*c))
23128 .collect::<String>()
23129 .chars()
23130 .rev()
23131 .collect();
23132
23133 if last_word.is_empty() {
23134 return Ok(CompletionResponse {
23135 completions: vec![],
23136 display_options: CompletionDisplayOptions::default(),
23137 is_incomplete: true,
23138 });
23139 }
23140
23141 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
23142 let to_lsp = |point: &text::Anchor| {
23143 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23144 point_to_lsp(end)
23145 };
23146 let lsp_end = to_lsp(&buffer_position);
23147
23148 let candidates = snippets
23149 .iter()
23150 .enumerate()
23151 .flat_map(|(ix, snippet)| {
23152 snippet
23153 .prefix
23154 .iter()
23155 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23156 })
23157 .collect::<Vec<StringMatchCandidate>>();
23158
23159 const MAX_RESULTS: usize = 100;
23160 let mut matches = fuzzy::match_strings(
23161 &candidates,
23162 &last_word,
23163 last_word.chars().any(|c| c.is_uppercase()),
23164 true,
23165 MAX_RESULTS,
23166 &Default::default(),
23167 executor.clone(),
23168 )
23169 .await;
23170
23171 if matches.len() >= MAX_RESULTS {
23172 is_incomplete = true;
23173 }
23174
23175 // Remove all candidates where the query's start does not match the start of any word in the candidate
23176 if let Some(query_start) = last_word.chars().next() {
23177 matches.retain(|string_match| {
23178 split_words(&string_match.string).any(|word| {
23179 // Check that the first codepoint of the word as lowercase matches the first
23180 // codepoint of the query as lowercase
23181 word.chars()
23182 .flat_map(|codepoint| codepoint.to_lowercase())
23183 .zip(query_start.to_lowercase())
23184 .all(|(word_cp, query_cp)| word_cp == query_cp)
23185 })
23186 });
23187 }
23188
23189 let matched_strings = matches
23190 .into_iter()
23191 .map(|m| m.string)
23192 .collect::<HashSet<_>>();
23193
23194 completions.extend(snippets.iter().filter_map(|snippet| {
23195 let matching_prefix = snippet
23196 .prefix
23197 .iter()
23198 .find(|prefix| matched_strings.contains(*prefix))?;
23199 let start = as_offset - last_word.len();
23200 let start = snapshot.anchor_before(start);
23201 let range = start..buffer_position;
23202 let lsp_start = to_lsp(&start);
23203 let lsp_range = lsp::Range {
23204 start: lsp_start,
23205 end: lsp_end,
23206 };
23207 Some(Completion {
23208 replace_range: range,
23209 new_text: snippet.body.clone(),
23210 source: CompletionSource::Lsp {
23211 insert_range: None,
23212 server_id: LanguageServerId(usize::MAX),
23213 resolved: true,
23214 lsp_completion: Box::new(lsp::CompletionItem {
23215 label: snippet.prefix.first().unwrap().clone(),
23216 kind: Some(CompletionItemKind::SNIPPET),
23217 label_details: snippet.description.as_ref().map(|description| {
23218 lsp::CompletionItemLabelDetails {
23219 detail: Some(description.clone()),
23220 description: None,
23221 }
23222 }),
23223 insert_text_format: Some(InsertTextFormat::SNIPPET),
23224 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23225 lsp::InsertReplaceEdit {
23226 new_text: snippet.body.clone(),
23227 insert: lsp_range,
23228 replace: lsp_range,
23229 },
23230 )),
23231 filter_text: Some(snippet.body.clone()),
23232 sort_text: Some(char::MAX.to_string()),
23233 ..lsp::CompletionItem::default()
23234 }),
23235 lsp_defaults: None,
23236 },
23237 label: CodeLabel::plain(matching_prefix.clone(), None),
23238 icon_path: None,
23239 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23240 single_line: snippet.name.clone().into(),
23241 plain_text: snippet
23242 .description
23243 .clone()
23244 .map(|description| description.into()),
23245 }),
23246 insert_text_mode: None,
23247 confirm: None,
23248 })
23249 }))
23250 }
23251
23252 Ok(CompletionResponse {
23253 completions,
23254 display_options: CompletionDisplayOptions::default(),
23255 is_incomplete,
23256 })
23257 })
23258}
23259
23260impl CompletionProvider for Entity<Project> {
23261 fn completions(
23262 &self,
23263 _excerpt_id: ExcerptId,
23264 buffer: &Entity<Buffer>,
23265 buffer_position: text::Anchor,
23266 options: CompletionContext,
23267 _window: &mut Window,
23268 cx: &mut Context<Editor>,
23269 ) -> Task<Result<Vec<CompletionResponse>>> {
23270 self.update(cx, |project, cx| {
23271 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23272 let project_completions = project.completions(buffer, buffer_position, options, cx);
23273 cx.background_spawn(async move {
23274 let mut responses = project_completions.await?;
23275 let snippets = snippets.await?;
23276 if !snippets.completions.is_empty() {
23277 responses.push(snippets);
23278 }
23279 Ok(responses)
23280 })
23281 })
23282 }
23283
23284 fn resolve_completions(
23285 &self,
23286 buffer: Entity<Buffer>,
23287 completion_indices: Vec<usize>,
23288 completions: Rc<RefCell<Box<[Completion]>>>,
23289 cx: &mut Context<Editor>,
23290 ) -> Task<Result<bool>> {
23291 self.update(cx, |project, cx| {
23292 project.lsp_store().update(cx, |lsp_store, cx| {
23293 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23294 })
23295 })
23296 }
23297
23298 fn apply_additional_edits_for_completion(
23299 &self,
23300 buffer: Entity<Buffer>,
23301 completions: Rc<RefCell<Box<[Completion]>>>,
23302 completion_index: usize,
23303 push_to_history: bool,
23304 cx: &mut Context<Editor>,
23305 ) -> Task<Result<Option<language::Transaction>>> {
23306 self.update(cx, |project, cx| {
23307 project.lsp_store().update(cx, |lsp_store, cx| {
23308 lsp_store.apply_additional_edits_for_completion(
23309 buffer,
23310 completions,
23311 completion_index,
23312 push_to_history,
23313 cx,
23314 )
23315 })
23316 })
23317 }
23318
23319 fn is_completion_trigger(
23320 &self,
23321 buffer: &Entity<Buffer>,
23322 position: language::Anchor,
23323 text: &str,
23324 trigger_in_words: bool,
23325 menu_is_open: bool,
23326 cx: &mut Context<Editor>,
23327 ) -> bool {
23328 let mut chars = text.chars();
23329 let char = if let Some(char) = chars.next() {
23330 char
23331 } else {
23332 return false;
23333 };
23334 if chars.next().is_some() {
23335 return false;
23336 }
23337
23338 let buffer = buffer.read(cx);
23339 let snapshot = buffer.snapshot();
23340 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23341 return false;
23342 }
23343 let classifier = snapshot
23344 .char_classifier_at(position)
23345 .scope_context(Some(CharScopeContext::Completion));
23346 if trigger_in_words && classifier.is_word(char) {
23347 return true;
23348 }
23349
23350 buffer.completion_triggers().contains(text)
23351 }
23352}
23353
23354impl SemanticsProvider for Entity<Project> {
23355 fn hover(
23356 &self,
23357 buffer: &Entity<Buffer>,
23358 position: text::Anchor,
23359 cx: &mut App,
23360 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23361 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23362 }
23363
23364 fn document_highlights(
23365 &self,
23366 buffer: &Entity<Buffer>,
23367 position: text::Anchor,
23368 cx: &mut App,
23369 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23370 Some(self.update(cx, |project, cx| {
23371 project.document_highlights(buffer, position, cx)
23372 }))
23373 }
23374
23375 fn definitions(
23376 &self,
23377 buffer: &Entity<Buffer>,
23378 position: text::Anchor,
23379 kind: GotoDefinitionKind,
23380 cx: &mut App,
23381 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23382 Some(self.update(cx, |project, cx| match kind {
23383 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23384 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23385 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23386 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23387 }))
23388 }
23389
23390 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23391 self.update(cx, |project, cx| {
23392 if project
23393 .active_debug_session(cx)
23394 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23395 {
23396 return true;
23397 }
23398
23399 buffer.update(cx, |buffer, cx| {
23400 project.any_language_server_supports_inlay_hints(buffer, cx)
23401 })
23402 })
23403 }
23404
23405 fn inline_values(
23406 &self,
23407 buffer_handle: Entity<Buffer>,
23408 range: Range<text::Anchor>,
23409 cx: &mut App,
23410 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23411 self.update(cx, |project, cx| {
23412 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23413
23414 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23415 })
23416 }
23417
23418 fn applicable_inlay_chunks(
23419 &self,
23420 buffer: &Entity<Buffer>,
23421 ranges: &[Range<text::Anchor>],
23422 cx: &mut App,
23423 ) -> Vec<Range<BufferRow>> {
23424 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23425 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23426 })
23427 }
23428
23429 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23430 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23431 lsp_store.invalidate_inlay_hints(for_buffers)
23432 });
23433 }
23434
23435 fn inlay_hints(
23436 &self,
23437 invalidate: InvalidationStrategy,
23438 buffer: Entity<Buffer>,
23439 ranges: Vec<Range<text::Anchor>>,
23440 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23441 cx: &mut App,
23442 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23443 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23444 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23445 }))
23446 }
23447
23448 fn range_for_rename(
23449 &self,
23450 buffer: &Entity<Buffer>,
23451 position: text::Anchor,
23452 cx: &mut App,
23453 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23454 Some(self.update(cx, |project, cx| {
23455 let buffer = buffer.clone();
23456 let task = project.prepare_rename(buffer.clone(), position, cx);
23457 cx.spawn(async move |_, cx| {
23458 Ok(match task.await? {
23459 PrepareRenameResponse::Success(range) => Some(range),
23460 PrepareRenameResponse::InvalidPosition => None,
23461 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23462 // Fallback on using TreeSitter info to determine identifier range
23463 buffer.read_with(cx, |buffer, _| {
23464 let snapshot = buffer.snapshot();
23465 let (range, kind) = snapshot.surrounding_word(position, None);
23466 if kind != Some(CharKind::Word) {
23467 return None;
23468 }
23469 Some(
23470 snapshot.anchor_before(range.start)
23471 ..snapshot.anchor_after(range.end),
23472 )
23473 })?
23474 }
23475 })
23476 })
23477 }))
23478 }
23479
23480 fn perform_rename(
23481 &self,
23482 buffer: &Entity<Buffer>,
23483 position: text::Anchor,
23484 new_name: String,
23485 cx: &mut App,
23486 ) -> Option<Task<Result<ProjectTransaction>>> {
23487 Some(self.update(cx, |project, cx| {
23488 project.perform_rename(buffer.clone(), position, new_name, cx)
23489 }))
23490 }
23491}
23492
23493fn consume_contiguous_rows(
23494 contiguous_row_selections: &mut Vec<Selection<Point>>,
23495 selection: &Selection<Point>,
23496 display_map: &DisplaySnapshot,
23497 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23498) -> (MultiBufferRow, MultiBufferRow) {
23499 contiguous_row_selections.push(selection.clone());
23500 let start_row = starting_row(selection, display_map);
23501 let mut end_row = ending_row(selection, display_map);
23502
23503 while let Some(next_selection) = selections.peek() {
23504 if next_selection.start.row <= end_row.0 {
23505 end_row = ending_row(next_selection, display_map);
23506 contiguous_row_selections.push(selections.next().unwrap().clone());
23507 } else {
23508 break;
23509 }
23510 }
23511 (start_row, end_row)
23512}
23513
23514fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23515 if selection.start.column > 0 {
23516 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23517 } else {
23518 MultiBufferRow(selection.start.row)
23519 }
23520}
23521
23522fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23523 if next_selection.end.column > 0 || next_selection.is_empty() {
23524 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23525 } else {
23526 MultiBufferRow(next_selection.end.row)
23527 }
23528}
23529
23530impl EditorSnapshot {
23531 pub fn remote_selections_in_range<'a>(
23532 &'a self,
23533 range: &'a Range<Anchor>,
23534 collaboration_hub: &dyn CollaborationHub,
23535 cx: &'a App,
23536 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23537 let participant_names = collaboration_hub.user_names(cx);
23538 let participant_indices = collaboration_hub.user_participant_indices(cx);
23539 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23540 let collaborators_by_replica_id = collaborators_by_peer_id
23541 .values()
23542 .map(|collaborator| (collaborator.replica_id, collaborator))
23543 .collect::<HashMap<_, _>>();
23544 self.buffer_snapshot()
23545 .selections_in_range(range, false)
23546 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23547 if replica_id == ReplicaId::AGENT {
23548 Some(RemoteSelection {
23549 replica_id,
23550 selection,
23551 cursor_shape,
23552 line_mode,
23553 collaborator_id: CollaboratorId::Agent,
23554 user_name: Some("Agent".into()),
23555 color: cx.theme().players().agent(),
23556 })
23557 } else {
23558 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23559 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23560 let user_name = participant_names.get(&collaborator.user_id).cloned();
23561 Some(RemoteSelection {
23562 replica_id,
23563 selection,
23564 cursor_shape,
23565 line_mode,
23566 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23567 user_name,
23568 color: if let Some(index) = participant_index {
23569 cx.theme().players().color_for_participant(index.0)
23570 } else {
23571 cx.theme().players().absent()
23572 },
23573 })
23574 }
23575 })
23576 }
23577
23578 pub fn hunks_for_ranges(
23579 &self,
23580 ranges: impl IntoIterator<Item = Range<Point>>,
23581 ) -> Vec<MultiBufferDiffHunk> {
23582 let mut hunks = Vec::new();
23583 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23584 HashMap::default();
23585 for query_range in ranges {
23586 let query_rows =
23587 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23588 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23589 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23590 ) {
23591 // Include deleted hunks that are adjacent to the query range, because
23592 // otherwise they would be missed.
23593 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23594 if hunk.status().is_deleted() {
23595 intersects_range |= hunk.row_range.start == query_rows.end;
23596 intersects_range |= hunk.row_range.end == query_rows.start;
23597 }
23598 if intersects_range {
23599 if !processed_buffer_rows
23600 .entry(hunk.buffer_id)
23601 .or_default()
23602 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23603 {
23604 continue;
23605 }
23606 hunks.push(hunk);
23607 }
23608 }
23609 }
23610
23611 hunks
23612 }
23613
23614 fn display_diff_hunks_for_rows<'a>(
23615 &'a self,
23616 display_rows: Range<DisplayRow>,
23617 folded_buffers: &'a HashSet<BufferId>,
23618 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23619 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23620 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23621
23622 self.buffer_snapshot()
23623 .diff_hunks_in_range(buffer_start..buffer_end)
23624 .filter_map(|hunk| {
23625 if folded_buffers.contains(&hunk.buffer_id) {
23626 return None;
23627 }
23628
23629 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23630 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23631
23632 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23633 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23634
23635 let display_hunk = if hunk_display_start.column() != 0 {
23636 DisplayDiffHunk::Folded {
23637 display_row: hunk_display_start.row(),
23638 }
23639 } else {
23640 let mut end_row = hunk_display_end.row();
23641 if hunk_display_end.column() > 0 {
23642 end_row.0 += 1;
23643 }
23644 let is_created_file = hunk.is_created_file();
23645 DisplayDiffHunk::Unfolded {
23646 status: hunk.status(),
23647 diff_base_byte_range: hunk.diff_base_byte_range,
23648 display_row_range: hunk_display_start.row()..end_row,
23649 multi_buffer_range: Anchor::range_in_buffer(
23650 hunk.excerpt_id,
23651 hunk.buffer_id,
23652 hunk.buffer_range,
23653 ),
23654 is_created_file,
23655 }
23656 };
23657
23658 Some(display_hunk)
23659 })
23660 }
23661
23662 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23663 self.display_snapshot
23664 .buffer_snapshot()
23665 .language_at(position)
23666 }
23667
23668 pub fn is_focused(&self) -> bool {
23669 self.is_focused
23670 }
23671
23672 pub fn placeholder_text(&self) -> Option<String> {
23673 self.placeholder_display_snapshot
23674 .as_ref()
23675 .map(|display_map| display_map.text())
23676 }
23677
23678 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23679 self.scroll_anchor.scroll_position(&self.display_snapshot)
23680 }
23681
23682 fn gutter_dimensions(
23683 &self,
23684 font_id: FontId,
23685 font_size: Pixels,
23686 max_line_number_width: Pixels,
23687 cx: &App,
23688 ) -> Option<GutterDimensions> {
23689 if !self.show_gutter {
23690 return None;
23691 }
23692
23693 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23694 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23695
23696 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23697 matches!(
23698 ProjectSettings::get_global(cx).git.git_gutter,
23699 GitGutterSetting::TrackedFiles
23700 )
23701 });
23702 let gutter_settings = EditorSettings::get_global(cx).gutter;
23703 let show_line_numbers = self
23704 .show_line_numbers
23705 .unwrap_or(gutter_settings.line_numbers);
23706 let line_gutter_width = if show_line_numbers {
23707 // Avoid flicker-like gutter resizes when the line number gains another digit by
23708 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23709 let min_width_for_number_on_gutter =
23710 ch_advance * gutter_settings.min_line_number_digits as f32;
23711 max_line_number_width.max(min_width_for_number_on_gutter)
23712 } else {
23713 0.0.into()
23714 };
23715
23716 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23717 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23718
23719 let git_blame_entries_width =
23720 self.git_blame_gutter_max_author_length
23721 .map(|max_author_length| {
23722 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23723 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23724
23725 /// The number of characters to dedicate to gaps and margins.
23726 const SPACING_WIDTH: usize = 4;
23727
23728 let max_char_count = max_author_length.min(renderer.max_author_length())
23729 + ::git::SHORT_SHA_LENGTH
23730 + MAX_RELATIVE_TIMESTAMP.len()
23731 + SPACING_WIDTH;
23732
23733 ch_advance * max_char_count
23734 });
23735
23736 let is_singleton = self.buffer_snapshot().is_singleton();
23737
23738 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23739 left_padding += if !is_singleton {
23740 ch_width * 4.0
23741 } else if show_runnables || show_breakpoints {
23742 ch_width * 3.0
23743 } else if show_git_gutter && show_line_numbers {
23744 ch_width * 2.0
23745 } else if show_git_gutter || show_line_numbers {
23746 ch_width
23747 } else {
23748 px(0.)
23749 };
23750
23751 let shows_folds = is_singleton && gutter_settings.folds;
23752
23753 let right_padding = if shows_folds && show_line_numbers {
23754 ch_width * 4.0
23755 } else if shows_folds || (!is_singleton && show_line_numbers) {
23756 ch_width * 3.0
23757 } else if show_line_numbers {
23758 ch_width
23759 } else {
23760 px(0.)
23761 };
23762
23763 Some(GutterDimensions {
23764 left_padding,
23765 right_padding,
23766 width: line_gutter_width + left_padding + right_padding,
23767 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23768 git_blame_entries_width,
23769 })
23770 }
23771
23772 pub fn render_crease_toggle(
23773 &self,
23774 buffer_row: MultiBufferRow,
23775 row_contains_cursor: bool,
23776 editor: Entity<Editor>,
23777 window: &mut Window,
23778 cx: &mut App,
23779 ) -> Option<AnyElement> {
23780 let folded = self.is_line_folded(buffer_row);
23781 let mut is_foldable = false;
23782
23783 if let Some(crease) = self
23784 .crease_snapshot
23785 .query_row(buffer_row, self.buffer_snapshot())
23786 {
23787 is_foldable = true;
23788 match crease {
23789 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23790 if let Some(render_toggle) = render_toggle {
23791 let toggle_callback =
23792 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23793 if folded {
23794 editor.update(cx, |editor, cx| {
23795 editor.fold_at(buffer_row, window, cx)
23796 });
23797 } else {
23798 editor.update(cx, |editor, cx| {
23799 editor.unfold_at(buffer_row, window, cx)
23800 });
23801 }
23802 });
23803 return Some((render_toggle)(
23804 buffer_row,
23805 folded,
23806 toggle_callback,
23807 window,
23808 cx,
23809 ));
23810 }
23811 }
23812 }
23813 }
23814
23815 is_foldable |= self.starts_indent(buffer_row);
23816
23817 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23818 Some(
23819 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23820 .toggle_state(folded)
23821 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23822 if folded {
23823 this.unfold_at(buffer_row, window, cx);
23824 } else {
23825 this.fold_at(buffer_row, window, cx);
23826 }
23827 }))
23828 .into_any_element(),
23829 )
23830 } else {
23831 None
23832 }
23833 }
23834
23835 pub fn render_crease_trailer(
23836 &self,
23837 buffer_row: MultiBufferRow,
23838 window: &mut Window,
23839 cx: &mut App,
23840 ) -> Option<AnyElement> {
23841 let folded = self.is_line_folded(buffer_row);
23842 if let Crease::Inline { render_trailer, .. } = self
23843 .crease_snapshot
23844 .query_row(buffer_row, self.buffer_snapshot())?
23845 {
23846 let render_trailer = render_trailer.as_ref()?;
23847 Some(render_trailer(buffer_row, folded, window, cx))
23848 } else {
23849 None
23850 }
23851 }
23852}
23853
23854impl Deref for EditorSnapshot {
23855 type Target = DisplaySnapshot;
23856
23857 fn deref(&self) -> &Self::Target {
23858 &self.display_snapshot
23859 }
23860}
23861
23862#[derive(Clone, Debug, PartialEq, Eq)]
23863pub enum EditorEvent {
23864 InputIgnored {
23865 text: Arc<str>,
23866 },
23867 InputHandled {
23868 utf16_range_to_replace: Option<Range<isize>>,
23869 text: Arc<str>,
23870 },
23871 ExcerptsAdded {
23872 buffer: Entity<Buffer>,
23873 predecessor: ExcerptId,
23874 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23875 },
23876 ExcerptsRemoved {
23877 ids: Vec<ExcerptId>,
23878 removed_buffer_ids: Vec<BufferId>,
23879 },
23880 BufferFoldToggled {
23881 ids: Vec<ExcerptId>,
23882 folded: bool,
23883 },
23884 ExcerptsEdited {
23885 ids: Vec<ExcerptId>,
23886 },
23887 ExcerptsExpanded {
23888 ids: Vec<ExcerptId>,
23889 },
23890 BufferEdited,
23891 Edited {
23892 transaction_id: clock::Lamport,
23893 },
23894 Reparsed(BufferId),
23895 Focused,
23896 FocusedIn,
23897 Blurred,
23898 DirtyChanged,
23899 Saved,
23900 TitleChanged,
23901 SelectionsChanged {
23902 local: bool,
23903 },
23904 ScrollPositionChanged {
23905 local: bool,
23906 autoscroll: bool,
23907 },
23908 TransactionUndone {
23909 transaction_id: clock::Lamport,
23910 },
23911 TransactionBegun {
23912 transaction_id: clock::Lamport,
23913 },
23914 CursorShapeChanged,
23915 BreadcrumbsChanged,
23916 PushedToNavHistory {
23917 anchor: Anchor,
23918 is_deactivate: bool,
23919 },
23920}
23921
23922impl EventEmitter<EditorEvent> for Editor {}
23923
23924impl Focusable for Editor {
23925 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23926 self.focus_handle.clone()
23927 }
23928}
23929
23930impl Render for Editor {
23931 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23932 let settings = ThemeSettings::get_global(cx);
23933
23934 let mut text_style = match self.mode {
23935 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23936 color: cx.theme().colors().editor_foreground,
23937 font_family: settings.ui_font.family.clone(),
23938 font_features: settings.ui_font.features.clone(),
23939 font_fallbacks: settings.ui_font.fallbacks.clone(),
23940 font_size: rems(0.875).into(),
23941 font_weight: settings.ui_font.weight,
23942 line_height: relative(settings.buffer_line_height.value()),
23943 ..Default::default()
23944 },
23945 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23946 color: cx.theme().colors().editor_foreground,
23947 font_family: settings.buffer_font.family.clone(),
23948 font_features: settings.buffer_font.features.clone(),
23949 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23950 font_size: settings.buffer_font_size(cx).into(),
23951 font_weight: settings.buffer_font.weight,
23952 line_height: relative(settings.buffer_line_height.value()),
23953 ..Default::default()
23954 },
23955 };
23956 if let Some(text_style_refinement) = &self.text_style_refinement {
23957 text_style.refine(text_style_refinement)
23958 }
23959
23960 let background = match self.mode {
23961 EditorMode::SingleLine => cx.theme().system().transparent,
23962 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23963 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23964 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23965 };
23966
23967 EditorElement::new(
23968 &cx.entity(),
23969 EditorStyle {
23970 background,
23971 border: cx.theme().colors().border,
23972 local_player: cx.theme().players().local(),
23973 text: text_style,
23974 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23975 syntax: cx.theme().syntax().clone(),
23976 status: cx.theme().status().clone(),
23977 inlay_hints_style: make_inlay_hints_style(cx),
23978 edit_prediction_styles: make_suggestion_styles(cx),
23979 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23980 show_underlines: self.diagnostics_enabled(),
23981 },
23982 )
23983 }
23984}
23985
23986impl EntityInputHandler for Editor {
23987 fn text_for_range(
23988 &mut self,
23989 range_utf16: Range<usize>,
23990 adjusted_range: &mut Option<Range<usize>>,
23991 _: &mut Window,
23992 cx: &mut Context<Self>,
23993 ) -> Option<String> {
23994 let snapshot = self.buffer.read(cx).read(cx);
23995 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23996 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23997 if (start.0..end.0) != range_utf16 {
23998 adjusted_range.replace(start.0..end.0);
23999 }
24000 Some(snapshot.text_for_range(start..end).collect())
24001 }
24002
24003 fn selected_text_range(
24004 &mut self,
24005 ignore_disabled_input: bool,
24006 _: &mut Window,
24007 cx: &mut Context<Self>,
24008 ) -> Option<UTF16Selection> {
24009 // Prevent the IME menu from appearing when holding down an alphabetic key
24010 // while input is disabled.
24011 if !ignore_disabled_input && !self.input_enabled {
24012 return None;
24013 }
24014
24015 let selection = self
24016 .selections
24017 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
24018 let range = selection.range();
24019
24020 Some(UTF16Selection {
24021 range: range.start.0..range.end.0,
24022 reversed: selection.reversed,
24023 })
24024 }
24025
24026 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24027 let snapshot = self.buffer.read(cx).read(cx);
24028 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24029 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
24030 }
24031
24032 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24033 self.clear_highlights::<InputComposition>(cx);
24034 self.ime_transaction.take();
24035 }
24036
24037 fn replace_text_in_range(
24038 &mut self,
24039 range_utf16: Option<Range<usize>>,
24040 text: &str,
24041 window: &mut Window,
24042 cx: &mut Context<Self>,
24043 ) {
24044 if !self.input_enabled {
24045 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24046 return;
24047 }
24048
24049 self.transact(window, cx, |this, window, cx| {
24050 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24051 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24052 Some(this.selection_replacement_ranges(range_utf16, cx))
24053 } else {
24054 this.marked_text_ranges(cx)
24055 };
24056
24057 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24058 let newest_selection_id = this.selections.newest_anchor().id;
24059 this.selections
24060 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24061 .iter()
24062 .zip(ranges_to_replace.iter())
24063 .find_map(|(selection, range)| {
24064 if selection.id == newest_selection_id {
24065 Some(
24066 (range.start.0 as isize - selection.head().0 as isize)
24067 ..(range.end.0 as isize - selection.head().0 as isize),
24068 )
24069 } else {
24070 None
24071 }
24072 })
24073 });
24074
24075 cx.emit(EditorEvent::InputHandled {
24076 utf16_range_to_replace: range_to_replace,
24077 text: text.into(),
24078 });
24079
24080 if let Some(new_selected_ranges) = new_selected_ranges {
24081 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24082 selections.select_ranges(new_selected_ranges)
24083 });
24084 this.backspace(&Default::default(), window, cx);
24085 }
24086
24087 this.handle_input(text, window, cx);
24088 });
24089
24090 if let Some(transaction) = self.ime_transaction {
24091 self.buffer.update(cx, |buffer, cx| {
24092 buffer.group_until_transaction(transaction, cx);
24093 });
24094 }
24095
24096 self.unmark_text(window, cx);
24097 }
24098
24099 fn replace_and_mark_text_in_range(
24100 &mut self,
24101 range_utf16: Option<Range<usize>>,
24102 text: &str,
24103 new_selected_range_utf16: Option<Range<usize>>,
24104 window: &mut Window,
24105 cx: &mut Context<Self>,
24106 ) {
24107 if !self.input_enabled {
24108 return;
24109 }
24110
24111 let transaction = self.transact(window, cx, |this, window, cx| {
24112 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24113 let snapshot = this.buffer.read(cx).read(cx);
24114 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24115 for marked_range in &mut marked_ranges {
24116 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24117 marked_range.start.0 += relative_range_utf16.start;
24118 marked_range.start =
24119 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24120 marked_range.end =
24121 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24122 }
24123 }
24124 Some(marked_ranges)
24125 } else if let Some(range_utf16) = range_utf16 {
24126 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24127 Some(this.selection_replacement_ranges(range_utf16, cx))
24128 } else {
24129 None
24130 };
24131
24132 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24133 let newest_selection_id = this.selections.newest_anchor().id;
24134 this.selections
24135 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24136 .iter()
24137 .zip(ranges_to_replace.iter())
24138 .find_map(|(selection, range)| {
24139 if selection.id == newest_selection_id {
24140 Some(
24141 (range.start.0 as isize - selection.head().0 as isize)
24142 ..(range.end.0 as isize - selection.head().0 as isize),
24143 )
24144 } else {
24145 None
24146 }
24147 })
24148 });
24149
24150 cx.emit(EditorEvent::InputHandled {
24151 utf16_range_to_replace: range_to_replace,
24152 text: text.into(),
24153 });
24154
24155 if let Some(ranges) = ranges_to_replace {
24156 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24157 s.select_ranges(ranges)
24158 });
24159 }
24160
24161 let marked_ranges = {
24162 let snapshot = this.buffer.read(cx).read(cx);
24163 this.selections
24164 .disjoint_anchors_arc()
24165 .iter()
24166 .map(|selection| {
24167 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24168 })
24169 .collect::<Vec<_>>()
24170 };
24171
24172 if text.is_empty() {
24173 this.unmark_text(window, cx);
24174 } else {
24175 this.highlight_text::<InputComposition>(
24176 marked_ranges.clone(),
24177 HighlightStyle {
24178 underline: Some(UnderlineStyle {
24179 thickness: px(1.),
24180 color: None,
24181 wavy: false,
24182 }),
24183 ..Default::default()
24184 },
24185 cx,
24186 );
24187 }
24188
24189 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24190 let use_autoclose = this.use_autoclose;
24191 let use_auto_surround = this.use_auto_surround;
24192 this.set_use_autoclose(false);
24193 this.set_use_auto_surround(false);
24194 this.handle_input(text, window, cx);
24195 this.set_use_autoclose(use_autoclose);
24196 this.set_use_auto_surround(use_auto_surround);
24197
24198 if let Some(new_selected_range) = new_selected_range_utf16 {
24199 let snapshot = this.buffer.read(cx).read(cx);
24200 let new_selected_ranges = marked_ranges
24201 .into_iter()
24202 .map(|marked_range| {
24203 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24204 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24205 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24206 snapshot.clip_offset_utf16(new_start, Bias::Left)
24207 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24208 })
24209 .collect::<Vec<_>>();
24210
24211 drop(snapshot);
24212 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24213 selections.select_ranges(new_selected_ranges)
24214 });
24215 }
24216 });
24217
24218 self.ime_transaction = self.ime_transaction.or(transaction);
24219 if let Some(transaction) = self.ime_transaction {
24220 self.buffer.update(cx, |buffer, cx| {
24221 buffer.group_until_transaction(transaction, cx);
24222 });
24223 }
24224
24225 if self.text_highlights::<InputComposition>(cx).is_none() {
24226 self.ime_transaction.take();
24227 }
24228 }
24229
24230 fn bounds_for_range(
24231 &mut self,
24232 range_utf16: Range<usize>,
24233 element_bounds: gpui::Bounds<Pixels>,
24234 window: &mut Window,
24235 cx: &mut Context<Self>,
24236 ) -> Option<gpui::Bounds<Pixels>> {
24237 let text_layout_details = self.text_layout_details(window);
24238 let CharacterDimensions {
24239 em_width,
24240 em_advance,
24241 line_height,
24242 } = self.character_dimensions(window);
24243
24244 let snapshot = self.snapshot(window, cx);
24245 let scroll_position = snapshot.scroll_position();
24246 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24247
24248 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24249 let x = Pixels::from(
24250 ScrollOffset::from(
24251 snapshot.x_for_display_point(start, &text_layout_details)
24252 + self.gutter_dimensions.full_width(),
24253 ) - scroll_left,
24254 );
24255 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24256
24257 Some(Bounds {
24258 origin: element_bounds.origin + point(x, y),
24259 size: size(em_width, line_height),
24260 })
24261 }
24262
24263 fn character_index_for_point(
24264 &mut self,
24265 point: gpui::Point<Pixels>,
24266 _window: &mut Window,
24267 _cx: &mut Context<Self>,
24268 ) -> Option<usize> {
24269 let position_map = self.last_position_map.as_ref()?;
24270 if !position_map.text_hitbox.contains(&point) {
24271 return None;
24272 }
24273 let display_point = position_map.point_for_position(point).previous_valid;
24274 let anchor = position_map
24275 .snapshot
24276 .display_point_to_anchor(display_point, Bias::Left);
24277 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24278 Some(utf16_offset.0)
24279 }
24280
24281 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24282 self.input_enabled
24283 }
24284}
24285
24286trait SelectionExt {
24287 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24288 fn spanned_rows(
24289 &self,
24290 include_end_if_at_line_start: bool,
24291 map: &DisplaySnapshot,
24292 ) -> Range<MultiBufferRow>;
24293}
24294
24295impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24296 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24297 let start = self
24298 .start
24299 .to_point(map.buffer_snapshot())
24300 .to_display_point(map);
24301 let end = self
24302 .end
24303 .to_point(map.buffer_snapshot())
24304 .to_display_point(map);
24305 if self.reversed {
24306 end..start
24307 } else {
24308 start..end
24309 }
24310 }
24311
24312 fn spanned_rows(
24313 &self,
24314 include_end_if_at_line_start: bool,
24315 map: &DisplaySnapshot,
24316 ) -> Range<MultiBufferRow> {
24317 let start = self.start.to_point(map.buffer_snapshot());
24318 let mut end = self.end.to_point(map.buffer_snapshot());
24319 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24320 end.row -= 1;
24321 }
24322
24323 let buffer_start = map.prev_line_boundary(start).0;
24324 let buffer_end = map.next_line_boundary(end).0;
24325 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24326 }
24327}
24328
24329impl<T: InvalidationRegion> InvalidationStack<T> {
24330 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24331 where
24332 S: Clone + ToOffset,
24333 {
24334 while let Some(region) = self.last() {
24335 let all_selections_inside_invalidation_ranges =
24336 if selections.len() == region.ranges().len() {
24337 selections
24338 .iter()
24339 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24340 .all(|(selection, invalidation_range)| {
24341 let head = selection.head().to_offset(buffer);
24342 invalidation_range.start <= head && invalidation_range.end >= head
24343 })
24344 } else {
24345 false
24346 };
24347
24348 if all_selections_inside_invalidation_ranges {
24349 break;
24350 } else {
24351 self.pop();
24352 }
24353 }
24354 }
24355}
24356
24357impl<T> Default for InvalidationStack<T> {
24358 fn default() -> Self {
24359 Self(Default::default())
24360 }
24361}
24362
24363impl<T> Deref for InvalidationStack<T> {
24364 type Target = Vec<T>;
24365
24366 fn deref(&self) -> &Self::Target {
24367 &self.0
24368 }
24369}
24370
24371impl<T> DerefMut for InvalidationStack<T> {
24372 fn deref_mut(&mut self) -> &mut Self::Target {
24373 &mut self.0
24374 }
24375}
24376
24377impl InvalidationRegion for SnippetState {
24378 fn ranges(&self) -> &[Range<Anchor>] {
24379 &self.ranges[self.active_index]
24380 }
24381}
24382
24383fn edit_prediction_edit_text(
24384 current_snapshot: &BufferSnapshot,
24385 edits: &[(Range<Anchor>, impl AsRef<str>)],
24386 edit_preview: &EditPreview,
24387 include_deletions: bool,
24388 cx: &App,
24389) -> HighlightedText {
24390 let edits = edits
24391 .iter()
24392 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24393 .collect::<Vec<_>>();
24394
24395 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24396}
24397
24398fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24399 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24400 // Just show the raw edit text with basic styling
24401 let mut text = String::new();
24402 let mut highlights = Vec::new();
24403
24404 let insertion_highlight_style = HighlightStyle {
24405 color: Some(cx.theme().colors().text),
24406 ..Default::default()
24407 };
24408
24409 for (_, edit_text) in edits {
24410 let start_offset = text.len();
24411 text.push_str(edit_text);
24412 let end_offset = text.len();
24413
24414 if start_offset < end_offset {
24415 highlights.push((start_offset..end_offset, insertion_highlight_style));
24416 }
24417 }
24418
24419 HighlightedText {
24420 text: text.into(),
24421 highlights,
24422 }
24423}
24424
24425pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24426 match severity {
24427 lsp::DiagnosticSeverity::ERROR => colors.error,
24428 lsp::DiagnosticSeverity::WARNING => colors.warning,
24429 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24430 lsp::DiagnosticSeverity::HINT => colors.info,
24431 _ => colors.ignored,
24432 }
24433}
24434
24435pub fn styled_runs_for_code_label<'a>(
24436 label: &'a CodeLabel,
24437 syntax_theme: &'a theme::SyntaxTheme,
24438) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24439 let fade_out = HighlightStyle {
24440 fade_out: Some(0.35),
24441 ..Default::default()
24442 };
24443
24444 let mut prev_end = label.filter_range.end;
24445 label
24446 .runs
24447 .iter()
24448 .enumerate()
24449 .flat_map(move |(ix, (range, highlight_id))| {
24450 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24451 style
24452 } else {
24453 return Default::default();
24454 };
24455 let muted_style = style.highlight(fade_out);
24456
24457 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24458 if range.start >= label.filter_range.end {
24459 if range.start > prev_end {
24460 runs.push((prev_end..range.start, fade_out));
24461 }
24462 runs.push((range.clone(), muted_style));
24463 } else if range.end <= label.filter_range.end {
24464 runs.push((range.clone(), style));
24465 } else {
24466 runs.push((range.start..label.filter_range.end, style));
24467 runs.push((label.filter_range.end..range.end, muted_style));
24468 }
24469 prev_end = cmp::max(prev_end, range.end);
24470
24471 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24472 runs.push((prev_end..label.text.len(), fade_out));
24473 }
24474
24475 runs
24476 })
24477}
24478
24479pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24480 let mut prev_index = 0;
24481 let mut prev_codepoint: Option<char> = None;
24482 text.char_indices()
24483 .chain([(text.len(), '\0')])
24484 .filter_map(move |(index, codepoint)| {
24485 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24486 let is_boundary = index == text.len()
24487 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24488 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24489 if is_boundary {
24490 let chunk = &text[prev_index..index];
24491 prev_index = index;
24492 Some(chunk)
24493 } else {
24494 None
24495 }
24496 })
24497}
24498
24499pub trait RangeToAnchorExt: Sized {
24500 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24501
24502 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24503 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24504 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24505 }
24506}
24507
24508impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24509 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24510 let start_offset = self.start.to_offset(snapshot);
24511 let end_offset = self.end.to_offset(snapshot);
24512 if start_offset == end_offset {
24513 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24514 } else {
24515 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24516 }
24517 }
24518}
24519
24520pub trait RowExt {
24521 fn as_f64(&self) -> f64;
24522
24523 fn next_row(&self) -> Self;
24524
24525 fn previous_row(&self) -> Self;
24526
24527 fn minus(&self, other: Self) -> u32;
24528}
24529
24530impl RowExt for DisplayRow {
24531 fn as_f64(&self) -> f64 {
24532 self.0 as _
24533 }
24534
24535 fn next_row(&self) -> Self {
24536 Self(self.0 + 1)
24537 }
24538
24539 fn previous_row(&self) -> Self {
24540 Self(self.0.saturating_sub(1))
24541 }
24542
24543 fn minus(&self, other: Self) -> u32 {
24544 self.0 - other.0
24545 }
24546}
24547
24548impl RowExt for MultiBufferRow {
24549 fn as_f64(&self) -> f64 {
24550 self.0 as _
24551 }
24552
24553 fn next_row(&self) -> Self {
24554 Self(self.0 + 1)
24555 }
24556
24557 fn previous_row(&self) -> Self {
24558 Self(self.0.saturating_sub(1))
24559 }
24560
24561 fn minus(&self, other: Self) -> u32 {
24562 self.0 - other.0
24563 }
24564}
24565
24566trait RowRangeExt {
24567 type Row;
24568
24569 fn len(&self) -> usize;
24570
24571 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24572}
24573
24574impl RowRangeExt for Range<MultiBufferRow> {
24575 type Row = MultiBufferRow;
24576
24577 fn len(&self) -> usize {
24578 (self.end.0 - self.start.0) as usize
24579 }
24580
24581 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24582 (self.start.0..self.end.0).map(MultiBufferRow)
24583 }
24584}
24585
24586impl RowRangeExt for Range<DisplayRow> {
24587 type Row = DisplayRow;
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 = DisplayRow> {
24594 (self.start.0..self.end.0).map(DisplayRow)
24595 }
24596}
24597
24598/// If select range has more than one line, we
24599/// just point the cursor to range.start.
24600fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24601 if range.start.row == range.end.row {
24602 range
24603 } else {
24604 range.start..range.start
24605 }
24606}
24607pub struct KillRing(ClipboardItem);
24608impl Global for KillRing {}
24609
24610const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24611
24612enum BreakpointPromptEditAction {
24613 Log,
24614 Condition,
24615 HitCondition,
24616}
24617
24618struct BreakpointPromptEditor {
24619 pub(crate) prompt: Entity<Editor>,
24620 editor: WeakEntity<Editor>,
24621 breakpoint_anchor: Anchor,
24622 breakpoint: Breakpoint,
24623 edit_action: BreakpointPromptEditAction,
24624 block_ids: HashSet<CustomBlockId>,
24625 editor_margins: Arc<Mutex<EditorMargins>>,
24626 _subscriptions: Vec<Subscription>,
24627}
24628
24629impl BreakpointPromptEditor {
24630 const MAX_LINES: u8 = 4;
24631
24632 fn new(
24633 editor: WeakEntity<Editor>,
24634 breakpoint_anchor: Anchor,
24635 breakpoint: Breakpoint,
24636 edit_action: BreakpointPromptEditAction,
24637 window: &mut Window,
24638 cx: &mut Context<Self>,
24639 ) -> Self {
24640 let base_text = match edit_action {
24641 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24642 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24643 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24644 }
24645 .map(|msg| msg.to_string())
24646 .unwrap_or_default();
24647
24648 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24649 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24650
24651 let prompt = cx.new(|cx| {
24652 let mut prompt = Editor::new(
24653 EditorMode::AutoHeight {
24654 min_lines: 1,
24655 max_lines: Some(Self::MAX_LINES as usize),
24656 },
24657 buffer,
24658 None,
24659 window,
24660 cx,
24661 );
24662 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24663 prompt.set_show_cursor_when_unfocused(false, cx);
24664 prompt.set_placeholder_text(
24665 match edit_action {
24666 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24667 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24668 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24669 },
24670 window,
24671 cx,
24672 );
24673
24674 prompt
24675 });
24676
24677 Self {
24678 prompt,
24679 editor,
24680 breakpoint_anchor,
24681 breakpoint,
24682 edit_action,
24683 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24684 block_ids: Default::default(),
24685 _subscriptions: vec![],
24686 }
24687 }
24688
24689 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24690 self.block_ids.extend(block_ids)
24691 }
24692
24693 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24694 if let Some(editor) = self.editor.upgrade() {
24695 let message = self
24696 .prompt
24697 .read(cx)
24698 .buffer
24699 .read(cx)
24700 .as_singleton()
24701 .expect("A multi buffer in breakpoint prompt isn't possible")
24702 .read(cx)
24703 .as_rope()
24704 .to_string();
24705
24706 editor.update(cx, |editor, cx| {
24707 editor.edit_breakpoint_at_anchor(
24708 self.breakpoint_anchor,
24709 self.breakpoint.clone(),
24710 match self.edit_action {
24711 BreakpointPromptEditAction::Log => {
24712 BreakpointEditAction::EditLogMessage(message.into())
24713 }
24714 BreakpointPromptEditAction::Condition => {
24715 BreakpointEditAction::EditCondition(message.into())
24716 }
24717 BreakpointPromptEditAction::HitCondition => {
24718 BreakpointEditAction::EditHitCondition(message.into())
24719 }
24720 },
24721 cx,
24722 );
24723
24724 editor.remove_blocks(self.block_ids.clone(), None, cx);
24725 cx.focus_self(window);
24726 });
24727 }
24728 }
24729
24730 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24731 self.editor
24732 .update(cx, |editor, cx| {
24733 editor.remove_blocks(self.block_ids.clone(), None, cx);
24734 window.focus(&editor.focus_handle);
24735 })
24736 .log_err();
24737 }
24738
24739 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24740 let settings = ThemeSettings::get_global(cx);
24741 let text_style = TextStyle {
24742 color: if self.prompt.read(cx).read_only(cx) {
24743 cx.theme().colors().text_disabled
24744 } else {
24745 cx.theme().colors().text
24746 },
24747 font_family: settings.buffer_font.family.clone(),
24748 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24749 font_size: settings.buffer_font_size(cx).into(),
24750 font_weight: settings.buffer_font.weight,
24751 line_height: relative(settings.buffer_line_height.value()),
24752 ..Default::default()
24753 };
24754 EditorElement::new(
24755 &self.prompt,
24756 EditorStyle {
24757 background: cx.theme().colors().editor_background,
24758 local_player: cx.theme().players().local(),
24759 text: text_style,
24760 ..Default::default()
24761 },
24762 )
24763 }
24764}
24765
24766impl Render for BreakpointPromptEditor {
24767 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24768 let editor_margins = *self.editor_margins.lock();
24769 let gutter_dimensions = editor_margins.gutter;
24770 h_flex()
24771 .key_context("Editor")
24772 .bg(cx.theme().colors().editor_background)
24773 .border_y_1()
24774 .border_color(cx.theme().status().info_border)
24775 .size_full()
24776 .py(window.line_height() / 2.5)
24777 .on_action(cx.listener(Self::confirm))
24778 .on_action(cx.listener(Self::cancel))
24779 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24780 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24781 }
24782}
24783
24784impl Focusable for BreakpointPromptEditor {
24785 fn focus_handle(&self, cx: &App) -> FocusHandle {
24786 self.prompt.focus_handle(cx)
24787 }
24788}
24789
24790fn all_edits_insertions_or_deletions(
24791 edits: &Vec<(Range<Anchor>, Arc<str>)>,
24792 snapshot: &MultiBufferSnapshot,
24793) -> bool {
24794 let mut all_insertions = true;
24795 let mut all_deletions = true;
24796
24797 for (range, new_text) in edits.iter() {
24798 let range_is_empty = range.to_offset(snapshot).is_empty();
24799 let text_is_empty = new_text.is_empty();
24800
24801 if range_is_empty != text_is_empty {
24802 if range_is_empty {
24803 all_deletions = false;
24804 } else {
24805 all_insertions = false;
24806 }
24807 } else {
24808 return false;
24809 }
24810
24811 if !all_insertions && !all_deletions {
24812 return false;
24813 }
24814 }
24815 all_insertions || all_deletions
24816}
24817
24818struct MissingEditPredictionKeybindingTooltip;
24819
24820impl Render for MissingEditPredictionKeybindingTooltip {
24821 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24822 ui::tooltip_container(cx, |container, cx| {
24823 container
24824 .flex_shrink_0()
24825 .max_w_80()
24826 .min_h(rems_from_px(124.))
24827 .justify_between()
24828 .child(
24829 v_flex()
24830 .flex_1()
24831 .text_ui_sm(cx)
24832 .child(Label::new("Conflict with Accept Keybinding"))
24833 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24834 )
24835 .child(
24836 h_flex()
24837 .pb_1()
24838 .gap_1()
24839 .items_end()
24840 .w_full()
24841 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24842 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24843 }))
24844 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24845 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24846 })),
24847 )
24848 })
24849 }
24850}
24851
24852#[derive(Debug, Clone, Copy, PartialEq)]
24853pub struct LineHighlight {
24854 pub background: Background,
24855 pub border: Option<gpui::Hsla>,
24856 pub include_gutter: bool,
24857 pub type_id: Option<TypeId>,
24858}
24859
24860struct LineManipulationResult {
24861 pub new_text: String,
24862 pub line_count_before: usize,
24863 pub line_count_after: usize,
24864}
24865
24866fn render_diff_hunk_controls(
24867 row: u32,
24868 status: &DiffHunkStatus,
24869 hunk_range: Range<Anchor>,
24870 is_created_file: bool,
24871 line_height: Pixels,
24872 editor: &Entity<Editor>,
24873 _window: &mut Window,
24874 cx: &mut App,
24875) -> AnyElement {
24876 h_flex()
24877 .h(line_height)
24878 .mr_1()
24879 .gap_1()
24880 .px_0p5()
24881 .pb_1()
24882 .border_x_1()
24883 .border_b_1()
24884 .border_color(cx.theme().colors().border_variant)
24885 .rounded_b_lg()
24886 .bg(cx.theme().colors().editor_background)
24887 .gap_1()
24888 .block_mouse_except_scroll()
24889 .shadow_md()
24890 .child(if status.has_secondary_hunk() {
24891 Button::new(("stage", row as u64), "Stage")
24892 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24893 .tooltip({
24894 let focus_handle = editor.focus_handle(cx);
24895 move |_window, cx| {
24896 Tooltip::for_action_in(
24897 "Stage Hunk",
24898 &::git::ToggleStaged,
24899 &focus_handle,
24900 cx,
24901 )
24902 }
24903 })
24904 .on_click({
24905 let editor = editor.clone();
24906 move |_event, _window, cx| {
24907 editor.update(cx, |editor, cx| {
24908 editor.stage_or_unstage_diff_hunks(
24909 true,
24910 vec![hunk_range.start..hunk_range.start],
24911 cx,
24912 );
24913 });
24914 }
24915 })
24916 } else {
24917 Button::new(("unstage", row as u64), "Unstage")
24918 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24919 .tooltip({
24920 let focus_handle = editor.focus_handle(cx);
24921 move |_window, cx| {
24922 Tooltip::for_action_in(
24923 "Unstage Hunk",
24924 &::git::ToggleStaged,
24925 &focus_handle,
24926 cx,
24927 )
24928 }
24929 })
24930 .on_click({
24931 let editor = editor.clone();
24932 move |_event, _window, cx| {
24933 editor.update(cx, |editor, cx| {
24934 editor.stage_or_unstage_diff_hunks(
24935 false,
24936 vec![hunk_range.start..hunk_range.start],
24937 cx,
24938 );
24939 });
24940 }
24941 })
24942 })
24943 .child(
24944 Button::new(("restore", row as u64), "Restore")
24945 .tooltip({
24946 let focus_handle = editor.focus_handle(cx);
24947 move |_window, cx| {
24948 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
24949 }
24950 })
24951 .on_click({
24952 let editor = editor.clone();
24953 move |_event, window, cx| {
24954 editor.update(cx, |editor, cx| {
24955 let snapshot = editor.snapshot(window, cx);
24956 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24957 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24958 });
24959 }
24960 })
24961 .disabled(is_created_file),
24962 )
24963 .when(
24964 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24965 |el| {
24966 el.child(
24967 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24968 .shape(IconButtonShape::Square)
24969 .icon_size(IconSize::Small)
24970 // .disabled(!has_multiple_hunks)
24971 .tooltip({
24972 let focus_handle = editor.focus_handle(cx);
24973 move |_window, cx| {
24974 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
24975 }
24976 })
24977 .on_click({
24978 let editor = editor.clone();
24979 move |_event, window, cx| {
24980 editor.update(cx, |editor, cx| {
24981 let snapshot = editor.snapshot(window, cx);
24982 let position =
24983 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24984 editor.go_to_hunk_before_or_after_position(
24985 &snapshot,
24986 position,
24987 Direction::Next,
24988 window,
24989 cx,
24990 );
24991 editor.expand_selected_diff_hunks(cx);
24992 });
24993 }
24994 }),
24995 )
24996 .child(
24997 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24998 .shape(IconButtonShape::Square)
24999 .icon_size(IconSize::Small)
25000 // .disabled(!has_multiple_hunks)
25001 .tooltip({
25002 let focus_handle = editor.focus_handle(cx);
25003 move |_window, cx| {
25004 Tooltip::for_action_in(
25005 "Previous Hunk",
25006 &GoToPreviousHunk,
25007 &focus_handle,
25008 cx,
25009 )
25010 }
25011 })
25012 .on_click({
25013 let editor = editor.clone();
25014 move |_event, window, cx| {
25015 editor.update(cx, |editor, cx| {
25016 let snapshot = editor.snapshot(window, cx);
25017 let point =
25018 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25019 editor.go_to_hunk_before_or_after_position(
25020 &snapshot,
25021 point,
25022 Direction::Prev,
25023 window,
25024 cx,
25025 );
25026 editor.expand_selected_diff_hunks(cx);
25027 });
25028 }
25029 }),
25030 )
25031 },
25032 )
25033 .into_any_element()
25034}
25035
25036pub fn multibuffer_context_lines(cx: &App) -> u32 {
25037 EditorSettings::try_get(cx)
25038 .map(|settings| settings.excerpt_context_lines)
25039 .unwrap_or(2)
25040 .min(32)
25041}